19、生命周期与感知:Lifecycle与Compose集成、LifecycleEventEffect、配置变更处理
生命周期管理,是Android开发里绕不开的话题。我记得刚接触Android那会儿,Activity的生命周期回调让我吃了不少苦头——onCreate里初始化、onPause里暂停、onDestroy里释放,稍有不慎就是内存泄漏或者空指针。到了Compose时代,情况有了很大变化,但生命周期的概念依然存在,只是换了一种更优雅的方式和我们打交道。
说白了,Compose的生命周期感知,核心就是让我们的UI代码能自动响应Activity或Fragment的生命周期变化。你不用再手动写一堆onStart、onStop的回调了,Compose帮你封装好了。
19.1 Lifecycle与Compose的集成方式
在Compose中,我们通过LocalLifecycleOwner这个CompositionLocal来获取当前的生命周期所有者。默认情况下,Compose会从最近的Activity或Fragment中拿到LifecycleOwner。
我个人的习惯是,在ViewModel里尽量不直接持有Lifecycle的引用,而是通过Compose层去感知。这样ViewModel保持纯Kotlin,测试起来也方便。
来看一个最简单的例子:
@Composable
fun LifecycleAwareScreen() {
val lifecycleOwner = LocalLifecycleOwner.current
val lifecycle = lifecycleOwner.lifecycle
// 观察生命周期状态变化
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_START -> Log.d("Lifecycle", "Screen started")
Lifecycle.Event.ON_STOP -> Log.d("Lifecycle", "Screen stopped")
else -> {}
}
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
Text("当前生命周期状态: ${lifecycle.currentState}")
}
嗯,这里要注意:DisposableEffect的key如果传了lifecycleOwner,那么当lifecycleOwner变化时,旧的observer会被清理,新的会被注册。这在Fragment切换场景下特别有用。
19.2 LifecycleEventEffect:更简洁的写法
每次都要写DisposableEffect加LifecycleEventObserver,说实话有点啰嗦。好在官方提供了LifecycleEventEffect这个扩展函数,让代码清爽很多。
我曾经在一个项目里,需要监听Activity的onResume来做数据刷新。如果用传统方式,得在Activity里写onResume回调,然后通知ViewModel。用Compose之后,一行代码搞定:
@Composable
fun DataRefreshScreen(viewModel: MyViewModel) {
// 当生命周期到达ON_RESUME时执行
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
viewModel.refreshData()
}
// 也可以监听多个事件
LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
viewModel.saveDraft()
}
// UI内容
val data by viewModel.data.collectAsState()
Text("数据: $data")
}
核心要点:LifecycleEventEffect会在Composable进入组合时开始监听,离开组合时自动移除监听。你不需要手动管理注册和注销。
你想想看,这比在Activity里写onResume、onPause要省多少事?而且代码就在Composable函数里,逻辑内聚性更强。
19.3 配置变更处理:不再需要onSaveInstanceState
配置变更,比如屏幕旋转、语言切换、字体大小变化,在传统View体系里是个麻烦事。Activity会被销毁重建,你得用onSaveInstanceState保存状态,然后在onCreate里恢复。
在Compose里,情况完全不同。因为Compose的状态管理机制天然支持配置变更。只要你的状态是用remember或mutableStateOf管理的,并且ViewModel是通过viewModel()获取的,配置变更后状态会自动保留。
为什么会这样?因为ViewModel的生命周期比Activity长,它不会因为配置变更而销毁。而Compose的remember在重组时会保留之前的值。
来看一个实际例子:
@Composable
fun RotationSafeScreen() {
// 这个状态在屏幕旋转后依然保留
var count by remember { mutableStateOf(0) }
// ViewModel在配置变更后也不会丢失
val viewModel: MyViewModel = viewModel()
Column(modifier = Modifier.padding(16.dp)) {
Text("点击次数: $count")
Button(onClick = { count++ }) {
Text("点击")
}
Text("ViewModel中的数据: ${viewModel.someData}")
}
}
我的建议:如果你有需要在配置变更后保留的UI状态,优先用rememberSaveable而不是remember。因为rememberSaveable会利用Bundle机制,即使进程被杀死也能恢复。我在一个表单页面里吃过亏,用户填了一半旋转屏幕,数据全丢了,后来换成rememberSaveable才解决。
19.4 生命周期感知的完整流程图
下面这张图展示了Compose中生命周期感知的核心流程,从Composable组合到生命周期事件响应,再到状态恢复:
19.5 实战中的避坑指南
我在项目中遇到过几个典型的生命周期问题,分享出来给大家参考:
我曾经踩过的坑:
- 在LaunchedEffect里直接使用lifecycle:LaunchedEffect的生命周期和Composable绑定,但如果你在里面直接访问
LocalLifecycleOwner.current,可能会拿到过时的引用。建议在LaunchedEffect外部获取lifecycle,然后作为参数传进去。 - 忘记处理ON_DESTROY:有些资源(比如传感器监听、蓝牙连接)必须在ON_DESTROY时释放。虽然Compose的DisposableEffect会在离开组合时自动清理,但如果你用了LifecycleEventEffect,记得也要监听ON_DESTROY事件。
- 配置变更后ViewModel里的LiveData重复观察:这个问题比较隐蔽。如果ViewModel里用LiveData,而Composable里用
observeAsState,配置变更后ViewModel不会重建,但Composable会重组。如果LiveData的观察者注册逻辑写在了重组路径里,可能会导致重复注册。解决方案是用collectAsState配合Flow,或者确保观察者注册在remember块里。
19.6 最佳实践总结
| 场景 | 推荐做法 | 说明 |
|---|---|---|
| 监听生命周期事件 | LifecycleEventEffect | 代码简洁,自动管理注册注销 |
| 需要更细粒度控制 | DisposableEffect + LifecycleEventObserver | 适合需要自定义observer的场景 |
| 配置变更保留UI状态 | rememberSaveable | 比remember更强,进程杀死也能恢复 |
| 配置变更保留业务数据 | ViewModel + StateFlow | ViewModel生命周期长,Flow天然支持 |
| 需要感知生命周期做动画 | LifecycleResumeEffect | 专门用于动画场景的扩展 |
个人经验:我一般会在项目里建一个LifecycleUtils.kt文件,把常用的生命周期监听封装成扩展函数。比如fun LifecycleOwner.onResume(action: () -> Unit),这样团队里其他人用起来也方便。代码复用率很高,推荐你也试试。
好了,关于生命周期与Compose的集成,核心就是这些。记住一点:Compose帮你管理了大部分生命周期细节,你只需要关注业务逻辑。但理解背后的原理,能让你在遇到奇怪问题时快速定位。嗯,就这样。
公众号:蓝海资料掘金营,微信deep3321