Jetpack Compose 页面生命周期 —— 三层模型与 Effect 全解

Compose 页面生命周期完全指南

Compose 的生命周期不是单一概念,而是三层叠加模型

  1. Android 生命周期LifecycleOwner)—— Activity / Fragment 的传统生命周期
  2. 组合生命周期(Composition)—— Composable 进入/离开组合树
  3. Effect 生命周期LaunchedEffect / DisposableEffect)—— 副作用随 key 变化

理解这三层如何协作,是写出无内存泄漏、无状态错乱的 Compose 代码的关键。


一、三层生命周期全景图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     onCreate()     →  Composition    →  Composition
onStart() 首次进入 重组
onResume()
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────┐
│ Activity Lifecycle │
│ Lifecycle.Event.ON_START / ON_RESUME / ... │
└─────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────┐
│ Composition(组合) │
│ 进入组合 → 重组(recomposition) → 退出组合 │
└─────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────┐
│ Effect(副作用) │
│ LaunchedEffect → key 变化 → 协程取消 │
│ DisposableEffect → key 变化 → dispose → 重启 │
└─────────────────────────────────────────────────┘

关键区别

层次粒度触发时机主要用途
Android Lifecycle页面级onStart / onResume / onStop整体可见性、前后台切换
Composition组件级首次组合 / 重组 / 退出UI 渲染、remember
Effect副作用key 变化 / 退出组合协程、监听器、资源管理

二、Composition 生命周期 —— 进入与退出

2.1 进入组合(Enter Composition)

Composable 函数首次被调用并加入组合树

1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun MyScreen() {
// 每次重组都会执行这里
val name = remember { "World" } // 只在首次组合时计算

// 首次进入组合时执行
DisposableEffect(Unit) {
println("📌 Entered composition")
onDispose { println("📌 Left composition") }
}

Text("Hello $name")
}

2.2 重组(Recomposition)

State 发生变化,依赖该 State 的 Composable 会被重新执行

1
2
3
4
5
6
7
8
9
10
@Composable
fun Counter() {
var count by remember { mutableIntStateOf(0) }
// ↑ count 变化会触发重组

Text("Count: $count") // 重新执行
Button(onClick = { count++ }) { // onClick lambda 不变
Text("Increment")
}
}

2.3 退出组合(Leave Composition)

Composable 从组合树中移除(条件渲染、导航离开等):

1
2
3
4
5
6
@Composable
fun Parent(showChild: Boolean) {
if (showChild) {
Child() // showChild = false 时退出组合
}
}

三、Effect API —— 副作用四大件

Compose 提供 4 个 Effect API 来管理副作用。它们的核心区别在于何时执行何时清理

3.1 LaunchedEffect —— 协程的入口

key 变化 → 取消旧协程 → 启动新协程;退出组合 → 取消协程

1
2
3
4
5
6
7
8
9
10
11
@Composable
fun LoadUserData(userId: String) {
var user by remember { mutableStateOf<User?>(null) }

LaunchedEffect(userId) {
// userId 变化时:取消旧协程 → 启动新协程
user = api.fetchUser(userId)
}

user?.let { Text(it.name) } ?: CircularProgressIndicator()
}

LaunchedEffect 生命周期时序

1
2
3
4
5
6
7
8
userId="a"          userId="b"        退出组合
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│协程: a │ → │取消 a │ → │取消 b
│fetch... │ │协程: b │ │ │
│ │ │fetch... │ │ │
└─────────┘ └─────────┘ └─────────┘

3.2 DisposableEffect —— 需要清理的副作用

**key 变化 → dispose() → 重新 enter();退出组合 → dispose()**。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Composable
fun ObserveUserStatus(userId: String) {
DisposableEffect(userId) {
val observer = object : Observer {
override fun onChange(status: String) {
// 更新状态
}
}
api.registerObserver(userId, observer)

onDispose {
api.unregisterObserver(userId, observer) // ← 清理!
}
}

// 这里不需要 LaunchedEffect,一次性注册 + 清理即可
}

DisposableEffect 典型场景

场景注册onDispose
Lifecycle 观察lifecycle.addObserver(...)lifecycle.removeObserver(...)
广播接收器context.registerReceiver(...)context.unregisterReceiver(...)
传感器监听sensorManager.registerListener(...)sensorManager.unregisterListener(...)
EventBusEventBus.register(...)EventBus.unregister(...)
WebSocketws.connect()ws.close()

3.3 SideEffect —— 每次重组后执行(无 key)

每次成功重组后调用,无清理逻辑。适用于与非 Compose 状态同步:

1
2
3
4
5
6
7
8
9
@Composable
fun AnalyticsScreen(screenName: String) {
val analytics = LocalAnalytics.current

SideEffect {
analytics.logScreenView(screenName)
// 每次重组后都执行(无 key,无法跳过)
}
}

3.4 rememberUpdatedState —— 保持引用最新

解决闭包捕获旧值的问题。长生命周期的 Effect 需要获取最新状态,但又不希望 key 变化导致重启:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Composable
fun LandingPage(onTimeout: () -> Unit) {
// ❌ 错误:onTimeout 变化会导致 LaunchedEffect 重启
LaunchedEffect(Unit) {
delay(3000L)
onTimeout() // 可能拿到旧的回调
}

// ✅ 正确:referenceToTimeout 总是最新的,但不会重启协程
val currentOnTimeout by rememberUpdatedState(onTimeout)
LaunchedEffect(Unit) {
delay(3000L)
currentOnTimeout() // 始终拿到最新的 onTimeout
}
}

3.5 Effect 对比总结

APIkey 变化退出组合每次重组后有清理可挂起
LaunchedEffect取消 + 重启取消自动取消协程
DisposableEffectdispose + 重启dispose手动 onDispose
SideEffect
rememberUpdatedState

四、Android 生命周期集成

4.1 获取 LifecycleOwner

1
2
3
// Compose 中获取 Lifecycle
val lifecycleOwner = LocalLifecycleOwner.current
val lifecycle = lifecycleOwner.lifecycle

LocalLifecycleOwner 由 Navigation 或包含 ComposeView 的 Activity/Fragment 自动提供。

4.2 监听生命周期事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Composable
fun LifecycleAwareComponent() {
val lifecycleOwner = LocalLifecycleOwner.current

DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_START -> println("👀 页面可见")
Lifecycle.Event.ON_STOP -> println("🙈 页面不可见")
Lifecycle.Event.ON_RESUME -> println("👉 获得焦点")
Lifecycle.Event.ON_PAUSE -> println("👈 失去焦点")
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)

onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}

4.3 常用生命周期事件含义

1
2
3
4
5
6
7
8
9
10
11
 ON_CREATE ═══ ON_START ═══ ON_RESUME
│ │ │
│ │ [页面在前台,可交互]
[页面可见]
[初始化完成] │ ON_PAUSE
│ │
▼ ▼
ON_STOP ← [部分不可见]


ON_DESTROY

4.4 组合 Lifecycle 与 StateFlow 的协程

**核心 API:repeatOnLifecycle**。在正确的生命周期状态下收集 flow,自动暂停/恢复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun ObserveWithLifecycle(viewModel: MyViewModel) {
val lifecycleOwner = LocalLifecycleOwner.current

val uiState by produceState<UiState>(
initialValue = UiState.Loading,
key1 = lifecycleOwner,
key2 = viewModel,
) {
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { value = it }
}
}
}

更简洁的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ViewModel 中
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}

// Composable 中 —— 使用 flowWithLifecycle 扩展
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val lifecycleOwner = LocalLifecycleOwner.current
val uiState by viewModel.uiState
.flowWithLifecycle(
lifecycle = lifecycleOwner.lifecycle,
minActiveState = Lifecycle.State.STARTED
)
.collectAsStateWithLifecycle(initialValue = UiState.Loading)
}

collectAsStateWithLifecycle(推荐)

1
2
3
4
5
6
7
8
// lifecycle-runtime-compose 库
// implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")

@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// 自动在 STARTED 收集,在 STOPPED 取消,无需手动 repeatOnLifecycle
}

4.5 生命周期状态速查

状态何时进入适合做的事情
CREATEDActivity 创建后一次性初始化
STARTED页面可见开始收集 Flow、动画、网络请求
RESUMED页面在前台可交互相机、位置更新、高优先级任务
PAUSED部分可见(如弹窗遮挡)暂停相机、降低刷新频率
STOPPED完全不可见停止 Flow 收集、释放重资源
DESTROYED销毁前最终清理

五、remember 与 rememberSaveable

5.1 remember —— 跨重组保留

1
2
3
4
5
6
7
8
9
@Composable
fun RememberExample() {
// ✅ 重组后保留
var count by remember { mutableIntStateOf(0) }

// ❌ 每次重组重置为 0
var badCount by mutableIntStateOf(0)
// ———— 因为重组会重新执行整个函数!
}

5.2 rememberSaveable —— 跨进程死亡保留

1
2
3
4
5
6
7
8
9
10
11
@Composable
fun SearchScreen() {
var query by rememberSaveable { mutableStateOf("") }
// 进程被杀死后恢复,query 值仍然保留

OutlinedTextField(
value = query,
onValueChange = { query = it },
label = { Text("Search") }
)
}
方式跨重组跨配置变更跨进程死亡
remember
rememberSaveable
ViewModel否(需 SavedStateHandle
ViewModel + SavedStateHandle

5.3 自定义 Saveable Saver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data class User(val name: String, val age: Int)

val UserSaver = run {
val nameKey = "name"
val ageKey = "age"
mapSaver(
save = { mapOf(nameKey to it.name, ageKey to it.age.toString()) },
restore = { User(it[nameKey]!!, it[ageKey]!!.toInt()) }
)
}

@Composable
fun UserScreen() {
var user by rememberSaveable(stateSaver = UserSaver) {
mutableStateOf(User("Alice", 25))
}
}

六、ViewModel 生命周期

6.1 ViewModel 何时销毁

1
2
3
4
5
6
7
8
9
10
Activity/Fragment 创建


ViewModel 创建

... 配置变更(旋转屏幕)... ViewModel 不销毁!


onCleared()
(Activity 真正销毁 / Fragment 移除)

6.2 ViewModel + SavedStateHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class EditorViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {

var title: String
get() = savedStateHandle.get<String>("title") ?: ""
set(value) { savedStateHandle["title"] = value }

var content: String
get() = savedStateHandle.get<String>("content") ?: ""
set(value) { savedStateHandle["content"] = value }
}

@Composable
fun EditorScreen(viewModel: EditorViewModel = viewModel()) {
// 进程杀死后恢复时,title 和 content 自动还原
OutlinedTextField(
value = viewModel.title,
onValueChange = { viewModel.title = it },
)
}

6.3 ViewModel 初始化时机(懒加载)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sealed interface EditorUiState {
data object Loading : EditorUiState
data class Ready(val title: String, val content: String) : EditorUiState
}

class EditorViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {

private val _uiState = MutableStateFlow<EditorUiState>(EditorUiState.Loading)
val uiState: StateFlow<EditorUiState> = _uiState.asStateFlow()

init {
// ViewModel 创建时加载数据(懒加载:首次访问 ViewModel 时)
loadContent()
}

private fun loadContent() {
val title = savedStateHandle.get<String>("title") ?: ""
val content = savedStateHandle.get<String>("content") ?: ""
_uiState.value = EditorUiState.Ready(title, content)
}
}

七、CompositionLocal —— 作用域内生命周期

CompositionLocal 的值在组合树的某个节点提供,子节点退出组合时自动不可见:

1
2
3
4
5
6
7
8
9
val LocalThemeColor = compositionLocalOf { Color.Unspecified }

@Composable
fun ThemeProvider(color: Color, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalThemeColor provides color) {
content() // 这里及子节点可获取 LocalThemeColor
}
// 离开这个 scope,回到默认值
}

八、配置变更 —— 屏幕旋转、语言切换等

资源配置变更时行为进程死亡时行为
remember丢失丢失
rememberSaveable保留保留
ViewModel保留丢失
ViewModel + SavedStateHandle保留保留
object / companion 单例保留丢失
DataStore / SharedPreferences保留保留

最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun OrderScreen(orderId: String) {
// 短期 UI 状态 → rememberSaveable
var expanded by rememberSaveable { mutableStateOf(false) }

// 业务数据 → ViewModel + SavedStateHandle
val viewModel: OrderViewModel = viewModel()
val order by viewModel.order.collectAsStateWithLifecycle()

LaunchedEffect(orderId) {
viewModel.loadOrder(orderId)
}
}

九、页面生命周期实战场景

9.1 回到前台时刷新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun RefreshOnResume(viewModel: DataViewModel) {
val lifecycle = LocalLifecycleOwner.current.lifecycle

DisposableEffect(lifecycle) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
viewModel.refresh()
}
}
lifecycle.addObserver(observer)
onDispose { lifecycle.removeObserver(observer) }
}
}

9.2 离开页面时暂停视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Composable
fun VideoPlayer(videoId: String) {
val lifecycle = LocalLifecycleOwner.current.lifecycle

DisposableEffect(lifecycle) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_PAUSE -> player.pause()
Lifecycle.Event.ON_RESUME -> player.resume()
Lifecycle.Event.ON_STOP -> player.release()
else -> {}
}
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
player.release()
}
}
}

9.3 秒杀倒计时(退出页面自动取消)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun FlashSaleCountdown(endTime: Long) {
var remaining by remember { mutableLongStateOf(endTime - System.currentTimeMillis()) }

LaunchedEffect(Unit) {
while (remaining > 0) {
delay(1000)
remaining = (endTime - System.currentTimeMillis()).coerceAtLeast(0)
}
}
// ✅ 退出页面时协程自动取消,无需手动清理

Text("${remaining / 1000} 秒后结束")
}

9.4 权限请求(关联生命周期)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Composable
fun RequestCameraPermission(onGranted: () -> Unit) {
val lifecycleOwner = LocalLifecycleOwner.current
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) onGranted()
}

DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
launcher.launch(android.Manifest.permission.CAMERA)
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
}

9.5 定时器 + 可见性联动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun AutoRefreshFeed(viewModel: FeedViewModel) {
val lifecycle = LocalLifecycleOwner.current.lifecycle

// lifecycle 作为 key:在 STARTED 时启动协程,STOPPED 时自动取消
LaunchedEffect(lifecycle) {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
while (true) {
delay(30_000)
viewModel.refresh()
}
}
}
}

十、常见生命周期错误与修复

问题原因修复
协程泄漏LaunchedEffect key 用 Unit 但外部依赖变化key 传入依赖项(如 userId
Flow 在后台仍然收集直接用 collectAsState()改用 collectAsStateWithLifecycle()repeatOnLifecycle
配置变更后状态丢失用了 remember 存重要状态改用 rememberSaveableSavedStateHandle
ViewModel 重新创建在 Composable 函数内手动 ViewModel() 且传了错误参数确保 key 参数正确;优先使用 viewModel() 工厂
动画在 ON_STOP 时继续动画未绑定生命周期LaunchedEffect + repeatOnLifecycle 包裹动画循环
onDispose 不执行DisposableEffect key 用了可变对象key 使用 stable 类型(StringIntUnit 等)
闭包捕获过期值Effect 启动后 lambda 被替换rememberUpdatedState 包装回调

十一、生命周期相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
dependencies {
// Compose 基础(含 remember、Effect API)
implementation("androidx.compose.runtime:runtime")

// ViewModel 集成
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")

// collectAsStateWithLifecycle(一键生命周期安全收集)
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")

// SavedStateHandle
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7")
}

十二、生命周期决策速查表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
需要做一件事,放在哪里?

┌─ 是否需要在「每次重组」后执行?
│ ├─ 是 → SideEffect { }
│ └─ 否 ↓

├─ 是否需要在「离开组合 / key 变化」时清理?
│ ├─ 是 → DisposableEffect(key) { onDispose { } }
│ └─ 否 ↓

├─ 是否需要「协程 / delay / 异步」?
│ ├─ 是 → LaunchedEffect(key) { }
│ └─ 否 ↓

├─ 是否需要「依赖 Android 生命周期」?
│ ├─ 是 → DisposableEffect(lifecycle) + repeatOnLifecycle
│ └─ 否 ↓

├─ 是否「UI 状态」需要在配置变更后恢复?
│ ├─ 是 → rememberSaveable { }
│ └─ 否 → remember { }

└─ 是否「业务数据」需要在进程死亡后恢复?
├─ 是 → ViewModel + SavedStateHandle
└─ 否 → ViewModel

全文覆盖 Compose 生命周期三层模型(Android Lifecycle / Composition / Effect)、四大 Effect API、rememberSaveablerepeatOnLifecycle、5 个实战场景和 7 类常见错误修复方案。