ConnectivityManager.CONNECTIVITY_ACTION устарел

95

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

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

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

Рагурам дб
источник
10
А что, если OP когда-нибудь захочет какое-то поведение, которое потребует повышения targetSdkVersionдо N или более поздней версии?
Майкл
1
Что ж, я тоже знаю, что если я не нацеливаю свое приложение на Android, я получу трансляцию. Но мое приложение должно поддерживать Android N. Как добиться того же поведения при широковещании в Android N? Есть ли другой подход, который я могу попробовать? @DavidWasser
db
Иногда я думаю, что имеет смысл беспокоиться о будущем в будущем. Это чисто прагматический подход к программированию. Конечно, вы всегда можете попытаться убедиться, что в вашем коде не используются устаревшие функции. С другой стороны, устаревшие функции обычно остаются в наличии в течение длительного времени, и может случиться так, что ваше приложение будет прекращено до того, как устаревшие функции исчезнут. Android N настолько нов, что я бы не стал тратить много времени на то, чтобы беспокоиться об этом. Пока что. Только мои 2 цента. Обратите внимание, что я написал комментарий к вопросу и не сказал, что «не делайте этого» правильным ответом.
Дэвид Вассер
2
@Raghuramdb Ваше приложение может работать на Android N, даже если вы не нацеливаете свое приложение на Android N. Вам нужно настроить таргетинг на Android N, только если вы хотите использовать функции, которые доступны только в Android N.
Дэвид Вассер,
2
Вы все равно можете использовать фильтр BroadcastReceiverс android.net.conn.CONNECTIVITY_CHANGEнамерением даже при таргетинге на API29, вам просто нужно зарегистрировать его в Application.OnCreate. Вы просто не получите никаких обновлений, когда приложение будет закрыто.
Pierre

Ответы:

101

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

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

Или вы можете использовать вместо этого 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
Поскольку этот метод будет работать только в том случае, если приложение работает на переднем плане. Означает ли это, что у нас больше нет возможности прослушивать событие подключения, когда приложение не работает на переднем плане? Наличие <action android: name = "android.net.conn.CONNECTIVITY_CHANGE" /> в manifest.xml больше не оказывает никакого влияния на Android N.
Cheok Yan Cheng
2
@CheokYanCheng AFAIK, это правильно. У вас должен быть процесс, который запускается на переднем плане, чтобы отслеживать события подключения. Похоже, что инженеры фреймворка Android сделали предположение, что прослушивание событий подключения в основном было сделано для того, чтобы знать, когда начать синхронизацию данных между клиентом и сервером. Таким образом, JobScheduler является рекомендуемым способом для этого варианта использования.
Amokrane Chentir
28
lol, черт возьми, еще 10 обновлений для Android, и все, что мы сможем написать, это приложение Hello World
DennisVA
1
Нужно ли мне отменить регистрацию NetworkCallback (например, в методе onDestroy активности)?
Руслан Берозов
2
@Ruslan да, конечно, а то вы утеките то, что зарегистрировано
DennisVA
35

Я обновлю Sayem'sответ, чтобы исправить проблемы с ворсом, которые он мне показывает.

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

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

    private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback

    private val networkRequestBuilder: NetworkRequest.Builder = NetworkRequest.Builder()
        .addTransportType(android.net.NetworkCapabilities.TRANSPORT_CELLULAR)
        .addTransportType(android.net.NetworkCapabilities.TRANSPORT_WIFI)

    override fun onActive() {
        super.onActive()
        updateConnection()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> connectivityManager.registerDefaultNetworkCallback(getConnectivityMarshmallowManagerCallback())
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> marshmallowNetworkAvailableRequest()
            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() {
        connectivityManager.registerNetworkCallback(networkRequestBuilder.build(), getConnectivityLollipopManagerCallback())
    }

    @TargetApi(Build.VERSION_CODES.M)
    private fun marshmallowNetworkAvailableRequest() {
    connectivityManager.registerNetworkCallback(networkRequestBuilder.build(), getConnectivityMarshmallowManagerCallback())
    }

    private fun getConnectivityLollipopManagerCallback(): 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("Accessing wrong API version")
       }
    }

    private fun getConnectivityMarshmallowManagerCallback(): ConnectivityManager.NetworkCallback {
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          connectivityManagerCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onCapabilitiesChanged(network: Network?, networkCapabilities: NetworkCapabilities?) {
                networkCapabilities?.let { capabilities ->
                    if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                        postValue(true)
                    }
                }
            }
            override fun onLost(network: Network?) {
                postValue(false)
            }
        }
        return connectivityManagerCallback
    } else {
        throw IllegalAccessError("Accessing wrong API version")
    }

    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
           }
    })

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

Кебаб Крабби
источник
2
Замечательное решение!
бокс
2
Очень хорошее решение для использования данных в реальном времени и поддержки более старой версии
Пракаш Шукла
Это лучшее решение, доступное в Интернете.
Каран Шарма
Очень хорошее решение! НО есть одно «нет» - это неправильный способ использования метода onAvailable (network: Network?), Потому что он вызывает, даже если Интернет недоступен. Лучше использовать onCapabilitiesChanged (network: Network, networkCapabilities: NetworkCapabilities) и проверить networkCapabilities.hasCapability (NET_CAPABILITY_INTERNET) и networkCapabilities.hasCapability (NET_CAPABILITY_VALIDATED).
DmitryKanunnikoff
как получить ip и тип сети в этой кодовой базе?
A_rmas
29

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

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

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

Дэвид Вассер
источник
Хороший тонкий улов :)
Амокран Чентир
Означает ли это, что приложение перестанет получать трансляции, если оно не на переднем плане? (так что я не могу слушать это, например, в сервисе?)
sundie
1
Я не знаю точно, мне нужно было бы проверить это, чтобы быть уверенным. Однако при чтении документации может показаться, что если ваше приложение не находится на переднем плане, вы не получите трансляцию Intent.
Дэвид Вассер
2
Но обнаружение изменения подключения в фоновом режиме является обязательным для любых sip-приложений (VoIP) ... эти приложения обычно работают в фоновом режиме в течение нескольких дней и переходят на передний план только при поступлении вызова (как и ваш номеронабиратель на телефоне). Эти приложения должны автоматически повторно подключаться в фоновом режиме. Это убивает все эти приложения (у которых нет собственного push-сервера) с платформы Android, поскольку они будут отключены. всегда.
Grisgram
просто используйте службу push firebase.
Pierre
21

Пожалуйста, проверьте первый ответ @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)
        }
    }
}

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

val connectionLiveData = ConnectionLiveData(context)
    connectionLiveData.observe(this, Observer { 
       // do whatever you want with network connectivity change 
})
Сайем
источник
кстати, вам не нужно определять IntentFilterявно. Вроде так:var intentFilter = IntentFilter(CONNECTIVITY_ACTION)
Райан Амарал
спасибо за ваше предложение. Я не хотел каждый раз создавать объект в onActive.
Sayem
Я имею в виду, что 2 глобальные переменные / свойства ( intentFilterи connectivityManager) вам не нужно явно определять их тип ( IntentFilterи ConnectivityManagerсоответственно).
Райан Амарал,
Большая часть этого (к сожалению) устарела ..
Эндрю,
7

Несколько дней назад я столкнулся с той же проблемой и решил использовать эту библиотеку 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();
Номан Рафик
источник
1
Я пробовал тот же планировщик и получаю подобное исключение. Вы пытаетесь создать задание без ограничений, это запрещено. не могли бы вы помочь нам решить эту проблему?
Санкет Качела
Использование Android-Job для этой цели действительно не очень хорошее решение. Он предназначен для запуска вещей в указанное время, один раз или периодически. Он предназначен для поддержки ретро-совместимости для сигналов тревоги и тому подобного. Это противоречит всей идее того, почему изменился API, и прочтению: developer.android.com/training/monitoring-device-state/ ... Вы можете быстро понять, почему.
pedronveloso
Единственная проблема в том, что в Android N это может быть запланировано не менее чем на 15 минут в будущем
Fire Crow
4

Я написал реализацию Kotlin, основанную на ответе Саяма, но без него 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" />

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

JJD
источник
1
Мне пришлось что-то изменить для обратных вызовов, чтобы иметь возможность «касаться представлений» в Activity (контексте) в основном потоке: (context as AppCompatActivity).runOnUiThread(object: Runnable{ override fun run() { onConnectionAvailable.invoke() } })вместо onConnectionAvailable.invoke(). То же самое для onConnectionLost.invoke().
Андрей Воробьев
Да, в зависимости от вашего варианта использования вам может потребоваться переключить потоки. Я бы не стал делать его частью класса, а вместо этого позволил бы потребителю класса позаботиться об этом. Но спасибо за подсказку.
JJD
4

На основе ответа @ KebabKrabby:

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Context.CONNECTIVITY_SERVICE
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.ConnectivityManager.CONNECTIVITY_ACTION
import android.net.ConnectivityManager.EXTRA_NO_CONNECTIVITY
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.os.Build
import androidx.lifecycle.LiveData

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

    private lateinit var networkCallback: ConnectivityManager.NetworkCallback
    private lateinit var broadcastReceiver: BroadcastReceiver

    override fun onActive() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
            networkCallback = createNetworkCallback()
            cm.registerDefaultNetworkCallback(networkCallback)
        } else {
            val intentFilter = IntentFilter(CONNECTIVITY_ACTION)
            broadcastReceiver = createBroadcastReceiver()
            context.registerReceiver(broadcastReceiver, intentFilter)
        }
    }

    override fun onInactive() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
            cm.unregisterNetworkCallback(networkCallback)
        } else {
            context.unregisterReceiver(broadcastReceiver)
        }
    }

    private fun createNetworkCallback() = object : ConnectivityManager.NetworkCallback() {

        override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
        ) {
            val isInternet = networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)
            val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
            postValue(isInternet && isValidated)
        }

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

    private fun createBroadcastReceiver() = object : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            val isNoConnectivity = intent?.extras?.getBoolean(EXTRA_NO_CONNECTIVITY) ?: true
            postValue(!isNoConnectivity)
        }
    }
}

И использование его почти так же, как в исходном ответе (например, если наблюдать из Activity):

ConnectivityWatcher(this).observe(this, Observer {
    Log.i("*-*-*", "is internet available? - ${if (it) "Yes" else "No"}")
})
ДмитрийКанунникофф
источник
2

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

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

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

патроны
источник
1

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

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

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

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

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

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

В документации говорится:

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

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

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

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

Вахиб Уль Хак
источник
Также имейте в виду, что на некоторых сильно настроенных EMUI, MIUI Android OS workManager (периодические задачи) не обязательно должен работать правильно.
Kebab Krabby
1

Когда мы регистрируем сетевой обратный вызов с помощью этого registerNetworkCallbackметода, иногда он не срабатывает, а иногда вызывает ложное срабатывание:

  1. Если мы запустим приложение с подключением к Интернету, onAvailableметод сработает.
  2. Но если при запуске приложения на устройстве нет подключения к Интернету, ничего из этого не NetworkCallbackвызывается (это очень странно из-за п. 1)
  3. Если у нас есть подключение к Wi-Fi, но без подключения к Интернету, onAvailableметод срабатывает. И я думаю, что это ложноположительное поведение, потому что мы ожидаем наблюдения за интернет-соединением.

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

Просто резюмируйте это и эти ответы (но только для API> = 21):

class ConnectionManager @Inject constructor(
    private val connectivityManager: ConnectivityManager,
    private val disposable: CompositeDisposable,
    private val singleTransformer: SingleTransformer<*, *>
) : LiveData<Boolean>() {

    private var isNetworkAvailable = true

    private val builder = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)

    private val callback = object : ConnectivityManager.NetworkCallback() {

        override fun onAvailable(network: Network) {
            ping()
        }

        override fun onLost(network: Network) {
            ping()
        }
    }

    private fun ping() {
        disposable.add(
            Single.fromCallable {
                try {
                    val timeoutMs = 1500
                    val socket = Socket()
                    val socketAddress = InetSocketAddress("8.8.8.8", 53)

                    socket.connect(socketAddress, timeoutMs)
                    socket.close()
                    true
                } catch (e: IOException) {
                    false
                }
            }
                .compose(singleTransformer as SingleTransformer<Boolean, Boolean>)
                .subscribeBy {
                    if (isNetworkAvailable != it){
                        value = it
                        isNetworkAvailable = it
                    }
                }
        )
    }

    override fun onActive() {
        ping()
        connectivityManager.registerNetworkCallback(builder.build(), callback)
    }

    override fun onInactive() {
        disposable.clear()
        connectivityManager.unregisterNetworkCallback(callback)
    }
}

Как предоставить зависимости

@Provides
fun provideTransformer(): SingleTransformer<Boolean, Boolean> {
    return SingleTransformer<Boolean, Boolean> { upstream: Single<Boolean> ->
        upstream.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
    }
}

@Singleton
@Provides
fun provideConnectivityManager(context: Context): ConnectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

@Singleton
@Provides
fun provideConnectionManager(connectivityManager: ConnectivityManager, singleTransformer: SingleTransformer<Boolean, Boolean>): ConnectionManager =
        ConnectionManager(connectivityManager, singleTransformer)

И как пользоваться:

@Inject
lateinit var connectionManager: ConnectionManager

//....

viewLifecycleOwner.observe(connectionManager) { isInternetAvailable ->
    // TODO 
}
Bitvale
источник