卷 I · CH 02 · 深度 02/18

Activity 不是应用,进程也不是承诺

屏幕旋转、退到后台、系统杀进程看起来都像「页面没了」,但它们要恢复的东西完全不同。

LIFECYCLEPROCESS DEATHSAVED STATE
▷ 先答一下

用户在消息框里打了半句,切到相机拍附件。系统因内存压力杀掉应用进程,稍后从最近任务恢复。哪样东西还一定活着?

  1. remember 里的草稿
  2. ViewModel 里的草稿
  3. Application 单例里的草稿
  4. 没有一个;除非它进入可恢复状态或持久层

三个寿命,不是一条生命周期

组合寿命最短:某段 Composable 是否还在 Composition 中。Activity 寿命更长:从创建到销毁,中间经历前台、可见、停止。进程寿命最不可控:当进程不再重要,系统可以直接回收,不会先给你一个可靠的「最后机会」。

配置变更时,旧 Activity 销毁、新 Activity 创建,但默认情况下同一 ViewModelStore 能把 ViewModel 交给新 Activity。进程死亡时,内存里的 Activity、ViewModel、单例和协程一起消失。系统保存的只是重建入口与少量实例状态,不是你的对象图。

可以带走的判断:生命周期不是回调顺序题,而是「这份状态要活过哪一种死亡」的存储选择题。

四类状态,四个去处

状态例子合适去处
纯视觉瞬时状态展开项、按压、动画进度remember
可序列化的 UI 输入草稿、筛选项、滚动位置rememberSaveableSavedStateHandle
页面业务状态会话详情、发送状态ViewModel 从数据层重建
不能丢的事实待发消息、患者同意记录Room、文件或服务端

Bundle 不是数据库。保存太多对象既慢又可能超过 Binder 事务限制。正确策略通常是保存一个能重新定位数据的键,例如会话 ID、草稿 ID;真正的数据从持久层重新观察。

回调要理解成可见性,不是业务流程

onStartonStop 大致对应可见,onResumeonPause 大致对应可交互。不要把「发消息必须完成」绑在某个回调上:多窗口、权限对话框、透明 Activity 都会让旧口诀失真。

Compose 收集 Flow 时,用 collectAsStateWithLifecycle() 让收集跟可见生命周期联动;必须跨进程可靠完成的上传交给 WorkManager。一个是停止浪费当前进程,另一个是即使当前进程消失也要重试

追问准备如果被问「ViewModel 会不会内存泄漏」,关键不是说会或不会,而是指出:它寿命长于 Activity View,所以不能持有 Activity、View 或短命 Context;需要 Context 时优先把资源访问留在 UI,或注入 Application 级依赖。
⌨ 自己跑一遍

给一个最小 ViewModel 加可恢复草稿。进程重建后,框架能从保存状态恢复 draft;消息列表则应该由 conversationId 重新查询:

可恢复:conversationId、draft
重新加载:messages
不可依赖:ViewModel 对象本身
class ChatViewModel(
    private val saved: SavedStateHandle,
    private val repository: ChatRepository
) : ViewModel() {
    val conversationId: String = checkNotNull(saved["conversationId"])

    val draft = saved.getStateFlow("draft", "")
    val messages = repository.observeMessages(conversationId)

    fun onDraftChanged(value: String) {
        saved["draft"] = value
    }
}

在 Android Studio 新建一个 Compose Activity,把「开发者选项 → 不保留活动」打开,再配合 adb shell am kill your.package 观察两种重建。不要用「强行停止」代替进程死亡测试,它的语义不同。

▸ 在现实里

用户从聊天页跳到系统相机或文件选择器时,你的页面可能停止甚至被回收。临床照片流程不能只把待上传字节握在 ViewModel 里;至少要有受控的临时文件 URI、可恢复的工作记录和明确的清理策略。

✗ 这个直觉是错的

「放进 ViewModel 就不会丢。」ViewModel 只跨配置变更,不跨进程死亡;它也不是通用缓存。

先问要跨过哪种死亡:重组、导航离开、Activity 重建、进程死亡、设备重启。答案决定 remember、ViewModel、保存状态、数据库或 WorkManager。

◇ 面试收口

答案是第 4 项。前三项都在进程内存里。需要恢复的短小 UI 输入进入保存状态;不能丢的业务事实必须先持久化。面试里主动区分配置变更与进程死亡,通常比背全套回调更能说明你真的理解 Android。

这一章的一句话

状态放在哪里,取决于它要活过哪一种死亡,而不是哪个 API 最顺手。

下一章把进程以外的系统门口补齐:一次上传究竟该用协程、WorkManager 还是前台服务?选错以后,差别不是优雅程度,而是任务会不会凭空消失。