本文译自「Understanding Execution Order in Jetpack Compose: DisposableEffect, LaunchedEffect, and Composables」,原文链接https://proandroiddev.com/understanding-execution-order-in-jetpack-compose-disposableeffect-launchedeffect-and-composables-d2d0b75b7ec8,由Sahil Thakar发布于2025年4月13日。
大家好,今天我们又来聊聊Jetpack-Compose的小话题。无论对于新手还是经验丰富的开发者来说,这都是一个小话题,但却是很关键的。我们将讨论一下Jetpack Compose中副作用和可组合项(composables)的执行顺序,特别是 DisposableEffect、LaunchedEffect 和可组合函数的执行顺序以及其生命周期交互过程。
我们将仔细探究 DisposableEffect 和 LaunchedEffect 在可组合项之间导航切换时是如何执行的,特别关注它们在返回之前访问过的页面时的行为。(许多经验丰富的开发者会告诉我我知道这一点,但我敢打赌,你们中的很多人并不知道)。
那么,让我们开始吧。
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 26 27 28 29 30 |
|
执行顺序迷题:为什么Column先执行?
答案在于这些副作用 API(DisposableEffect、LaunchedEffect)相对于组合(Composition)的实际执行时间。
1. 组合阶段优先
- Jetpack Compose 首先在组合期间构建UI树。
- 此时,Column 是一个可组合函数。它会在组合阶段立即执行,以构建 UI。
- 因此:Column() 首先运行 → 打出日志 “Column”。
2. 副作用在组合期间注册,但在组合完成后执行
- DisposableEffect 和 LaunchedEffect 在组合期间注册其工作, 但它们的实际执行发生在组合完成后。
- Compose 使用内部调度程序(通过 Recomposer)在提交帧后运行副作用。
因此,实际时间线如下所示:
1 2 3 4 5 6 7 8 |
|
这里我们讨论了composables和副作用之间的执行顺序。 那么在 LaunchedEffect和 DisposableEffect副作用函数之间,谁又将先执行呢?
让我们来仔细看看。
副作用函数的执行顺序(组合完成后):
- DisposableEffect → 首先运行
- LaunchedEffect → 随后运行
为啥子呢?
此顺序由Compose运行时定义的:
- DisposableEffect 是同步的,用于在组合后立即处理设置/清理。
- LaunchedEffect 会启动一个协程,而协程的启动是异步的,计划在其他同步效果(例如 DisposableEffect)之后运行。
内部机制:Jetpack Compose 维护了明确定义的效果应用顺序。
- DisposableEffect、SideEffect、SnapshotFlow 等副作用会在组合后立即触发(同步)。
- 然后,基于协程的效果(例如 LaunchedEffect)会被调度到下一个运行(异步,通过 Recomposer)。
现在,让我们看看在可组合项之间导航切换时 DisposableEffect 和 LaunchedEffect 是如何执行的,尤其关注它们在返回之前访问过的屏幕时的行为。
输出结果会让你大吃一惊。
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
|
它(Jetpack Compose导航)内部实际发生了什么?
Compose Navigation在 NavHost中围绕可组合项的行为遵循以下逻辑:
- 首先进行新目的地(此处为 ScreenA)的组合。
- 导航切换时,Compose会立即为新屏幕创建UI。
- 新页面(此处为 ScreenA)的 DisposableEffect 和 LaunchedEffect 会在新页面组合期间或之后立即执行。
- 在新目的地成功组合并提交到 UI 层次结构后,会处理上一个页面的可组合项(此处为 ScreenB)。
- Compose 会保持上一个可组合项(此处为 ScreenB)短暂处于活动状态,直到新可组合项(此处为 ScreenA)稳定,以确保导航顺畅。
- 只有在新的可组合项(此处为 ScreenA)完全组合后,Compose 才会清理并移除(dispose)上一个可组合项(此处为 ScreenB)。
因此,导航期间的实际生命周期流程是酱婶儿的:
1 2 3 4 5 6 7 8 |
|
为啥Compose要酱紫 搞?
Compose Navigation 会谨慎处理页面的组合,以确保丝滑(seamless)的用户体验和稳定性:
- 它不会在确保目标页面 (ScreenA) 已组合并准备就绪之前过早地处理上一个可组合项 (ScreenB)。
- 这可以避免在导航切换过程中出现视觉故障或空白屏幕。
- 只有在确保新页面安全到位后,Compose 才会触发处理上一个屏幕的操作。
Jetpack Compose NavHost内部机制(简化版本):
在调用 popBackStack() 或 navigate() 时,Compose 的 NavHost 内部的工作方式如下:
- 新的路由组合开始(可组合项创建)。
- 成功组合并提交帧后,不再位于 NavHost 后栈中的旧可组合项节点将被标记为待处理。
- 然后,Compose 会在下一帧中运行这些已移除可组合项的处置逻辑 (onDispose)。
因此,即使你在视觉上立即导航回原点,销毁式的操作(如onDispose)也会略微延迟执行,以保证界面的整体稳定性。
如果你有任何疑问,请留言,我会尽快回复你。💬✨ 我们很快会深入探讨Jetpack Compose,敬请期待!🚀 在此之前,祝你coding愉快!🎉👨💻