docs(05): 锚点补记三十七 —— 长跑第3次定案(30min 有效通过;2h 失败是模拟器 system_server 崩溃连带杀进程,五条证据)+ Android 14 划掉会终止 dataSync 前台服务(验收造场景改用返回键 finish Activity)+ W57 端到端验收通过(含未做改前对照的如实说明)

This commit is contained in:
AskaEth committed 2026-10-07 00:18:51 +08:00
1 parent f9555fba85
commit c8f19f9c84
1 file changed
+61
+61
View File
@@ -1960,3 +1960,64 @@ pidof top.yeij.cyrene # 期望:与前面同一个 PID(
**★ 顺带推翻了一条猜想**:锚点三十五 §7 猜「上次复查时进程不在」可能是**保活开关没开**。这次 App 一启动我就查了——**服务立刻就在跑**(开关是开的)。所以那条猜想**是错的**,上次进程消失是**别的**原因(可能是系统低内存强杀,也可能是被手动划掉——**仍未定**,别当结论)。
**当前转发**:`tcp:80 → tcp:18090`(假后端)、`tcp:10942 → tcp:10942`(真后端)。要回真后端改这一条即可。
## ★★ 锚点补记三十七 · 长跑第 3 次定案(模拟器崩,不是 App 的问题)+ W57 端到端验收通过(2026-10-07 00:30)
### 1. 长跑第 3 次结果:**30 分钟有效通过;2 小时那次失败是模拟器自己崩了**
原始输出:
```
=== 30min 抽查 @ 22:46:34 === === 2h 复查 @ 00:16:34 ===
pid: 31092 pid: (空)
服务: 3 服务: 0
前台: launcher(仍在后台) 前台: Window{4f748ad …launcher}
ws: 1 ws: 0
注入 delivered=0 / proactive 通知数 0
```
**30 分钟那一条是有效证据**:pid 与基线一致(31092)、服务 3 条、仍在后台、WS 还连着 ⇒ **半小时后台存活成立**。
**2 小时那一条的失败与 App 无关**,证据链(五条独立):
1. `logcat` 在**模拟器时间 16:02(≈宿主 00:08,正卡在两次抽查之间)**出现整段**开机序列**(`sys.boot_completed=1`、lmkd 启动、apexd…)。
2. 但 `/proc/uptime` = **10.19 小时,没变** ⇒ **不是整机重启,是 system_server(framework)崩溃后重启**。
3. launcher 的 window id 从 `2cbfcc8` → `4f748ad` ⇒ launcher 也重启过(对上)。
4. `dumpsys activity exit-info top.yeij.cyrene`(共 16 条)里**没有 pid 31092 的退出记录**,最新一条还是宿主 21:29 我装包那次(`reason=16 PACKAGE UPDATED`)⇒ 走的**不是**正常进程退出路径。
5. 通知栏里那条 `keep_alive` 通知**还残留着** ⇒ 进程非正常死亡(系统没来得及清)。
**结论**:模拟器 framework 崩 ⇒ 连带杀掉所有 App ⇒ 与客户端保活能力无关。**但也不能反过来当「保活能撑 2 小时」的证据**(那次没跑到)。模拟器这台机器(摘要里就记过「极不稳、launcher ANR 过、system_server 会崩」)**不适合做小时级长跑**,要拿这个结论得上真机。
### 2. ⚠️ 一条要紧的平台发现:Android 14 起「从最近任务划掉」会终止 `dataSync` 前台服务
原本 W57 的验收步骤是照「把卡片划掉」写的 —— 那**测不出 W57**:Android 14 改变了行为,用户划掉 App 时它的前台服务会被一并终止(我们的服务类型是 `dataSync`),进程随之死掉,于是「Activity 没了但进程还在」这个场景**根本造不出来**。
**改用正确造法**(本次采用,成功):
```
adb shell am start -n top.yeij.cyrene/.app.MainActivity # 确保 Activity 在前台
adb shell input keyevent 4 ×4 # 逐层返回直到 Activity finish
```
判据:**前台变成 launcher**(Activity 已 finish)+ **pid 不变** + **服务条数不变** + **WS 仍连着** ⇒ 场景成立。
### 3. W57 端到端验收:**通过**(`emulator-5554` + 假后端 18090,含 W57 的 APK = `900d191`)
| 步骤 | 实测值 |
|---|---|
| 通知权限 | `POST_NOTIFICATIONS: granted=true` |
| 装包含 W57 的 APK 后 | `pid=3180`、`KeepAliveService` **3 条**(开关本来就是开的,服务随启动自起) |
| 造场景(返回键 ×4) | 前台 = **launcher**(Activity finish)、**pid 仍 3180**、服务 **3 条**、假后端 `wsClients=1` |
| 注入前 `channel=proactive` | **0** |
| 注入 `proactive_message`(content「W57验收:Activity 没了也要弹」) | `delivered=1` |
| 注入后 `channel=proactive` | **1** |
| 通知内容 | `android.title=昔涟`、`android.text=W57验收:Activity 没了也要弹`(`bigText` 同) |
| 注入后 pid | **3180**(与场景建立前同一个 ⇒ **同一进程**,不是重启后补弹) |
⇒ **Activity 已销毁、进程靠前台服务活着时,「她主动说的话」照常弹通知。** 这正是 W57 要修的那一档。
**⚠️ 如实说明我们没做的**:**没有做「改前不弹」的对照实验**(要另出 W57 之前的 APK、再走一遍,会占构建锁和一次装包)。所以「改前确实不弹」这一半靠的是**代码级确定性证据**(订阅挂在 `viewModelScope`、`replay=0` 的 SharedFlow 丢弃无订阅者的 emit)与 9 条单测,**不是**实验证据。**别把它说成「实验证明改前是坏的」。**
### 4. 环境现状(收工前)
- HEAD = **`900d191`** = `origin/android`;worktree/分支/构建锁**全清**;主仓唯一脏 = `devdocs/02`(后端在途 + 我那条 markup 登记)。
- 模拟器上装的是**含 W57 的 APK**;App 现在**进程活着但 Activity 已 finish**(前台 launcher),登录的是假后端 `localhost`。
- 转发:`tcp:80 → tcp:18090`(假后端)、`tcp:10942 → tcp:10942`(真后端)。**要回真后端只改 `tcp:80` 一条。**
- 假后端 18090 进程仍在。