ConnectivityManager.CONNECTIVITY_ACTION obsoleto

94

No Android N, é mencionado no site oficial que "Aplicativos direcionados ao Android N não recebem transmissões de CONNECTIVITY_ACTION". E também é mencionado que JobSchedulerpode ser usado como alternativa. Mas o JobSchedulernão fornece exatamente o mesmo comportamento que o CONNECTIVITY_ACTIONbroadcast.

Em meu aplicativo Android, usei essa transmissão para saber o estado da rede do dispositivo. Eu queria saber se esse estado era CONNECTINGou CONNECTEDcom a ajuda de CONNECTIVITY_ACTIONtransmissão e era o mais adequado para minhas necessidades.

Agora que está obsoleto, alguém pode me sugerir uma abordagem alternativa para obter o estado atual da rede?

Raghuram db
fonte
10
E se o OP algum dia desejar algum comportamento que exija o aumento de targetSdkVersionpara N ou mais tarde?
Michael,
1
Bem, eu também sei que, se não direcionar meu aplicativo ao Android, a NI receberei a transmissão. Mas meu aplicativo precisa ser compatível com Android N. Como posso obter o mesmo comportamento de transmissão no Android N? Existe alguma outra abordagem que eu possa tentar? @DavidWasser
Raghuram db
Às vezes acho que faz mais sentido se preocupar com o futuro no futuro. Esta é uma abordagem puramente pragmática da programação. Claro, você sempre pode tentar se certificar de que seu código não usa nenhum recurso obsoleto. Por outro lado, os recursos obsoletos geralmente permanecem por muito tempo e pode ser que seu aplicativo chegue ao fim antes que os recursos obsoletos desapareçam. O Android N é tão novo que eu não gastaria muito tempo me preocupando com ele. Ainda. Apenas meus 2 centavos. Observe que escrevi um comentário para a pergunta e não sugeri que "não faça isso" fosse uma resposta válida.
David Wasser
2
@Raghuramdb Seu aplicativo pode ser executado no Android N mesmo se você não direcionar seu aplicativo para Android N. Você só precisa direcionar o Android N se quiser usar recursos que estão disponíveis apenas no Android N.
David Wasser
2
Você ainda pode usar o BroadcastReceivercom o android.net.conn.CONNECTIVITY_CHANGEfiltro de intent, mesmo ao direcionar a API29, você só precisa registrá-lo no Application.OnCreate. Você simplesmente não receberá nenhuma atualização quando o aplicativo for fechado.
Pierre

Respostas:

96

O que será preterido é a capacidade de um aplicativo em segundo plano receber alterações de estado de conexão de rede.

Como David Wasser disse, você ainda pode ser notificado sobre mudanças de conectividade se o componente do aplicativo for instanciado (não destruído) e você tiver registrado seu receptor programaticamente com seu contexto, em vez de fazer isso no manifesto.

Ou você pode usar NetworkCallback . Em particular, você precisará substituir onAvailable para alterações de estado conectado.

Deixe-me esboçar um snippet rapidamente:

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
   }
}
Amokrane Chentir
fonte
2
Uma vez que esta técnica só funcionará se o aplicativo estiver rodando em primeiro plano. Isso significa que não temos mais capacidade de ouvir o evento de conexão, quando o aplicativo não está sendo executado em primeiro plano? Ter <action android: name = "android.net.conn.CONNECTIVITY_CHANGE" /> no manifest.xml não tem mais efeito no Android N.
Cheok Yan Cheng
2
@CheokYanCheng AFAIK isso é correto. Você precisa ter um processo executado em primeiro plano para ouvir os eventos de conectividade. Parece que a suposição feita pelos engenheiros da estrutura do Android foi que ouvir eventos de conectividade era feito principalmente para saber quando começar a sincronizar dados entre cliente e servidor. Portanto, JobScheduler é a maneira recomendada para esse caso de uso.
Amokrane Chentir
24
lol o que diabos, mais 10 atualizações do Android e tudo o que seremos capazes de escrever é um app hello world
DennisVA
1
Preciso cancelar o registro de NetworkCallback (por exemplo, no método onDestroy da atividade)?
Ruslan Berozov
2
@Ruslan sim, claro, ou você
vazará o
33

Vou atualizar a Sayem'sresposta para corrigir problemas de lint que está mostrando para mim.

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

E mesmo uso:

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

Btw obrigado sayem por sua solução.

Kebab Krabby
fonte
2
Solução incrível!
caixa
2
Solução muito boa para usar dados ao vivo e suporte a versão anterior
Prakash Shukla
Esta é a melhor solução disponível na internet.
Karan Sharma
Muito boa solução! MAS há um "não" - é a maneira errada de usar o método onAvailable (network: Network?), Porque invoca até mesmo a Internet não disponível. É melhor usar onCapabilitiesChanged (network: Network, networkCapabilities: NetworkCapabilities) e verificar networkCapabilities.hasCapability (NET_CAPABILITY_INTERNET) e networkCapabilities.hasCapability (NET_CAPABILITY_VALIDATED).
DmitryKanunnikoff
1
@DmitryKanunnikoff eu atualizei o código.
Kebab Krabby
28

A documentação do Android N afirma:

Os aplicativos direcionados ao Android N não recebem transmissões de CONNECTIVITY_ACTION, mesmo se tiverem entradas de manifesto para solicitar notificação desses eventos. Os aplicativos em execução em primeiro plano ainda podem escutar CONNECTIVITY_CHANGE em seu thread principal se solicitarem uma notificação com um BroadcastReceiver.

Isso significa que você ainda pode registrar um BroadcastReceiverse seu aplicativo estiver sendo executado em primeiro plano, a fim de detectar alterações na conectividade de rede.

David Wasser
fonte
Boa captura sutil :)
Amokrane Chentir
isso significa que o aplicativo deixará de receber transmissões quando não estiver em primeiro plano? (então não posso ouvir em um culto, por exemplo?)
domingo,
1
Não sei ao certo, precisaria testar para ter certeza. No entanto, ao ler a documentação, pareceria que, se seu aplicativo não estivesse em primeiro plano, você não obteria a transmissão Intent.
David Wasser,
1
Mas detectar a mudança de conectividade em segundo plano é obrigatório para qualquer aplicativo sip (VoIP) ... esses aplicativos normalmente são executados em segundo plano por dias e passam para o primeiro plano apenas se uma chamada for recebida (assim como o discador do telefone). Esses aplicativos precisam se reconectar automaticamente em segundo plano. Isso mata todos os aplicativos (que não têm seu próprio servidor push) da plataforma Android, pois eles estarão offline. sempre.
Grisgram
basta usar o serviço de push do Firebase.
Pierre de
20

Verifique primeiro a resposta de @Amokrane Chentir para obter suporte para Android N.

Para quem deseja dar suporte em todos os níveis da API e observar na interface do usuário, verifique o código abaixo.

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

observar na IU (Atividade / Fragmento):

val connectionLiveData = ConnectionLiveData(context)
    connectionLiveData.observe(this, Observer { 
       // do whatever you want with network connectivity change 
})
Sayem
fonte
btw, você não precisa definir IntentFilterexplicitamente. Assim:var intentFilter = IntentFilter(CONNECTIVITY_ACTION)
Ryan Amaral
Obrigado por sua sugestão. Eu não queria criar um objeto toda vez em onActive.
Sayem
Quero dizer que as 2 variáveis ​​/ propriedades globais ( intentFiltere connectivityManager) você não precisa definir explicitamente seu tipo ( IntentFiltere ConnectivityManagerrespectivamente).
Ryan Amaral
7

Eu tive o mesmo problema alguns dias atrás e decidi usar esta biblioteca Android-Job

Esta biblioteca usa e JobSchedular, dependendo de qual versão do Android o aplicativo está sendo executado.GcmNetworkManagerBroadcastReceiver

Começar um trabalho é bastante fácil

new JobRequest.Builder(DemoSyncJob.TAG)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(false)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED) // this is what gets the job done
            .build()
            .schedule();
Noman Rafique
fonte
1
Tentei o mesmo programador e recebi uma exceção como esta. Você está tentando construir um trabalho sem restrições, isso não é permitido. você pode nos ajudar a resolver isso ??
Sanket Kachhela
Usar o Android-Job para essa finalidade não é uma solução muito boa. Destina-se a executar coisas em um horário especificado, uma vez ou periodicamente. Destina-se a trazer suporte retro-compatibilidade para alarmes e tal. Isso vai contra toda a ideia de por que a API mudou, e lendo: developer.android.com/training/monitoring-device-state/… Você pode rapidamente entender o porquê.
pedronveloso
o único problema é que no Android N ele só pode ser agendado para um mínimo de 15 minutos no futuro
Fire Crow
4

Escrevi uma implementação do Kotlin baseada na resposta de Sayam, mas sem LiveData. Decidi invocar (neste momento) o método de API mais recente ( ConnectivityManager#registerDefaultNetworkCallback) que tem como alvo o 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)
        }
    }

}

Uso:

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

ou:

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

Não se esqueça de adicionar a ACCESS_NETWORK_STATEpermissão em seu AndroidManifest.xml :

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

Estou ansioso para ler comentários úteis e melhorias de você.

JJD
fonte
1
Tive que mudar algo para que os retornos de chamada pudessem "tocar as visualizações" em uma Activity (contexto) no thread principal: em (context as AppCompatActivity).runOnUiThread(object: Runnable{ override fun run() { onConnectionAvailable.invoke() } })vez de onConnectionAvailable.invoke(). O mesmo para onConnectionLost.invoke().
Андрей Воробьев
Sim, dependendo do seu caso de uso, você pode precisar alternar os threads. Eu não faria parte da classe, mas sim deixaria que o consumidor da classe cuidasse disso. Mas obrigado pela dica.
JJD
1

Concordo com a resposta sugerida por @rds.

Lembre-se de que CONNECTIVITY_ACTION foi descontinuado no nível 28 da API.

Se você tem o requisito de que o estado Wifi (conectar / desconectar) deve ser detectado apesar do aplicativo ser encerrado e você deseja direcionar a versão mais recente, então você não tem muita escolha.

Você precisa usar connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

A questão é que você não pode usar BroadcastReceiver, então como então?

Você pode usar JobScheduler ou melhor se WorkManager (Solicitação Periódica). Por que Periódico porque se for um OneTimeRequest, ele só poderá ser executado uma vez e continuar ouvindo enquanto seu aplicativo está em primeiro plano.

A documentação diz:

Os retornos de chamada continuarão a ser chamados até que o aplicativo saia ou o link #unregisterNetworkCallback (NetworkCallback)} seja chamado.

Depois que o aplicativo é encerrado ou removido da lista de aplicativos recentes, networkCallback não será capaz de escutar.

Então, você precisa de tais trabalhos periódicos para fazer o aplicativo ouvir continuamente. Qual deve ser a duração? Isso depende de você e depende de cada caso.

Eu sei que é um jeito meio feio, mas é assim que é. Um desafio pode ser que se o dispositivo do usuário estiver no modo Soneca ou o aplicativo estiver no estado de espera, seu trabalho pode ser atrasado.

Wahib Ul Haq
fonte
Lembre-se também de que, em alguns EMUI altamente personalizados, o gerenciador de trabalho do MIUI Android OS (tarefas periódicas) nem sempre precisa funcionar corretamente.
Kebab Krabby
1

Quando registramos um retorno de chamada de rede usando o registerNetworkCallbackmétodo, às vezes ele não dispara e às vezes dispara falso-positivo:

  1. Se iniciarmos um aplicativo com conexão à Internet, o onAvailablemétodo será acionado.
  2. Mas se não houver conexão com a Internet no dispositivo quando iniciamos um aplicativo, nada do NetworkCallbacké chamado (é muito estranho por causa da p. 1)
  3. Se tivermos conexão wi-fi, mas sem onAvailablegatilhos de método de conexão à Internet . E acho que é um comportamento falso-positivo porque esperamos observar a conexão com a Internet.

Como você pode ver no código abaixo, por padrão, a conexão com a Internet está disponível e dispara somente se mudar. Sem gatilhos falso-positivos.

Resuma esta e esta respostas (mas apenas para 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)
    }
}

Como fornecer as dependências

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

E como usar:

@Inject
lateinit var connectionManager: ConnectionManager

//....

viewLifecycleOwner.observe(connectionManager) { isInternetAvailable ->
    // TODO 
}
Bitvale
fonte
0

Com base na resposta de @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)
        }
    }
}

E usando quase o mesmo que na resposta original (se observar de uma atividade, por exemplo):

ConnectivityWatcher(this).observe(this, Observer {
    Log.i("*-*-*", "is internet available? - ${if (it) "Yes" else "No"}")
})
DmitryKanunnikoff
fonte