4 changed files with 153 additions and 0 deletions
@ -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") |
||||
} |
||||
} |
||||
Loading…
Reference in new issue