From 77fbedc350480f47cd55a84fffacbd6777aba736 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Tue, 6 Oct 2026 18:55:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(keepalive):=20=E6=9D=83=E9=99=90=E3=80=8C?= =?UTF-8?q?=E5=8E=BB=E7=B3=BB=E7=BB=9F=E8=AE=BE=E7=BD=AE=E9=87=8C=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E5=90=8E=E5=9B=9E=E6=9D=A5=E3=80=8D=E8=BF=99=E4=B8=80?= =?UTF-8?q?=E6=A1=A3=E7=8E=B0=E5=9C=A8=E7=9C=9F=E8=83=BD=E6=8A=8A=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E8=B5=B7=E8=B5=B7=E6=9D=A5=EF=BC=88=E8=A1=A5=20W53S?= =?UTF-8?q?=20=E7=99=BB=E8=AE=B0=E7=9A=84=E9=82=A3=E4=B8=80=E6=A0=BC?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题(W53S 标出、我核过代码确认):CyreneApp 里那个起停前台服务的 LaunchedEffect 的 key 只有 `keepAliveEnabled` 与登录态,而通知权限是**现读**的 ⇒ 用户在系统设置里授权后回到 App 时它不会重跑, 设置页已经显示「已开」而服务其实没起(要等下次启动或再动一次开关)。 修法:给 AppPresence 补一条**与既有 `appInForeground` 同源**的 StateFlow(`onStart/onStop` 里 从同一个集合重算,布尔 getter 语义一字未动),让那个 effect 收它当 key —— 「回到前台」正是 「从系统设置返回」必经、且必然变化的那一位。没有新增任何依赖。 --- .../kotlin/top/yeij/cyrene/app/CyreneApp.kt | 5 ++++- .../top/yeij/cyrene/core/notify/AppPresence.kt | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/top/yeij/cyrene/app/CyreneApp.kt b/app/src/main/kotlin/top/yeij/cyrene/app/CyreneApp.kt index 01508ee..5e48f71 100644 --- a/app/src/main/kotlin/top/yeij/cyrene/app/CyreneApp.kt +++ b/app/src/main/kotlin/top/yeij/cyrene/app/CyreneApp.kt @@ -40,7 +40,10 @@ fun CyreneApp(container: AppContainer) { * 而授权变化只有两条路——本端刚申请完(设置页那次写入会连带把这里重跑一遍)、或用户在系统 * 设置里改过(下次启动必然重跑)。 */ - LaunchedEffect(prefs.keepAliveEnabled, state) { + // ⚠️ key 里必须有「回到前台」这一位(W53S 登记的那一格):通知权限是**现读**的,而用户在系统 + // 设置里授权后回到 App 时,只有这个状态会变 —— 少了它,UI 会说「已开」而服务其实没起。 + val appInForeground by container.appPresence.appInForegroundFlow.collectAsState() + LaunchedEffect(prefs.keepAliveEnabled, state, appInForeground) { when ( keepAliveAction( enabled = prefs.keepAliveEnabled, diff --git a/app/src/main/kotlin/top/yeij/cyrene/core/notify/AppPresence.kt b/app/src/main/kotlin/top/yeij/cyrene/core/notify/AppPresence.kt index 6e18050..1964dc4 100644 --- a/app/src/main/kotlin/top/yeij/cyrene/core/notify/AppPresence.kt +++ b/app/src/main/kotlin/top/yeij/cyrene/core/notify/AppPresence.kt @@ -2,6 +2,9 @@ package top.yeij.cyrene.core.notify import java.util.Collections import java.util.concurrent.ConcurrentHashMap +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow /** * 进程级的两个「我此刻在哪儿」的事实(W53N):**App 在不在前台**、**聊天页正显示哪个会话**。 @@ -44,9 +47,23 @@ class AppPresence { val appInForeground: Boolean get() = startedActivities.isNotEmpty() + private val _appInForeground = MutableStateFlow(false) + + /** + * [appInForeground] 的**可订阅**形态(W53S 登记的那一格要它)。 + * + * 为什么需要第二条出口:`CyreneApp` 那个「起 / 停前台服务」的 `LaunchedEffect` 的 key 里没有 + * 通知权限,于是**用户在系统设置里授权后回到 App** 时它不会重跑 —— UI 已经显示「已开」, + * 服务却没起。权限本身必须**现读**(不该缓存成状态),能当 key 的是「刚好会在这件事发生时变化」 + * 的信号:**回到前台**(从系统设置返回必经 `onStart`)。所以这里把「在不在前台」补成一条流。 + * 判据本身仍然只读 [appInForeground] 那个布尔——两条出口算的是**同一份事实**(同一个集合)。 + */ + val appInForegroundFlow: StateFlow = _appInForeground.asStateFlow() + /** 一个 Activity 进入「可见」这一档(`onStart`)。 */ fun onActivityStarted(owner: Any) { startedActivities.add(owner) + _appInForeground.value = appInForeground } /** @@ -57,6 +74,7 @@ class AppPresence { */ fun onActivityStopped(owner: Any) { startedActivities.remove(owner) + _appInForeground.value = appInForeground } /** 聊天页刚显示(或刚换成)[sessionId];`null` = 它离开了屏幕。 */