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 JobScheduler
może być używany jako alternatywa. Ale JobScheduler
nie zapewnia dokładnie tego samego zachowania, co CONNECTIVITY_ACTION
broadcast.
W mojej aplikacji na Androida korzystałem z tej transmisji, aby poznać stan sieci urządzenia. Chciałem wiedzieć, czy ten stan był, CONNECTING
czy CONNECTED
z pomocą CONNECTIVITY_ACTION
transmisji 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?
targetSdkVersion
na N lub później?BroadcastReceiver
zandroid.net.conn.CONNECTIVITY_CHANGE
zamiarem filtra nawet podczas kierowania API29, wystarczy zarejestrować sięApplication.OnCreate
. Po zamknięciu aplikacji po prostu nie będziesz otrzymywać żadnych aktualizacji.Odpowiedzi:
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 } }
źródło
Zaktualizuję
Sayem's
odpowiedź 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.
źródło
Dokumentacja dla systemu Android N stwierdza:
Oznacza to, że nadal możesz zarejestrować,
BroadcastReceiver
jeśli Twoja aplikacja działa na pierwszym planie, aby wykrywać zmiany w łączności sieciowej.źródło
Intent
.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 })
źródło
IntentFilter
jawnie definiować . Na przykład:var intentFilter = IntentFilter(CONNECTIVITY_ACTION)
intentFilter
iconnectivityManager
) nie musisz jawnie definiować ich typu (IntentFilter
iConnectivityManager
odpowiednio).Kilka dni temu napotkałem ten sam problem i zdecydowałem się skorzystać z tej biblioteki Android-Job
Ta biblioteka zastosowania
JobSchedular
,GcmNetworkManager
orazBroadcastReceiver
w 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();
źródło
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_STATE
uprawnienia w pliku AndroidManifest.xml :<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Z niecierpliwością czekam na pomocne komentarze i ulepszenia od Ciebie.
źródło
(context as AppCompatActivity).runOnUiThread(object: Runnable{ override fun run() { onConnectionAvailable.invoke() } })
zamiastonConnectionAvailable.invoke()
. To samo dotyczyonConnectionLost.invoke()
.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"}") })
źródło
Aplikacje przeznaczone dla systemu Android N (Nougat) nie odbierają
CONNECTIVITY_ACTION
transmisji zdefiniowanych w manifeście (zobacz Svelte ).Możliwe rozwiązania:
ConnectivityManager.registernetworkCallback()
gdy aplikacja jest uruchomiona.JobScheduler
i określ sieć bez pomiaru za pośrednictwemsetRequiredNetworkType()
.Zobacz także Android O - Wykryj zmianę łączności w tle
źródło
Zgadzam się z odpowiedzią sugerowaną przez @rds.
Pamiętaj, że funkcja CONNECTIVITY_ACTION jest przestarzała na poziomie interfejsu API 28.
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:
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.
źródło
Kiedy rejestrujemy wywołanie zwrotne w sieci przy użyciu tej
registerNetworkCallback
metody, czasami nie jest ono wyzwalane, a czasami wywołuje fałszywie dodatnie:onAvailable
metoda zostanie uruchomiona .NetworkCallback
jest wywoływane (jest to bardzo dziwne z powodu str.1)onAvailable
wyzwalaczy 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 }
źródło