Android 学习笔记 D21|一次收藏错位和返回异常,怎样定位真正的根因
发布摘要:用一个可重复制造的跨页面问题完成第三周回归,追踪收藏事实来源、首页身份和网页返回事件。标签:Android、调试、状态管理、项目复盘。
前六天增加了主题、滑页、传统列表、网页阅读、分享和后台任务。每个功能单独演示都正常,连起来却可能出错。本篇构造一个教学诊断案例:从收藏页进入详情,取消收藏,再打开网页并分享,最后返回,旧收藏仍出现,页签还跳回文章。这个案例是用于复现与推理的实验,不是已经发生在用户项目里的真实经历。
本文对应 D21。相关基础回读《第一行代码》第 3---5 章的页面与列表、第 7---8 章的存储与组件、第 10---11 章的服务与网络,以及第 13.6 节后台任务。今天不增加新库,目标是把多个局部正确的功能组成一个一致的系统。
一、先写操作路径,暂时不要修改代码
准备三十篇带不同编号的受控文章,至少收藏二十篇,使收藏列表超过一屏。进入收藏页并滚到一个不在首屏的固定编号,记录它为本次目标,打开详情、取消收藏,然后打开原文 A、从 A 跳到 B、打开分享面板并取消。连续返回,直到再次看到首页。
预期分成三件事:先由 B 回 A,再退出阅读页;详情中收藏已取消;回到原首页时仍位于收藏页,目标编号已经消失。只有把三个预期分开,才不会把"回来看到第一页"与"收藏数据错误"误认为同一个导航问题。
复现后先记录实际现象,再提出假设。如果只看到收藏图标错了,不应直接断言数据库未写入;如果日志出现多个首页对象,也不等于一定存在泄漏,可能是错误导航创建了新的条目。
二、沿着事件和数据分别追踪
事件方向是按钮到 ViewModel,再到 Repository 和 DAO;数据显示方向是数据库流到 ViewModel,再到各页面。诊断要在边界处记录同一个文章编号,不要只写"点击成功""刷新完成"这样无法关联的日志。
以下是放在调试源码中的完整辅助对象,需要 android.util.Log 和 java.util.concurrent.atomic.AtomicLong。它只记录可公开的文章编号、状态摘要和实例标签,不记录令牌、文件内容或完整网页参数。尚未在用户工程编译运行。
kotlin
import android.util.Log
import java.util.concurrent.atomic.AtomicLong
object FlowTrace {
private val sequence = AtomicLong()
fun record(stage: String, articleId: Long?, detail: String) {
val order = sequence.incrementAndGet()
Log.d("ArticleTrace", "#$order stage=$stage id=$articleId $detail")
}
fun instanceTag(value: Any): String =
Integer.toHexString(System.identityHashCode(value))
}
第一处在取消按钮记录编号和目标值,第二处在数据库写入完成后记录成功,第三处在收藏流发射时记录编号集合,第四处在页面收到状态时记录首页条目标识、页码和集合。实例标记只是调试线索,不当作永久业务编号。
如果写入成功且数据库流已移除编号,但收藏页仍显示它,范围就收缩到状态转换或 UI。检查是否写过 remember { initialBookmarks },把首次输入冻结成局部副本;是否有普通可变集合原地更新而没有可观察通知;是否错误复用了列表 key。
三、把两种故障分开修复
本实验故意让收藏页缓存首次列表。修复是由共享 Repository 的最新流生成 UI 状态,让页面直接消费,并用文章编号作为稳定 key。不要用"返回后手动刷新一次"掩盖副本,因为下一次详情、传统 View 页或后台任务修改数据仍可能失步。单一事实来源与数据层
第二个故障故意在阅读页退出时重新创建 Home 返回项。修复是弹出当前阅读项,让已有首页条目继续承担页码和列表状态,而不是新建一个默认页码为零的首页。需要检查导航条目保存状态的配置;只有同一个业务目的地名称,并不证明是同一个返回项。Navigation 3 状态保存
网页返回另设检查:只有当前可见阅读页且 canGoBack 为真时,网页处理器才接管系统返回。返回条件变化必须更新到可观察状态,否则 UI 中的旧布尔值可能让处理器一直启用。工具栏返回与系统返回采用相同的优先级,减少路径差异。
四、回归应覆盖修复周边,而不是只重走一次
先在文章页收藏,再从传统 View 页取消,预期两种界面都反映数据库变化。随后从收藏页进入详情,直接返回,预期页码和滚动位置保持;再走一次网页 A 到 B 到返回的路径,预期两条栈按顺序变化。
主题回归在固定深色下执行整条路径,并旋转一次。若旧 View 页没有使用项目主题配置,应将其列为明确差异或补齐适配,不能只凭 Compose 首页截图宣称全应用一致。分享取消后不应修改收藏;分享面板出现也不应被记录成发送成功。
Worker 回归关注另一类问题:排队时离开页面、网络恢复后写数据库,重新进入应用时观察最新缓存。KEEP 避免同名未完成工作堆积,但页面刷新仍可能并发,因此还要检查同一文章重复写入和收藏保护。WorkManager 的唯一工作范围可以对照官方管理说明。
最后重复进入退出传统页,核对旧 View 的收集停止、binding 清理和 ComposeView 释放;这里只证明检查过的引用链,不以一次没有崩溃作为完全不存在内存问题的结论。
五、怎样写成有证据的复盘段落
复盘可以按这样组织:"在收藏页取消后返回仍显示旧条目。数据库查询和 Flow 发射均已移除该编号,但页面日志仍打印初始集合。定位到页面把首次参数保存在 remember 中。移除副本后由共享流驱动,并回归详情、传统页和后台更新三种修改入口。"
这段话只有完成真实实验后才能改成自己的实测经历。写博客时将证据编号、截图位置和实际版本补上;如果只完成一半,应如实写"已定位到 UI 副本,尚未验证后台更新路径"。技术复盘的价值来自证据把原因缩小,而不是使用很多排查术语。
六、原创面试问答与追问
可连接题库Fragment 之间通信与Activity 生命周期。以下自拟题用来检查能否解释案例。
问:数据写入成功,界面为什么仍旧? 答:写入到显示之间还有流转换、状态持有和观察链,任何一处副本都可能滞后。追问:第一步怎么查?先对比数据库实际值、流发射值与界面接收值。
问:返回到 Home 为什么会回第一页? 答:可能创建了新的首页条目,也可能保存状态未接入。追问:如何区分?同时记录返回栈身份和 Pager 状态创建时机。
问:修复后只重测原路径够吗? 答:还需验证共享边界上的其他入口。追问:本案例选哪些?详情取消、传统页取消和后台数据更新,因为它们都可能暴露副本问题。
七、当天练习与验收
先用二十分钟预测案例现象,四十分钟整理日志点,一百五十分钟复现并修复,三十分钟回归,最后一小时闭卷讲述根因。独立任务从"网页打不开、快速切页、重新进入传统页"抽一个条件,先写假设再操作。
交付应是一份完整问题记录:操作、预期、实际、证据、修改和回归结果。通过标准是能说明排除了哪些原因、为何修改在正确层次,以及哪些扩展仍没有验证。