android: мониторинг смены сети — NetworkChangeMonitor, перезапуск ядра при смене интерфейса

This commit is contained in:
evgeny committed 2026-08-17 21:03:05 +03:00
1 parent f3b33bcf74
commit 7dee49ce63
4 files changed
+153

No files matched your search

@@ -0,0 +1,101 @@
package com.utun.chat.data
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
import android.os.Handler
import android.os.Looper
import com.utun.chat.ChatApplication
/**
* Слушатель смены сети (ConnectivityManager.NetworkCallback).
*
* На Android нативный netlink-мониторинг сокетов недоступен (Permission denied),
* поэтому о смене сети сообщает ConnectivityManager. При смене default-сети
* (mobile↔wifi, смена IP) пересобираем конфиг сокетов и перезапускаем C-ядро,
* чтобы оно забиндилось на актуальный интерфейс.
*
* Регистрируется в HeadlessService (foreground) — работает в фоне.
*/
object NetworkChangeMonitor {
private var started = false
/* Пропуск первого onAvailable: он приходит сразу при регистрации с текущим
* состоянием сети, а не как реальное событие смены. */
private var primed = false
/* Рестарт в процессе — защита от наложения (C-ядро тоже имеет CAS-гарды). */
private var restarting = false
/* Последний известный набор интерфейсов (ifName=ip) для лога «было → стало». */
private var lastInterfaces: List<Pair<String, String>>? = null
private const val DEBOUNCE_MS = 500L
private val handler = Handler(Looper.getMainLooper())
private val restartRunnable = Runnable { doRestart() }
private val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
if (!primed) { primed = true; return }
schedule()
}
override fun onLost(network: Network) {
if (!primed) return
schedule()
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
if (!primed) return
schedule()
}
}
private fun schedule() {
handler.removeCallbacks(restartRunnable)
handler.postDelayed(restartRunnable, DEBOUNCE_MS)
}
private fun doRestart() {
if (restarting || !NativeLib.isReady()) return
restarting = true
Thread {
try {
val provider = ChatApplication.instance.configProvider
val now = provider.getAvailableInterfaces()
val prev = lastInterfaces
lastInterfaces = now
val prevStr = prev?.joinToString(", ") { (n, ip) -> "$n=$ip" } ?: "n/a"
val nowStr = now.joinToString(", ") { (n, ip) -> "$n=$ip" }
LogManager.addLog("INFO", "NET", "network change: [$prevStr] -> [$nowStr]")
NativeLib.notifyNetworkActive()
val path = provider.getString("db.path") ?: ""
val port = provider.getInt("control.port")
NativeLib.restart(path, port, provider.buildConfigText())
} finally {
restarting = false
}
}.start()
}
fun start() {
if (started) return
val ctx = ChatApplication.instance.applicationContext
val cm = ctx.getSystemService(ConnectivityManager::class.java) ?: return
/* Если сети ещё нет — первый onAvailable будет реальным событием. */
primed = cm.activeNetwork == null
cm.registerDefaultNetworkCallback(callback)
started = true
LogManager.addLog("INFO", "NET", "network change listener started")
}
fun stop() {
if (!started) return
val ctx = ChatApplication.instance.applicationContext
val cm = ctx.getSystemService(ConnectivityManager::class.java) ?: return
cm.unregisterNetworkCallback(callback)
handler.removeCallbacks(restartRunnable)
started = false
LogManager.addLog("INFO", "NET", "network change listener stopped")
}
}
@@ -13,6 +13,7 @@ import com.utun.chat.MainActivity
import com.utun.chat.data.LogManager
import com.utun.chat.data.NativeLib
import com.utun.chat.data.NetworkActivityMonitor
import com.utun.chat.data.NetworkChangeMonitor
class HeadlessService : Service() {
override fun onCreate() {
@@ -47,6 +48,7 @@ class HeadlessService : Service() {
if (ok) {
LogManager.addLog("INFO", "SRV", "uTun core started")
NetworkActivityMonitor.start()
NetworkChangeMonitor.start()
} else {
LogManager.addLog("ERROR", "SRV", "uTun start failed")
}
@@ -54,6 +56,7 @@ class HeadlessService : Service() {
override fun onDestroy() {
LogManager.addLog("INFO", "SRV", "uTun service stopping")
NetworkChangeMonitor.stop()
NetworkActivityMonitor.stop()
NativeLib.stop()
super.onDestroy()