ConnectivityManager.CONNECTIVITY_ACTION устарел

В Android N на официальном сайте упоминается, что "Приложения, ориентированные на Android N, не получают трансляции CONNECTIVITY_ACTION". И также упоминается, что JobScheduler можно использовать в качестве альтернативы. Но JobScheduler не обеспечивает точно такое же поведение, как и CONNECTIVITY_ACTION.

В моем приложении для Android я использовал эту трансляцию, чтобы узнать состояние сети устройства. Я хотел знать, было ли это состояние CONNECTING или CONNECTED с помощью трансляции CONNECTIVITY_ACTION, и это было лучше всего подходит для моего требования.

Теперь, когда он устарел, может ли кто-нибудь предложить мне альтернативный подход для получения текущего состояния сети?

Ответ 1

Не рекомендуется использовать фоновое приложение для получения изменений состояния сетевого подключения.

Как сказал Дэвид Вассер, вы все равно можете получать уведомления об изменениях подключения, если создается экземпляр компонента приложения (не уничтожен) и вы зарегистрировали свой получатель программно с его контекстом, вместо того, чтобы делать это в манифест.

Или вместо этого вы можете использовать NetworkCallback. В частности, вам нужно будет переопределить onAvailable для изменений подключенного состояния.

Позвольте мне быстро составить фрагмент:

public class ConnectionStateMonitor extends NetworkCallback {

   final NetworkRequest networkRequest;

   public ConnectionStateMonitor() {
       networkRequest = new NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
   }

   public void enable(Context context) {
       ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
       connectivityManager.registerNetworkCallback(networkRequest , this);
   }

   // Likewise, you can have a disable method that simply calls ConnectivityManager.unregisterNetworkCallback(NetworkCallback) too.

   @Override
   public void onAvailable(Network network) {
       // Do what you need to do here
   }
}

Ответ 2

В документации для Android N указано:

Приложения, ориентированные на Android N, не получают CONNECTIVITY_ACTION широковещательные передачи, даже если они имеют манифестные записи для запроса уведомления этих событий. Приложения, работающие на переднем плане, могут CONNECTIVITY_CHANGE в своем основном потоке, если они запрашивают уведомление с BroadcastReceiver.

Это означает, что вы все равно можете зарегистрировать BroadcastReceiver, если ваше приложение работает на переднем плане, чтобы обнаружить изменения в сетевом подключении.

Ответ 3

Я обновлю ответ Sayem для устранения проблем с исправлением ворса.

class ConnectionLiveData(val context: Context) : LiveData<Boolean>() {

    private var connectivityManager: ConnectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager

    private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback

    override fun onActive() {
        super.onActive()
        updateConnection()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> connectivityManager.registerDefaultNetworkCallback(getConnectivityManagerCallback())
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> lollipopNetworkAvailableRequest()
            else -> {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    context.registerReceiver(networkReceiver, IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")) // android.net.ConnectivityManager.CONNECTIVITY_ACTION
                }
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            connectivityManager.unregisterNetworkCallback(connectivityManagerCallback)
        } else {
            context.unregisterReceiver(networkReceiver)
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private fun lollipopNetworkAvailableRequest() {
        val builder = NetworkRequest.Builder()
            .addTransportType(android.net.NetworkCapabilities.TRANSPORT_CELLULAR)
            .addTransportType(android.net.NetworkCapabilities.TRANSPORT_WIFI)
        connectivityManager.registerNetworkCallback(builder.build(), getConnectivityManagerCallback())
    }

    private fun getConnectivityManagerCallback(): ConnectivityManager.NetworkCallback {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

            connectivityManagerCallback = object : ConnectivityManager.NetworkCallback() {
                override fun onAvailable(network: Network?) {
                    postValue(true)
                }

                override fun onLost(network: Network?) {
                    postValue(false)
                }
            }
            return connectivityManagerCallback
        } else {
            throw IllegalAccessError("Should not happened")
        }
    }

    private val networkReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            updateConnection()
        }
    }

    private fun updateConnection() {
        val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
        postValue(activeNetwork?.isConnected == true)
    }
}

И то же использование:

    val connectionLiveData = ConnectionLiveData(context)
        connectionLiveData.observe(this, Observer { isConnected ->
           isConnected?.let {
             // do job
           }
    })

Кстати, спасибо, Сайем, за ваше решение.

Ответ 4

Пожалуйста, сначала проверьте ответ @Amokrane Chentir для поддержки Android N.

Для тех, кто хочет поддержать на всех уровнях API & соблюдайте это в интерфейсе, пожалуйста, проверьте ниже код.

LiveData NetworkConnection:

class ConnectionLiveData(val context: Context) : LiveData<Boolean>(){

    var  intentFilter = IntentFilter(CONNECTIVITY_ACTION)
    private var  connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
    private lateinit var networkCallback : NetworkCallback

    init {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            networkCallback = NetworkCallback(this)
        }
    }

    override fun onActive() {
        super.onActive()
        updateConnection()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> connectivityManager.registerDefaultNetworkCallback(networkCallback)
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
                val builder = NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI)
                connectivityManager.registerNetworkCallback(builder.build(), networkCallback)
            }
            else -> {
                context.registerReceiver(networkReceiver, intentFilter)
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            connectivityManager.unregisterNetworkCallback(networkCallback)
        } else{
            context.unregisterReceiver(networkReceiver)
        }
    }


    private val networkReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            updateConnection()
        }
    }

    fun updateConnection() {
        val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
        postValue(activeNetwork?.isConnectedOrConnecting == true)
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    class NetworkCallback(val liveData : ConnectionLiveData) : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network?) {
            liveData.postValue(true)
        }

        override fun onLost(network: Network?) {
            liveData.postValue(false)
        }
    }
}

наблюдать в пользовательском интерфейсе (действие/фрагмент):

val connectionLiveData = ConnectionLiveData(context)
    connectionLiveData.observe(this, Observer { 
       // do whatever you want with network connectivity change 
})

Ответ 5

Я столкнулся с той же проблемой несколько дней назад, и решил использовать эту библиотеку Android-Job

В этой библиотеке используются JobSchedular, GcmNetworkManager и BroadcastReceiver в зависимости от версии Android, на которой запущено приложение.

Запуск задания довольно легко

new JobRequest.Builder(DemoSyncJob.TAG)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(false)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED) // this is what gets the job done
            .build()
            .schedule();

Ответ 6

Приложения для Android N (Nougat) не получают трансляции CONNECTIVITY_ACTION, определенные в манифесте (см. Svelte).

Возможные решения:

См. также Android O - обнаружение изменений подключения в фоновом режиме.

Ответ 7

Я согласен с ответом, предложенным @rds.

Помните, что CONNECTIVITY_ACTION устарело на уровне API 28.

Если у вас есть требование, чтобы состояние Wi-Fi (подключение/отключение) было обнаружено, несмотря на приложение будет убито, и вы хотите настроить таргетинг на последнюю версию, то вы У меня нет большого выбора.

Вам нужно использовать connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

Вопрос в том, что вы не можете использовать BroadcastReceiver, так как же тогда?

Вы можете использовать JobScheduler или лучше, если WorkManager (Периодический запрос). Почему периодический, потому что если это OneTimeRequest, то он сможет запускаться только один раз и продолжать слушать, пока ваше приложение находится на переднем плане.

Документация гласит:

Обратные вызовы будут по-прежнему вызываться до тех пор, пока приложение не закроется или не будет вызвана ссылка #unregisterNetworkCallback (NetworkCallback)}.

Как только приложение будет удалено или удалено из списка последних приложений, networkCallback не сможет прослушивать.

Итак, вам нужны такие периодические задания, чтобы приложение постоянно слушало. Сколько должна быть продолжительность? Это зависит от вас и зависит от случая к случаю.

Я знаю, что это немного уродливо, но так оно и есть. Одной из проблем может быть то, что если пользовательское устройство находится в режиме ожидания или приложение находится в режиме ожидания, ваша работа может быть отложена.

Ответ 8

Я написал реализацию Kotlin, основанную на ответе Sayam, но без LiveData. Я решил вызвать (на данный момент) самый последний метод API (ConnectivityManager#registerDefaultNetworkCallback), предназначенный для Android Nougat.

/**
 * Observes network connectivity by consulting the [ConnectivityManager].
 * Observing can run infinitely or automatically be stopped after the first response is received.
 */
class ConnectivityObserver @JvmOverloads constructor(

        val context: Context,
        val onConnectionAvailable: () -> Unit,
        val onConnectionLost: () -> Unit = {},
        val shouldStopAfterFirstResponse: Boolean = false

) {

    private val connectivityManager
        get() = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    @Suppress("DEPRECATION")
    private val intentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)

    private val broadCastReceiver = object : BroadcastReceiver() {

        @Suppress("DEPRECATION")
        override fun onReceive(context: Context?, intent: Intent?) {
            if (ConnectivityManager.CONNECTIVITY_ACTION != intent?.action) {
                return
            }
            val networkInfo = connectivityManager.activeNetworkInfo
            if (networkInfo != null && networkInfo.isConnectedOrConnecting) {
                onConnectionAvailable.invoke()
            } else {
                onConnectionLost.invoke()
            }
            if (shouldStopAfterFirstResponse) {
                stop()
            }
        }

    }

    private lateinit var networkCallback: ConnectivityManager.NetworkCallback

    init {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            networkCallback = object : ConnectivityManager.NetworkCallback() {

                override fun onAvailable(network: Network) {
                    super.onAvailable(network)
                    onConnectionAvailable.invoke()
                    if (shouldStopAfterFirstResponse) {
                        stop()
                    }
                }

                override fun onLost(network: Network?) {
                    super.onLost(network)
                    onConnectionLost.invoke()
                    if (shouldStopAfterFirstResponse) {
                        stop()
                    }
                }
            }
        }
    }

    fun start() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            // Decouple from component lifecycle, use application context.
            // See: https://developer.android.com/reference/android/content/Context.html#getApplicationContext()
            context.applicationContext.registerReceiver(broadCastReceiver, intentFilter)
        } else {
            connectivityManager.registerDefaultNetworkCallback(networkCallback)
        }
    }

    fun stop() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            context.applicationContext.unregisterReceiver(broadCastReceiver)
        } else {
            connectivityManager.unregisterNetworkCallback(networkCallback)
        }
    }

}

Использование:

val onConnectionAvailable = TODO()
val connectivityObserver = ConnectivityObserver(context, onConnectionAvailable)
connectivityObserver.start()
connectivityObserver.stop()

или:

val onConnectionAvailable = TODO()
val onConnectionLost = TODO()
ConnectivityObserver(context, 
    onConnectionAvailable, 
    onConnectionLost, 
    shouldStopAfterFirstResponse = true
).start()

Не забудьте добавить разрешение ACCESS_NETWORK_STATE в свой AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Я с нетерпением жду от вас полезных комментариев и улучшений.