У меня было почти готовое решение для этой проблемы, кроме извлечения IPv4-адреса, поэтому я отправлю его здесь, чтобы вы могли его использовать.
В основном, решение состоит из двух основных компонентов: «службы», которая отслеживает сетевые изменения, и тема RX , на которую вы подписываетесь и публикуете обновления об изменениях в сети.
Шаг 0: Подготовка
Убедитесь, что ваш файл AndroidManifest.xml
имеет следующие разрешения:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
В вашем приложении должны быть включены параметры совместимости, чтобы разрешить использование функций Java 8. Добавьте следующие строки в свой build.gradle
файл:
android {
...
compileOptions {
targetCompatibility = "8"
sourceCompatibility = "8"
}
}
Чтобы использовать RX Kotlin, добавьте следующие зависимости:
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.0'
Шаг 1: Реализуйте прослушиватель сетевых изменений service
Импорт опущен, чтобы код был как можно более лаконичным. NetworkReachabilityService
- это не обычная служба Android, которую вы можете запустить, и она будет работать, даже когда приложение будет убито. Это класс, который устанавливает прослушиватель на ConnectivityManager
и обрабатывает все обновления, связанные с состоянием сети.
Любой тип обновления обрабатывается аналогично: что-то изменено -> сообщение NetworkState
объект с соответствующим значением. При каждом изменении мы можем запросить IPv4 для отображения в пользовательском интерфейсе (см. Шаг 3).
sealed class NetworkState {
data class Available(val type: NetworkType) : NetworkState()
object Unavailable : NetworkState()
object Connecting : NetworkState()
object Losing : NetworkState()
object Lost : NetworkState()
}
sealed class NetworkType {
object WiFi : NetworkType()
object CELL : NetworkType()
object OTHER : NetworkType()
}
class NetworkReachabilityService private constructor(context: Application) {
private val connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
// There are more functions to override!
override fun onLost(network: Network) {
super.onLost(network)
postUpdate(NetworkState.Lost)
}
override fun onUnavailable() {
super.onUnavailable()
postUpdate(NetworkState.Unavailable)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
super.onLosing(network, maxMsToLive)
postUpdate(NetworkState.Losing)
}
override fun onAvailable(network: Network) {
super.onAvailable(network)
updateAvailability(connectivityManager.getNetworkCapabilities(network))
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
updateAvailability(networkCapabilities)
}
}
companion object {
// Subscribe to this subject to get updates on network changes
val NETWORK_REACHABILITY: BehaviorSubject<NetworkState> =
BehaviorSubject.createDefault(NetworkState.Unavailable)
private var INSTANCE: NetworkReachabilityService? = null
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
fun getService(context: Application): NetworkReachabilityService {
if (INSTANCE == null) {
INSTANCE = NetworkReachabilityService(context)
}
return INSTANCE!!
}
}
private fun updateAvailability(networkCapabilities: NetworkCapabilities?) {
if (networkCapabilities == null) {
postUpdate(NetworkState.Unavailable)
return
}
var networkType: NetworkType = NetworkType.OTHER
if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
networkType = NetworkType.CELL
}
if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
networkType = NetworkType.WiFi
}
postUpdate(NetworkState.Available(networkType))
}
private fun postUpdate(networkState: NetworkState) {
NETWORK_REACHABILITY.onNext(networkState)
}
fun pauseListeningNetworkChanges() {
try {
connectivityManager.unregisterNetworkCallback(networkCallback)
} catch (e: IllegalArgumentException) {
// Usually happens only once if: "NetworkCallback was not registered"
}
}
fun resumeListeningNetworkChanges() {
pauseListeningNetworkChanges()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
connectivityManager.registerDefaultNetworkCallback(networkCallback)
} else {
connectivityManager.registerNetworkCallback(
NetworkRequest.Builder().build(),
networkCallback
)
}
}
}
Шаг 2: Реализуйте метод извлечения IPv4 (бонусный IPv6)
У меня было чтобы немного изменить извлечение IPv4, так как он не возвращал никаких IPv4-адресов, в то время как устройство явно имело один. Это два метода извлечения адресов IPv4 и IPv6 соответственно. Методы были изменены с использованием этого SO-ответа о том, как извлекать IP-адреса. В целом, это на 90% то же сопоставление inetAddresses
со значениями IP-адресов.
Добавьте эти два метода в NetworkReachabilityService
class:
fun getIpv4HostAddress(): String? =
NetworkInterface.getNetworkInterfaces()?.toList()?.mapNotNull { networkInterface ->
networkInterface.inetAddresses?.toList()
?.filter { !it.isLoopbackAddress && it.hostAddress.indexOf(':') < 0 }
?.mapNotNull { if (it.hostAddress.isNullOrBlank()) null else it.hostAddress }
?.firstOrNull { it.isNotEmpty() }
}?.firstOrNull()
fun getIpv6HostAddress(): String? =
NetworkInterface.getNetworkInterfaces()?.toList()?.mapNotNull { networkInterface ->
networkInterface.inetAddresses?.toList()
?.filter { !it.isLoopbackAddress && it is Inet6Address }
?.mapNotNull { if (it.hostAddress.isNullOrBlank()) null else it.hostAddress }
?.firstOrNull { it.isNotEmpty() }
}?.firstOrNull()
Шаг 3: Обновите пользовательский интерфейс
Простое решение, связанное с пользовательским интерфейсом, представляет собой прямую подписку на тему NETWORK_REACHABILITY
, и при каждом изменении, полученном через эту тему, мы извлекаем данные IPv4 из NetworkReachabilityService
и отображаем их в пользовательском интерфейсе. Вы хотите рассмотреть два основных метода: subscribeToUpdates
и updateIPv4Address
. И не забудьте отказаться от подписки, используя unsubscribeFromUpdates
, чтобы предотвратить утечку памяти.
class MainActivity : AppCompatActivity() {
private val compositeDisposable = CompositeDisposable()
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.text_view)
val service = NetworkReachabilityService.getService(application)
service.resumeListeningNetworkChanges()
subscribeToUpdates()
}
override fun onDestroy() {
super.onDestroy()
unsubscribeFromUpdates()
}
private fun unsubscribeFromUpdates() {
compositeDisposable.dispose()
compositeDisposable.clear()
}
private fun subscribeToUpdates() {
val disposableSubscription =
NetworkReachabilityService.NETWORK_REACHABILITY
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ networkState ->
// We do not care about networkState right now
updateIPv4Address()
}, {
// Handle the error
it.printStackTrace()
})
compositeDisposable.addAll(disposableSubscription)
}
private fun updateIPv4Address() {
val service = NetworkReachabilityService.getService(application)
textView.text = service.getIpv4HostAddress()
}
}
Резюме
Используя экземпляр ConnectivityManager
, мы устанавливаем слушателя, который реагирует на любое изменение сети. Каждое изменение запускает обновление, которое отправляет значение субъекту RX, содержащему последнее состояние сети. Подписавшись на эту тему, мы можем отслеживать изменения состояния сети и предполагать, что у устройства был изменен адрес, и, таким образом, мы обновили sh значение IPv4, отображаемое в TextView
.
Я решил, что этот код подходит для go на GitHub, поэтому здесь ссылка на проект .