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()