ConnectivityManager.CONNECTIVITY_ACTION obsoleto


94

In Android N, sul sito ufficiale viene indicato che "Le app destinate ad Android N non ricevono trasmissioni CONNECTIVITY_ACTION". Ed è anche detto che JobSchedulerpuò essere usato come alternativa. Ma JobSchedulernon fornisce esattamente lo stesso comportamento della CONNECTIVITY_ACTIONtrasmissione.

Nella mia applicazione Android, stavo usando questa trasmissione per conoscere lo stato della rete del dispositivo. Volevo sapere se questo stato era CONNECTINGo CONNECTEDcon l'aiuto della CONNECTIVITY_ACTIONtrasmissione ed era più adatto alle mie esigenze.

Ora che è deprecato, qualcuno può suggerirmi l'approccio alternativo per ottenere lo stato corrente della rete?


10
E se un giorno l'OP volesse un comportamento che richiede di aumentare il targetSdkVersiona N o più tardi?
Michael

1
Beh, anch'io so che se non indirizzo la mia applicazione ad Android NI riceverà la trasmissione. Ma la mia applicazione deve supportare Android N. Come posso ottenere lo stesso comportamento di trasmissione in Android N? C'è qualche altro approccio che posso provare? @DavidWasser
Raghuram db

A volte penso che abbia più senso preoccuparsi del futuro in futuro. Questo è un approccio puramente pragmatico alla programmazione. Ovviamente puoi sempre provare ad assicurarti che il tuo codice non utilizzi funzionalità deprecate. D'altra parte, le funzionalità deprecate di solito restano in circolazione per molto tempo e potrebbe essere che la tua app finisca prima che le funzionalità deprecate scompaiano. Android N è così nuovo che non passerei molto tempo a preoccuparmene. Ancora. Solo i miei 2 centesimi. Tieni presente che ho scritto un commento alla domanda e non ho suggerito che "non farlo" fosse una risposta valida.
David Wasser

2
@Raghuramdb La tua app può essere eseguita su Android N anche se non indirizzi la tua app ad Android N. Devi scegliere come target Android N solo se desideri utilizzare le funzionalità disponibili solo in Android N.
David Wasser

2
Puoi comunque utilizzare il BroadcastReceivercon il android.net.conn.CONNECTIVITY_CHANGEfiltro intent anche quando scegli come target API29, devi solo registrarlo Application.OnCreate. Non riceverai alcun aggiornamento quando l'app è chiusa.
Pierre

Risposte:


97

Ciò che sarà deprecato è la possibilità per un'applicazione in background di ricevere le modifiche allo stato della connessione di rete.

Come ha detto David Wasser , puoi comunque ricevere una notifica delle modifiche alla connettività se il componente dell'app viene istanziato (non distrutto) e hai registrato il ricevitore a livello di codice con il suo contesto, invece di farlo nel manifest.

Oppure puoi utilizzare NetworkCallback . In particolare, sarà necessario eseguire l'override di onAvailable per le modifiche allo stato connesso.

Fammi disegnare rapidamente uno snippet:

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
Poiché questa tecnica sarà in grado di funzionare solo se l'app è in esecuzione in primo piano. Ciò significa che non abbiamo più la possibilità di ascoltare l'evento di connessione, quando l'app non è in esecuzione in primo piano? La presenza di <action android: name = "android.net.conn.CONNECTIVITY_CHANGE" /> in manifest.xml non ha più alcun effetto su Android N.
Cheok Yan Cheng

2
@CheokYanCheng AFAIK è corretto. È necessario disporre di un processo in esecuzione in primo piano per ascoltare gli eventi di connettività. Sembra che l'ipotesi fatta dagli ingegneri del framework Android fosse che l'ascolto degli eventi di connettività fosse fatto principalmente per sapere quando iniziare a sincronizzare i dati tra client e server. Pertanto, JobScheduler è il modo consigliato per quel caso d'uso.
Amokrane Chentir

25
lol che diavolo, altri 10 aggiornamenti Android e tutto quello che saremo in grado di scrivere è un'app Hello World
DennisVA

1
Devo annullare la registrazione di NetworkCallback (ad esempio, nel metodo onDestroy dell'attività)?
Ruslan Berozov

2
@Ruslan sì, naturalmente, o trapelerai tutto ciò che è registrato
DennisVA

34

Aggiornerò la Sayem'srisposta per risolvere i problemi di lanugine che mi vengono mostrati.

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 lo stesso utilizzo:

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

Btw grazie Sayem per la tua soluzione.


2
Soluzione straordinaria!
box

2
Ottima soluzione per utilizzare dati in tempo reale e supportare la versione precedente
Prakash Shukla

Questa è la migliore soluzione disponibile su Internet.
Karan Sharma,

Ottima soluzione! MA ce n'è uno "non" - è un modo sbagliato di usare il metodo onAvailable (network: Network?), Perché invoca anche Internet non disponibile. È meglio usare onCapabilitiesChanged (network: Network, networkCapabilities: NetworkCapabilities) e controllare networkCapabilities.hasCapability (NET_CAPABILITY_INTERNET) e networkCapabilities.hasCapability (NET_CAPABILITY_VALIDATED).
DmitryKanunnikoff

come ottenere l'ip e il tipo di rete all'interno di questa base di codice?
A_rmas il

28

La documentazione per Android N afferma:

Le app destinate ad Android N non ricevono trasmissioni CONNECTIVITY_ACTION, anche se dispongono di voci manifest per richiedere la notifica di questi eventi. Le app in esecuzione in primo piano possono ancora ascoltare CONNECTIVITY_CHANGE sul thread principale se richiedono una notifica con un BroadcastReceiver.

Ciò significa che puoi ancora registrare a BroadcastReceiverse la tua app è in esecuzione in primo piano, al fine di rilevare i cambiamenti nella connettività di rete.


Bella cattura sottile :)
Amokrane Chentir

significa che l'app smetterà di ricevere le trasmissioni una volta che non è in primo piano? (Quindi non posso ascoltarlo in un servizio, ad esempio?)
Sundie

1
Non lo so per certo, avrei bisogno di provarlo per esserne sicuro. Tuttavia, leggendo la documentazione sembrerebbe che se la tua app non è in primo piano, non riceverai la trasmissione Intent.
David Wasser

2
Ma per rilevare il cambiamento di connettività in background è obbligatorio per qualsiasi sip-app (VoIP) ... quelle app sono normalmente in esecuzione in background per giorni e passano in primo piano solo se arriva una chiamata (proprio come il tuo dialer del telefono) .. Queste app devono riconnettersi automaticamente in background. Questo uccide tutte quelle app (che non hanno il proprio server push) dalla piattaforma Android poiché saranno offline. sempre.
Grisgram

basta usare il servizio push firebase.
Pierre

21

Si prega di controllare la prima risposta @Amokrane Chentir per il supporto Android N.

Per coloro che desiderano supportare tutti i livelli api e osservarli nell'interfaccia utente, controllare il codice seguente.

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

osservare nell'interfaccia utente (attività / frammento):

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

a proposito, non è necessario definire IntentFilteresplicitamente. In questo modo:var intentFilter = IntentFilter(CONNECTIVITY_ACTION)
Ryan Amaral

grazie per il tuo suggerimento. Non volevo creare oggetti ogni volta in onActive.
Sayem

Voglio dire che le 2 variabili / proprietà globali ( intentFiltere connectivityManager) non è necessario definire esplicitamente il loro tipo ( IntentFiltere ConnectivityManagerrispettivamente).
Ryan Amaral

7

Ho riscontrato lo stesso problema qualche giorno fa e ho deciso di utilizzare questa libreria Android-Job

Questa libreria utilizza JobSchedular, GcmNetworkManagereBroadcastReceiver a seconda della versione di Android l'applicazione è in esecuzione.

Iniziare un lavoro è abbastanza facile

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

1
Ho provato lo stesso schedler e ottenendo un'eccezione come questa Stai cercando di costruire un lavoro senza vincoli, questo non è consentito. puoi aiutarci a risolvere questo problema?
Sanket Kachhela

Usare Android-Job per questo scopo non è davvero una buona soluzione. È pensato per eseguire le cose in un momento specificato, una volta o periodicamente. Ha lo scopo di fornire il supporto per la retrocompatibilità per allarmi e simili. Questo va contro l'intera idea del motivo per cui l'API è cambiata e leggendo: developer.android.com/training/monitoring-device-state/… Puoi rapidamente farti un'idea del perché.
pedronveloso

l'unico problema è che in Android N può essere programmato solo per un minimo di 15 minuti in futuro
Fire Crow

4

Ho scritto un'implementazione di Kotlin che si basa sulla risposta di Sayam ma senza LiveData. Ho deciso di invocare l'ultimo metodo API (a questo punto nel tempo) ( ConnectivityManager#registerDefaultNetworkCallback) che ha come target 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)
        }
    }

}

Utilizzo:

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

o:

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

Non dimenticare di aggiungere l' ACCESS_NETWORK_STATEautorizzazione nel tuo AndroidManifest.xml :

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

Non vedo l'ora di leggere utili commenti e miglioramenti da parte tua.


1
Ho dovuto cambiare qualcosa per i callback per essere in grado di "toccare le visualizzazioni" in un'attività (contesto) sul thread principale: (context as AppCompatActivity).runOnUiThread(object: Runnable{ override fun run() { onConnectionAvailable.invoke() } })invece di onConnectionAvailable.invoke(). Lo stesso per onConnectionLost.invoke().
Андрей Воробьев

Sì, a seconda del tuo caso d'uso potresti dover cambiare thread. Non lo farei parte della classe, ma lascerei che il consumatore della classe se ne occupasse. Ma grazie per il suggerimento.
JJD


1

Sono d'accordo con la risposta suggerita da @rds.

Tieni presente che CONNECTIVITY_ACTION è obsoleto nel livello API 28.

Se hai il requisito che lo stato Wifi (connessione / disconnessione) debba essere rilevato nonostante l'app sia stata interrotta e desideri scegliere come target l'ultima versione, non hai molta scelta.

Devi usare connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

La domanda è che non puoi usare BroadcastReceiver, quindi come?

Puoi utilizzare JobScheduler o meglio se WorkManager (Periodic Request). Perché periodico perché se si tratta di una OneTimeRequest, sarà in grado di funzionare solo una volta e continuare ad ascoltare mentre la tua app è in primo piano.

La documentazione dice:

I callback continueranno a essere chiamati fino alla chiusura dell'applicazione o fino alla chiamata del collegamento #unregisterNetworkCallback (NetworkCallback)}.

Una volta che l'app viene terminata o rimossa dall'elenco delle app recenti, networkCallback non sarà in grado di ascoltare.

Quindi, hai bisogno di tali lavori periodici per far ascoltare l'app continuamente. Quanto dovrebbe essere la durata? Dipende da te e dipende da caso a caso.

So che è un modo un po 'brutto, ma è così. Una sfida potrebbe essere che se il dispositivo dell'utente è in modalità Doze o l'app è in stato di standby, il tuo lavoro potrebbe subire ritardi.


Tieni inoltre presente che su alcune EMUI fortemente personalizzate, il workManager del sistema operativo Android MIUI (attività periodiche) non deve funzionare sempre correttamente.
Kebab Krabby

1

Quando registriamo una richiamata di rete utilizzando il registerNetworkCallbackmetodo, a volte non si attiva e talvolta si attiva un falso positivo:

  1. Se avviamo un'app con connessione Internet, il onAvailablemetodo si attiva.
  2. Ma se non c'è connessione Internet sul dispositivo quando avviamo un'app, non NetworkCallbackviene chiamato nulla (è molto strano a causa di p. 1)
  3. Se abbiamo una connessione wifi ma senza il onAvailablemetodo di connessione a Internet , si attiva. E penso che sia un comportamento falso positivo perché ci aspettiamo che la connessione Internet osservi.

Come puoi vedere nel codice di seguito, la connessione Internet predefinita è disponibile e si attiva solo se cambia. Nessun trigger di falsi positivi.

Riassumi questo e questo risponde (ma solo per 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)
    }
}

Come fornire le dipendenze

@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 come usare:

@Inject
lateinit var connectionManager: ConnectionManager

//....

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

1

Basato sulla risposta di @ 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 usandolo quasi come nella risposta originale (se osservato da un'attività, per esempio):

ConnectivityWatcher(this).observe(this, Observer {
    Log.i("*-*-*", "is internet available? - ${if (it) "Yes" else "No"}")
})
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.