ConnectivityManager.CONNECTIVITY_ACTION jest przestarzały

95

W systemie Android N na oficjalnej stronie internetowej jest napisane, że „Aplikacje przeznaczone dla systemu Android N nie odbierają transmisji CONNECTIVITY_ACTION”. Wspomina się również, że JobSchedulermoże być używany jako alternatywa. Ale JobSchedulernie zapewnia dokładnie tego samego zachowania, co CONNECTIVITY_ACTIONbroadcast.

W mojej aplikacji na Androida korzystałem z tej transmisji, aby poznać stan sieci urządzenia. Chciałem wiedzieć, czy ten stan był, CONNECTINGczy CONNECTEDz pomocą CONNECTIVITY_ACTIONtransmisji i najlepiej pasował do moich wymagań.

Teraz, gdy jest przestarzały, czy ktoś może zaproponować mi alternatywne podejście do uzyskania aktualnego stanu sieci?

Raghuram db
źródło
10
A co, jeśli kiedyś OP będzie chciał jakiegoś zachowania, które będzie wymagało zmiany targetSdkVersionna N lub później?
Michael,
1
Cóż, ja też wiem, że jeśli nie skieruję mojej aplikacji na Androida, NI otrzymam transmisję. Ale moja aplikacja musi obsługiwać Androida N. Jak mogę uzyskać takie samo zachowanie rozgłaszania w systemie Android N? Czy jest jakieś inne podejście, które mogę wypróbować? @DavidWasser
Raghuram db
Czasami myślę, że bardziej sensowne jest martwienie się o przyszłość w przyszłości. Jest to czysto pragmatyczne podejście do programowania. Oczywiście zawsze możesz spróbować upewnić się, że Twój kod nie używa żadnych przestarzałych funkcji. Z drugiej strony przestarzałe funkcje zwykle pozostają dostępne przez długi czas i może się zdarzyć, że Twoja aplikacja zostanie wycofana, zanim wycofane funkcje znikną. Android N jest tak nowy, że nie spędzałbym dużo czasu na zamartwianiu się nim. Jeszcze. Tylko moje 2 centy. Zwróć uwagę, że napisałem komentarz do pytania i nie sugerowałem, że „nie rób tego” było prawidłową odpowiedzią.
David Wasser,
2
@Raghuramdb Twoja aplikacja może działać na Androidzie N, nawet jeśli nie kierujesz swojej aplikacji na Androida N. Musisz tylko kierować reklamy na Androida N, jeśli chcesz korzystać z funkcji, które są dostępne tylko w Androidzie N.
David Wasser
2
Nadal można używać BroadcastReceiverz android.net.conn.CONNECTIVITY_CHANGEzamiarem filtra nawet podczas kierowania API29, wystarczy zarejestrować się Application.OnCreate. Po zamknięciu aplikacji po prostu nie będziesz otrzymywać żadnych aktualizacji.
Pierre,

Odpowiedzi:

102

To, co zostanie wycofane, to możliwość odbierania zmian stanu połączenia sieciowego przez aplikację działającą w tle.

Jak powiedział David Wasser , nadal możesz otrzymywać powiadomienia o zmianach w łączności, jeśli składnik aplikacji zostanie utworzony (nie zostanie zniszczony) i jeśli zarejestrowałeś swój odbiornik programowo z jego kontekstem, zamiast robić to w manifeście.

Możesz też zamiast tego użyć NetworkCallback . W szczególności konieczne będzie zastąpienie opcji onAvailable w przypadku zmian stanu połączenia.

Pozwól mi szybko napisać fragment kodu:

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
źródło
2
Ponieważ ta technika będzie działać tylko wtedy, gdy aplikacja działa na pierwszym planie. Czy to oznacza, że ​​nie mamy już możliwości nasłuchiwania zdarzenia połączenia, gdy aplikacja nie działa na pierwszym planie? Umieszczenie <action android: name = "android.net.conn.CONNECTIVITY_CHANGE" /> w
pliku
2
@CheokYanCheng AFAIK, to prawda. Aby nasłuchiwać zdarzeń związanych z łącznością, musisz mieć proces działający na pierwszym planie. Wygląda na to, że założenie, które przyjęli inżynierowie platformy Android, było takie, że nasłuchiwanie zdarzeń związanych z łącznością odbywało się głównie w celu ustalenia, kiedy rozpocząć synchronizację danych między klientem a serwerem. Dlatego JobScheduler jest zalecanym sposobem dla tego przypadku użycia.
Amokrane Chentir
30
lol co do cholery, kolejne 10 aktualizacji Androida i wszystko, co będziemy w stanie napisać, to aplikacja Hello World
DennisVA
1
Czy muszę wyrejestrować NetworkCallback (na przykład w metodzie onDestroy działania)?
Ruslan Berozov
2
@Ruslan tak, oczywiście, albo ujawnisz wszystko, co jest zarejestrowane
DennisVA
35

Zaktualizuję Sayem'sodpowiedź na problemy z naprawianiem kłaczków, które mi pokazują.

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

I to samo użycie:

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

Przy okazji dzięki sayem za twoje rozwiązanie.

Kebab Krabby
źródło
2
Niesamowite rozwiązanie!
pudełko
2
Bardzo dobre rozwiązanie do korzystania z danych na żywo i obsługi starszej wersji
Prakash Shukla
To najlepsze rozwiązanie dostępne w internecie.
Karan Sharma
Bardzo dobre rozwiązanie! ALE jest jedno „nie” - jest to niewłaściwy sposób użycia metody onAvailable (sieć: Sieć?), Ponieważ wywołuje ona nawet brak dostępu do Internetu. Lepiej jest użyć onCapabilitiesChanged (network: Network, networkCapabilities: NetworkCapabilities) i sprawdzić networkCapabilities.hasCapability (NET_CAPABILITY_INTERNET) i networkCapabilities.hasCapability (NET_CAPABILITY_VALIDATED).
DmitryKanunnikoff
jak uzyskać adres IP i typ sieci w ramach tej bazy kodu?
A_rmas
29

Dokumentacja dla systemu Android N stwierdza:

Aplikacje na Androida N nie odbierają transmisji CONNECTIVITY_ACTION, nawet jeśli mają wpisy manifestu żądające powiadomienia o tych zdarzeniach. Aplikacje działające na pierwszym planie mogą nadal nasłuchiwać CONNECTIVITY_CHANGE w swoim głównym wątku, jeśli zażądają powiadomienia za pomocą BroadcastReceiver.

Oznacza to, że nadal możesz zarejestrować, BroadcastReceiverjeśli Twoja aplikacja działa na pierwszym planie, aby wykrywać zmiany w łączności sieciowej.

David Wasser
źródło
Niezły subtelny chwyt :)
Amokrane Chentir
czy to oznacza, że ​​aplikacja przestanie odbierać transmisje, gdy nie będzie na pierwszym planie? (więc nie mogę tego słuchać na przykład w serwisie?)
sundie
1
Nie wiem na pewno, musiałbym to przetestować, żeby mieć pewność. Jednak po przeczytaniu dokumentacji wydaje się, że jeśli Twoja aplikacja nie jest na pierwszym planie, nie otrzymasz transmisji Intent.
David Wasser,
2
Jednak wykrycie zmiany łączności w tle jest obowiązkowe dla wszystkich aplikacji SIP (VoIP) ... te aplikacje zwykle działają w tle przez kilka dni i przechodzą na pierwszy plan tylko wtedy, gdy nadejdzie połączenie (podobnie jak dialer telefonu). . Te aplikacje muszą automatycznie łączyć się ponownie w tle. To zabija wszystkie te aplikacje (które nie mają własnego serwera push) z platformy Android, ponieważ będą one offline. zawsze.
Grisgram
po prostu użyj usługi push w Firebase.
Pierre,
21

Proszę sprawdzić pierwszą odpowiedź @Amokrane Chentir, aby uzyskać wsparcie dla systemu Android N.

Dla tych, którzy chcą obsługiwać wszystkie poziomy API i obserwować to w interfejsie użytkownika, sprawdź poniższy kod.

LiveData połączenia sieciowego:

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

obserwuj w interfejsie użytkownika (aktywność / fragment):

val connectionLiveData = ConnectionLiveData(context)
    connectionLiveData.observe(this, Observer { 
       // do whatever you want with network connectivity change 
})
Sayem
źródło
przy okazji, nie musisz IntentFilterjawnie definiować . Na przykład:var intentFilter = IntentFilter(CONNECTIVITY_ACTION)
Ryan Amaral
dzięki za Twoją sugestię. Nie chciałem za każdym razem tworzyć obiektu w onActive.
Sayem
Chodzi mi o to, że 2 zmienne / właściwości globalne ( intentFilteri connectivityManager) nie musisz jawnie definiować ich typu ( IntentFilteri ConnectivityManagerodpowiednio).
Ryan Amaral
Większość materiałów jest (niestety) przestarzała ...
Andrew,
7

Kilka dni temu napotkałem ten sam problem i zdecydowałem się skorzystać z tej biblioteki Android-Job

Ta biblioteka zastosowania JobSchedular, GcmNetworkManageroraz BroadcastReceiverw zależności od wersji Android aplikacja jest uruchomiona.

Rozpoczęcie pracy jest dość łatwe

new JobRequest.Builder(DemoSyncJob.TAG)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(false)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED) // this is what gets the job done
            .build()
            .schedule();
Noman Rafique
źródło
1
Próbowałem tego samego harmonogramu i otrzymałem taki wyjątek Próbujesz zbudować pracę bez ograniczeń, to jest niedozwolone. czy możesz nam pomóc rozwiązać ten problem?
Sanket Kachhela
Korzystanie z Android-Job w tym celu nie jest naprawdę dobrym rozwiązaniem. Ma na celu uruchamianie rzeczy w określonym czasie, jednorazowo lub okresowo. Ma zapewnić obsługę wstecznej kompatybilności dla Alarmów i tym podobnych. Jest to sprzeczne z całą ideą, dlaczego zmienił się interfejs API, i czytając: developer.android.com/training/monitoring-device-state/ ... Możesz szybko zrozumieć, dlaczego.
pedronveloso
Jedynym problemem jest to, że w Androidzie N można to zaplanować tylko na co najmniej 15 minut w przyszłości
Fire Crow
4

Napisałem implementację Kotlin, która jest oparta na odpowiedzi Sayama, ale bez LiveData. Zdecydowałem się wywołać (w tym momencie) najnowszą metodę API ( ConnectivityManager#registerDefaultNetworkCallback), która jest przeznaczona dla systemu 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)
        }
    }

}

Stosowanie:

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

lub:

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

Nie zapomnij dodać ACCESS_NETWORK_STATEuprawnienia w pliku AndroidManifest.xml :

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

Z niecierpliwością czekam na pomocne komentarze i ulepszenia od Ciebie.

JJD
źródło
1
Musiałem coś zmienić dla wywołań zwrotnych, aby móc „dotykać widoków” w działaniu (kontekście) w głównym wątku: (context as AppCompatActivity).runOnUiThread(object: Runnable{ override fun run() { onConnectionAvailable.invoke() } })zamiast onConnectionAvailable.invoke(). To samo dotyczy onConnectionLost.invoke().
Андрей Воробьев
Tak, w zależności od przypadku użycia może zajść potrzeba przełączenia wątków. Nie uczyniłbym tego częścią klasy, ale zamiast tego pozwoliłbym zająć się tym konsumentowi klasy. Ale dziękuję za podpowiedź.
JJD
4

Na podstawie odpowiedzi @ 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)
        }
    }
}

I używając go prawie tak samo, jak w oryginalnej odpowiedzi (na przykład, jeśli obserwujesz z działania):

ConnectivityWatcher(this).observe(this, Observer {
    Log.i("*-*-*", "is internet available? - ${if (it) "Yes" else "No"}")
})
DmitryKanunnikoff
źródło
2

Aplikacje przeznaczone dla systemu Android N (Nougat) nie odbierają CONNECTIVITY_ACTIONtransmisji zdefiniowanych w manifeście (zobacz Svelte ).

Możliwe rozwiązania:

Zobacz także Android O - Wykryj zmianę łączności w tle

rds
źródło
1

Zgadzam się z odpowiedzią sugerowaną przez @rds.

Pamiętaj, że funkcja CONNECTIVITY_ACTION jest przestarzała na poziomie interfejsu API 28.

Jeśli masz wymaganie, aby stan Wi-Fi (połączenie / rozłączenie) był wykrywany pomimo zabicia aplikacji i chcesz wybrać najnowszą wersję, nie masz dużego wyboru.

Musisz użyć connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

Pytanie brzmi, że nie możesz używać BroadcastReceiver, więc jak wtedy?

Możesz użyć JobScheduler lub lepiej, jeśli WorkManager (okresowe żądanie). Dlaczego okresowo, ponieważ jeśli jest to OneTimeRequest, będzie można go uruchomić tylko raz i kontynuować nasłuchiwanie, gdy aplikacja jest na pierwszym planie.

Dokumentacja mówi:

Wywołania zwrotne będą wywoływane do momentu zamknięcia aplikacji lub wywołania linku #unregisterNetworkCallback (NetworkCallback)}.

Gdy aplikacja zostanie zabita lub usunięta z listy ostatnich aplikacji, funkcja networkCallback nie będzie mogła słuchać.

Potrzebujesz więc takich okresowych zadań, aby aplikacja stale nasłuchiwała. Ile powinien być czas trwania? To zależy od Ciebie i zależy od przypadku.

Wiem, że to trochę brzydkie, ale tak jest. Jednym z wyzwań może być to, że jeśli urządzenie użytkownika jest w trybie drzemki lub aplikacja jest w stanie gotowości, Twoje zadanie może być opóźnione.

Wahib Ul Haq
źródło
Należy również pamiętać, że w niektórych mocno dostosowanych EMUI menedżer pracy MIUI Android OS (zadania okresowe) nie musi zawsze działać poprawnie.
Kebab Krabby
1

Kiedy rejestrujemy wywołanie zwrotne w sieci przy użyciu tej registerNetworkCallbackmetody, czasami nie jest ono wyzwalane, a czasami wywołuje fałszywie dodatnie:

  1. Jeśli uruchomimy aplikację z połączeniem internetowym, onAvailablemetoda zostanie uruchomiona .
  2. Ale jeśli na urządzeniu nie ma połączenia z Internetem, gdy uruchamiamy aplikację, nic z tego nie NetworkCallbackjest wywoływane (jest to bardzo dziwne z powodu str.1)
  3. Jeśli mamy połączenie Wi-Fi, ale bez onAvailablewyzwalaczy metody połączenia z Internetem . Myślę, że jest to fałszywie pozytywne zachowanie, ponieważ oczekujemy obserwacji połączenia internetowego.

Jak widać w poniższym kodzie, domyślnie połączenie internetowe jest dostępne i uruchamia się tylko wtedy, gdy się zmieni. Brak wyzwalaczy fałszywie dodatnich.

Po prostu podsumuj to i te odpowiedzi (ale tylko dla 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)
    }
}

Jak zapewnić zależności

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

I jak używać:

@Inject
lateinit var connectionManager: ConnectionManager

//....

viewLifecycleOwner.observe(connectionManager) { isInternetAvailable ->
    // TODO 
}
bitvale
źródło