docs(05): 锚点补记三十八 —— 分享面板 P0 崩溃(两次:Dp.Infinity 当哨兵值 / 展开态只去上限没去 verticalScroll)已修并真机验收(收起 153px↔展开 255px,同一 pid 无崩溃);三条教训(单测抓不到渲染层崩溃、子代理论证再详细也不能替代真机点按、verticalScroll 的约束来源要看谁在外面);顺带推翻 W56『首帧回流看不见』的结论

This commit is contained in:
AskaEth committed 2026-10-07 09:51:59 +08:00
1 parent df7b504fa2
commit c40a752bcf
1 file changed
+57
+57
View File
@@ -2033,3 +2033,60 @@ adb shell input keyevent 4 ×4 # 逐层返回直到
- 原因:这次切换只改了 `adb reverse` 的目标,**App 看到的服务器地址字符串始终是 `localhost`、账号也还是同一个** ⇒ 按 W48 的口径(VM key 用**账号指纹**)key 没变 ⇒ 数据保留。**客户端无从感知**,所以这不是它的 bug。 - 原因:这次切换只改了 `adb reverse` 的目标,**App 看到的服务器地址字符串始终是 `localhost`、账号也还是同一个** ⇒ 按 W48 的口径(VM key 用**账号指纹**)key 没变 ⇒ 数据保留。**客户端无从感知**,所以这不是它的 bug。
- 正常路径上不会发生:**换服务器地址必须走登录页**(地址栏只在登录页),而登出会 `clearAll` ⇒ 数据会被清掉。所以**不登记为缺口**,只记这里。 - 正常路径上不会发生:**换服务器地址必须走登录页**(地址栏只在登录页),而登出会 `clearAll` ⇒ 数据会被清掉。所以**不登记为缺口**,只记这里。
- 那条残留会在 App **冷启动**(重新从服务端拉历史)后消失;我**没有**强杀进程去清它(不想打断当时正开着的保活服务状态)。 - 那条残留会在 App **冷启动**(重新从服务端拉历史)后消失;我**没有**强杀进程去清它(不想打断当时正开着的保活服务状态)。
## ★★ 锚点补记三十八 · 分享面板崩溃(W58,两次)—— 真机验收发现并修掉(2026-10-07 01:55)
> 主人这一轮说「**允许你截图验收**,1. 不用(不换前台服务类型)」,于是我开始自己上机验收 UI —— 第一次动分享面板就**验出一个 P0**。
### 1. 现象与根因(**同一个异常,踩了两次**)
真机崩溃栈(两次完全一致):
```
java.lang.IllegalStateException: Vertically scrollable component was measured with an
infinity maximum height constraints, which is disallowed.
at CheckScrollableContainerConstraintsKt.checkScrollableContainerConstraints
at androidx.compose.foundation.ScrollNode.measure
...
at androidx.compose.foundation.layout.SizeNode.measure ← heightIn(max = …)
at androidx.compose.ui.window.DialogLayout.internalOnMeasure
```
**第一次**(`01:37:30` pid 17630,分享进来、面板刚打开):`ShareConfirmBody` 里 `var cap by remember(text) { mutableStateOf(Dp.Infinity) }` —— **拿 `Dp.Infinity` 当「还没量到」的哨兵值**,于是首帧 `heightIn(max = Dp.Infinity)` = 不施加约束,同一串 modifier 上的 `verticalScroll` 就在无界高度下被测量。
**第二次**(`01:48:53` pid 2077,**点「展开」的那一下**):我第一笔修复把展开态的 `heightIn` 去掉了(改对了方向),**但把 `verticalScroll` 留着** —— 而外层 `ShareConfirmPreviews` **自己就是一个 `verticalScroll`**,它给内容的是**无界高度** ⇒ 这一层的 scroll 又在无界高度下被测量。
**修法(两笔合起来才完整)**:
1. `ShareConfirmPanel.kt`(`df7b504`):这一层的高度上限**只经纯函数 `shareConfirmWindowCap(measured)` 一个出口**(返回 `measured` 或 `SHARE_PREVIEW_MAX_HEIGHT`,**永远有限**),哨兵值改用 `null`;**展开态把这一层整个去掉(上限 + 滚动一起)**,只留外层那一层滚 —— 这也正是 W56 原本写在注释里的设计意图。
2. 测试:`ShareConfirmWindowCapTest` 4 条,钉住「任何输入都不会产出非有限上限」+「两种状态在链上都还有有限约束」。
3. 全量真跑:**114 suites / 1374 tests / 0 failures / 0 errors**。
### 2. 真机验收(`emulator-5554` + 真后端,改后重装)
用 `uiautomator dump` 的 **bounds** 当判据(比截图准,也能避开「抓到首帧」的坑):
| 状态 | 正文节点高度 | 控件 | pid |
|---|---|---|---|
| 收起 | `[127,1614][953,1767]` = **153px(3 行)** | 「展开」 | 4063 |
| 点「展开」后 | `[127,1512][953,1767]` = **255px**,面板整体上移 | 「收起」 | **4063** |
| 再点「收起」 | 回到 **153px** | — | **4063** |
三次操作**同一个 pid**,`exit-info` 里没有新崩溃记录 ⇒ **崩溃修掉、展开/收起功能都正常**。
### 3. ★ 三条教训(都值得写死)
1. **单测抓不到渲染层崩溃**。W54D / W56 都只有纯 JVM 单测(本模块没有 Compose UI 测试依赖),两笔改动都「测试全绿」——而真机上一打开面板就闪退、一点展开又闪退。**动渲染层必须上机**,这条从今天起按硬约束对待。
2. **「论证很详细」不等于「真机正确」**。W54D 的交付说明里专门论证过「`roundToPx` 不会退化成 0、不会触发 `checkScrollableContainerConstraints`」——方向全对,但**结论反了**:触发那个检查的恰恰是 `Constraints.Infinity` **本身**。子代理的反编译论证值得读,但**不能替代一次真机点按**。
3. **`verticalScroll` 的约束来源要看「谁在外面」**。同一个 `verticalScroll`,被 `heightIn(max = 有限值)` 包着就安全;一旦外面是另一个 `verticalScroll`(哪怕是隔了一层的 Column),它拿到的就是无界高度 ⇒ 崩。**「去掉上限」和「去掉滚动」是两件事,必须一起做。**
### 4. 顺带纠正一个我自己的误判(W56 的结论被推翻)
我第一次看截图时以为「3 行窗口和展开控件都没生效」(截图上正文显示了 5 行、也没有「展开」)。**是错的**:`uiautomator dump` 显示「展开」节点在、正文节点高 153px(3 行)。
真相是我**抓到了首帧**(此时 `cap` 还是 fallback `150dp`,还没收敛),而 dump 是稍后采的(已收敛到 3 行)。
⇒ 这反过来证明:**W56 当初判定「首帧那次回流看不见」是错的,它在真机上确实可见**(5 行 → 3 行跳一下)。
**待办**:要不要消掉它(W56 给的备选是组合期 `rememberTextMeasurer`)——**等主人定**,不是回归、不影响可用性。
### 5. 环境(本轮变化)
- **模拟器已重启**(主人建议):`adb emu kill` → 冷启动 `-avd Pixel_10_Pro_XL -no-boot-anim -no-snapshot-load`,约 35 秒 `boot_completed=1`。重启**会丢 `adb reverse`**,已重设:`tcp:80 → 10942`(真后端)、`tcp:10942 → 10942`。
- ⚠️ 重启前的旧模拟器上,**system_server 崩过至少三次**(`com.android.phone` 反复报 `DeadSystemException: The system died`,时间 `00:03` / `00:48` / `01:19`)——这也是长跑第 3 次 2 小时失败的原因(见上一节)。
- HEAD = **`df7b504`** = `origin/android`;主仓唯一脏 = `devdocs/02`(后端在途 + 我的 markup 登记)。