diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 20a3812..b259abb 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -17,6 +17,12 @@
+
+
+
+
+
+
= 34) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
} else {
0
}
- ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, serviceType)
+ ServiceCompat.startForeground(this, NOTIFICATION_ID, buildNotification(serverName), serviceType)
- android.util.Log.i(TAG, "Foreground service started")
- // The service exists only to keep the process alive while a TS connection is active.
- // If Android kills it, there is no reliable way to restore the TS session, so don't
- // ask the system to recreate it with a misleading "connected" notification.
+ android.util.Log.i(TAG, "Foreground service started for $serverName")
return Service.START_NOT_STICKY
}
override fun onDestroy() {
+ releaseLocks()
super.onDestroy()
- android.util.Log.i(TAG, "Connection service destroyed")
+ android.util.Log.i(TAG, "Connection service destroyed, locks released")
}
- /**
- * 确保通知频道已创建。
- * Android 8.0+ 需要通知频道,重复创建是安全的(系统会忽略已存在的频道)。
- */
+ // ── WakeLock ───────────────────────────────────────────────
+
+ @Suppress("DEPRECATION")
+ private fun acquireWakeLock() {
+ val pm = getSystemService(POWER_SERVICE) as PowerManager
+ wakeLock = pm.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK,
+ WAKELOCK_TAG,
+ ).apply {
+ acquire()
+ }
+ android.util.Log.i(TAG, "WakeLock acquired (PARTIAL)")
+ }
+
+ private fun releaseLocks() {
+ wakeLock?.let {
+ if (it.isHeld) {
+ it.release()
+ android.util.Log.i(TAG, "WakeLock released")
+ }
+ }
+ wakeLock = null
+
+ wifiLock?.let {
+ if (it.isHeld) {
+ it.release()
+ android.util.Log.i(TAG, "WiFi lock released")
+ }
+ }
+ wifiLock = null
+ }
+
+ // ── WiFi Lock ──────────────────────────────────────────────
+
+ private fun acquireWifiLock() {
+ val wm = applicationContext.getSystemService(WIFI_SERVICE) as? WifiManager ?: return
+ wifiLock = wm.createWifiLock(
+ WifiManager.WIFI_MODE_FULL_HIGH_PERF,
+ "tsmobile:wifi",
+ ).apply {
+ acquire()
+ }
+ android.util.Log.i(TAG, "WiFi lock acquired")
+ }
+
+ // ── 通知频道 ───────────────────────────────────────────────
+
private fun ensureChannel() {
val nm = getSystemService(NotificationManager::class.java) ?: return
if (nm.getNotificationChannel(CHANNEL_ID) != null) return
@@ -100,11 +180,7 @@ class ConnectionService : LifecycleService() {
nm.createNotificationChannel(channel)
}
- /**
- * 构建通知。
- */
private fun buildNotification(serverName: String): Notification {
- // 点击通知回到 MainActivity
val pendingIntent = PendingIntent.getActivity(
this,
0,
@@ -119,8 +195,8 @@ class ConnectionService : LifecycleService() {
.setContentTitle("已连接到 TeamSpeak")
.setContentText(serverName)
.setPriority(NotificationCompat.PRIORITY_LOW)
- .setOngoing(true) // 不可滑动清除
- .setShowWhen(false) // 不显示时间
+ .setOngoing(true)
+ .setShowWhen(false)
.setContentIntent(pendingIntent)
.build()
}
diff --git a/android/app/src/main/java/com/tsmobile/app/viewmodel/ServerViewModel.kt b/android/app/src/main/java/com/tsmobile/app/viewmodel/ServerViewModel.kt
index 5e49adb..6f940c3 100644
--- a/android/app/src/main/java/com/tsmobile/app/viewmodel/ServerViewModel.kt
+++ b/android/app/src/main/java/com/tsmobile/app/viewmodel/ServerViewModel.kt
@@ -168,17 +168,23 @@ class ServerViewModel(application: Application) : AndroidViewModel(application)
// 启动时自动检查更新
checkForUpdate()
- // 网络变动检测 — 简单原则:
- // 任何 NetworkCallback 触发 + 已连接 → 网络一定变了 → 必须重连
- // (UDP socket 绑定在旧 IP 上,即使 isAvailable 不变也已死)
+ // 网络变动检测:
+ // 任何 NetworkCallback 触发 + 前台 + 已连接 → 重连(UDP socket 绑定在旧 IP 上已死)
+ // 后台忽略:Android 挂起应用时可能限制网络导致误触发,
+ // 真正的断线由 Go idle timeout(60s 无包)兜底,或回到前台时检测
//
// 两层 collector:
- // 1. networkChangeCount → 任何网络事件 → Connected 就重连
- // 2. isAvailable false→true → WaitingForNetwork 时网络恢复 → 重连
+ // 1. networkChangeCount → 任何网络事件 → Connected 就重连(仅前台)
+ // 2. isAvailable false→true → WaitingForNetwork 时网络恢复 → 重连(不限前后台)
viewModelScope.launch {
var firstEmission = true
networkMonitor.networkChangeCount.collect { _ ->
if (firstEmission) { firstEmission = false; return@collect }
+ // 后台忽略:防止 Android 后台网络限制导致 NetworkCallback 误触发
+ if (!isAppInForeground()) {
+ android.util.Log.d(TAG, "Network change ignored (app in background)")
+ return@collect
+ }
if (_connectionState.value is ConnectionState.Connected) {
android.util.Log.w(TAG, "Network changed while connected, reconnecting")
val chId = Repository.currentChannelId.value
@@ -208,6 +214,10 @@ class ServerViewModel(application: Application) : AndroidViewModel(application)
}
}
+ /** 应用是否在前台(RESUMED 状态) */
+ private fun isAppInForeground(): Boolean =
+ ProcessLifecycleOwner.get().lifecycle.currentState == Lifecycle.State.RESUMED
+
override fun onCleared() {
super.onCleared()
networkMonitor.stop()