Compose的基本使用
1. LaunchedEffect
就像是派一个助手去做某件事:
@Composable
fun TimerExample() {
var time by remember { mutableStateOf(0) }
// 就像安排一个助手每秒更新时间
LaunchedEffect(Unit) {
while(true) {
delay(1000)
time++
}
}
Text("Time: $time")
}
生活例子:
- 就像你请助手每隔一秒看一下时钟,然后告诉你时间
- 当你不需要这个服务时(组件销毁),助手就会停止工作
2. DisposableEffect
像是租借设备,用完要归还:
@Composable
fun SensorExample() {
DisposableEffect(Unit) {
// 借用传感器
val sensor = getSensor()
sensor.start()
// 用完归还
onDispose {
sensor.stop()
}
}
}
生活例子:
- 就像你租了一台相机
- 用完后要记得归还
- 确保不会浪费资源
3. SideEffect
像是记日记,记录发生的事:
@Composable
fun LoggingExample(count: Int) {
SideEffect {
// 记录每次数值变化
println("Count changed to: $count")
}
}
生活例子:
- 就像每次你做完一件事
- 都在日记本上记一笔
- 用来追踪发生了什么
4. rememberCoroutineScope
像是雇一个随时待命的助手:
@Composable
fun HelperExample() {
val helper = rememberCoroutineScope()
Button(onClick = {
helper.launch {
// 执行一些耗时任务
delay(1000)
showMessage("Done!")
}
}) {
Text("Start Task")
}
}
生活例子:
- 就像有个助手随时待命
- 当你需要做某事时
- 可以立即安排助手去做
流程图
graph TD
A[组件创建] --> B{需要什么类型的Effect?}
B -->|异步任务| C[LaunchedEffect]
B -->|资源管理| D[DisposableEffect]
B -->|记录操作| E[SideEffect]
B -->|待命助手| F[rememberCoroutineScope]
C --> G[执行异步操作]
G --> H[完成或取消]
D --> I[使用资源]
I --> J[清理资源]
E --> K[执行同步操作]
F --> L[创建协程作用域]
L --> M[按需执行任务]
实际应用示例
- 定时刷新示例:
@Composable
fun RefreshExample() {
var data by remember { mutableStateOf("") }
// 像是定时提醒
LaunchedEffect(Unit) {
while(true) {
data = fetchNewData()
delay(5000) // 每5秒刷新一次
}
}
Text("Data: $data")
}
- 监听器示例:
@Composable
fun ListenerExample() {
// 像是安装和卸载监听器
DisposableEffect(Unit) {
val listener = EventListener()
addEventListener(listener)
onDispose {
removeEventListener(listener)
}
}
}
- 组合使用示例:
@Composable
fun CombinedExample() {
val scope = rememberCoroutineScope() // 待命助手
var data by remember { mutableStateOf("") }
// 定时任务
LaunchedEffect(Unit) {
loadInitialData()
}
// 资源管理
DisposableEffect(Unit) {
startService()
onDispose { stopService() }
}
// 记录操作
SideEffect {
logDataChange(data)
}
// 用户操作
Button(onClick = {
scope.launch {
refreshData()
}
}) {
Text("Refresh")
}
}
使用建议
- 选择合适的Effect:
- 异步操作 → LaunchedEffect
- 资源管理 → DisposableEffect
- 操作记录 → SideEffect
- 随时任务 → rememberCoroutineScope
- 注意事项:
- Effect要在Composable函数中使用
- 正确处理清理工作
- 避免过度使用Effect
这样的设计让我们能够:
- 有序地管理副作用
- 正确处理资源
- 保持代码清晰
- 提高应用性能
Effect 和compose runtime的交互
Compose Runtime 和 Effect 的交互机制:
1. Compose Runtime 和 Effect 的基本关系
graph TD
A[Compose Runtime] -->|创建| B[Effect Registry]
B -->|管理| C[Effect 实例]
C -->|生命周期| D[Effect 执行]
D -->|状态变化| A
2. Effect 的注册和执行过程
// 简化的 Runtime 实现
class ComposeRuntime {
// Effect 注册表
private val effectRegistry = mutableMapOf<Any, EffectInfo>()
// 执行组合
fun compose() {
// 1. 创建组合上下文
val context = CompositionContext()
// 2. 执行组合
try {
// 进入组合阶段
context.startComposition()
// 执行可组合函数
content()
} finally {
// 完成组合
context.endComposition()
}
}
}
// Effect 信息
data class EffectInfo(
val key: Any,
val effect: suspend () -> Unit,
val cleanup: () -> Unit
)
3. LaunchedEffect 的实现原理
@Composable
fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit
) {
val currentComposer = currentComposer
DisposableEffect(key1) {
// 1. 创建新的协程
val job = currentComposer.scopeCoroutine.launch {
block()
}
// 2. 清理时取消协程
onDispose {
job.cancel()
}
}
}
生活例子:
- Compose Runtime 就像一个餐厅经理
- Effect 就像是不同的服务员
- 每个服务员负责特定的任务(Effect)
- 经理负责协调所有服务员的工作
4. 实际工作流程
@Composable
fun EffectExample() {
// 1. 创建状态
var count by remember { mutableStateOf(0) }
// 2. 注册 Effect
LaunchedEffect(count) {
// 3. Effect 执行
println("Count changed to: $count")
}
}
工作流程图:
sequenceDiagram
participant Composable
participant Runtime
participant EffectManager
participant Effect
Composable->>Runtime: 创建/更新组件
Runtime->>EffectManager: 检查 Effect 注册
alt 首次执行
EffectManager->>Effect: 创建新 Effect
else 键值变化
EffectManager->>Effect: 重新执行 Effect
else 键值未变
EffectManager->>Effect: 保持现有 Effect
end
Effect-->>Runtime: Effect 执行结果
Runtime-->>Composable: 更新UI
5. Effect 生命周期管理
class EffectManager {
private val activeEffects = mutableMapOf<Any, EffectHandle>()
fun manageEffect(key: Any, effect: Effect) {
// 1. 检查是否存在相同 key 的 Effect
val existing = activeEffects[key]
if (existing != null) {
if (existing.shouldUpdate(effect)) {
// 2. 需要更新:清理旧的,启动新的
existing.dispose()
activeEffects[key] = effect.start()
}
// 否则保持现有 Effect
} else {
// 3. 新建 Effect
activeEffects[key] = effect.start()
}
}
}
6. 状态追踪和重组
class EffectScope {
private var currentEffect: Effect? = null
fun trackEffect(effect: Effect) {
// 1. 记录当前正在执行的 Effect
currentEffect = effect
try {
// 2. 执行 Effect
effect.execute()
} finally {
// 3. 清理追踪
currentEffect = null
}
}
}
7. 优化机制
class EffectOptimizer {
fun shouldRecompose(oldKey: Any?, newKey: Any?): Boolean {
return when {
// 1. 键值相同,不需要重组
oldKey === newKey -> false
// 2. 结构相等性检查
oldKey != null && newKey != null &&
oldKey == newKey -> false
// 3. 其他情况需要重组
else -> true
}
}
}
关键点总结:
-
注册机制
- Runtime 管理所有 Effect 的注册
- 每个 Effect 都有唯一的标识
-
生命周期管理
- 创建:首次执行时
- 更新:键值变化时
- 销毁:组件销毁时
-
状态追踪
- 自动追踪 Effect 依赖的状态
- 只在必要时重新执行
-
优化策略
- 避免不必要的重新执行
- 智能的清理机制
- 协程作用域管理
这种机制确保了:
- Effect 的正确执行
- 资源的有效管理
- 性能的优化
- 状态的一致性
开发中的Effect优化
让我从实际开发角度,总结 Effect 的最佳实践和优化建议:
1. 正确使用 Effect 的 key
// ❌ 错误示例:不必要的重组
@Composable
fun BadExample() {
LaunchedEffect(System.currentTimeMillis()) { // 每次重组都会创建新的 Effect
doSomething()
}
}
// ✅ 正确示例:使用稳定的 key
@Composable
fun GoodExample(id: String) {
LaunchedEffect(id) { // 只在 id 变化时重新执行
doSomething()
}
}
2. 避免重复创建 Effect
// ❌ 错误示例:在循环中创建 Effect
@Composable
fun BadListExample(items: List<Item>) {
items.forEach { item ->
LaunchedEffect(Unit) { // 每个 item 都创建一个 Effect
processItem(item)
}
}
}
// ✅ 正确示例:合并 Effect
@Composable
fun GoodListExample(items: List<Item>) {
LaunchedEffect(items) { // 一个 Effect 处理所有 items
items.forEach { item ->
processItem(item)
}
}
}
3. 合理管理资源
// ❌ 错误示例:资源泄露
@Composable
fun BadResourceExample() {
val context = LocalContext.current
val locationManager = context.getSystemService(LOCATION_SERVICE) as LocationManager
LaunchedEffect(Unit) {
locationManager.requestLocationUpdates(/*...*/)
// 没有清理监听器
}
}
// ✅ 正确示例:使用 DisposableEffect
@Composable
fun GoodResourceExample() {
val context = LocalContext.current
DisposableEffect(Unit) {
val locationManager = context.getSystemService(LOCATION_SERVICE) as LocationManager
locationManager.requestLocationUpdates(/*...*/)
onDispose {
locationManager.removeUpdates(/*...*/)
}
}
}
想象你租了一个健身房的储物柜:
错误的方式(BadResourceExample):
// 相当于:
fun 使用储物柜() {
// 租了储物柜
val 储物柜 = 健身房.租储物柜()
// 开始使用储物柜
储物柜.存放物品()
// ❌ 走的时候忘记还钥匙了!
// 下次别人就不能用这个储物柜了
}
这种情况会造成:
- 储物柜一直被占用
- 其他人不能使用
- 浪费资源
正确的方式(GoodResourceExample):
// 相当于:
fun 使用储物柜() {
DisposableEffect(Unit) {
// 租储物柜
val 储物柜 = 健身房.租储物柜()
储物柜.存放物品()
// ✅ 离开时一定会执行清理工作
onDispose {
// 记得归还钥匙
储物柜.取回物品()
储物柜.归还()
}
}
}
在实际代码中:
locationManager.requestLocationUpdates()
就像租用储物柜removeUpdates()
就像归还钥匙DisposableEffect
确保我们不会忘记做清理工作
这样可以:
- 防止资源泄露
- 让系统资源能被其他人使用
- 保持程序运行效率
简单来说:
- LaunchedEffect 适合做一次性的事情
- DisposableEffect 适合处理需要"借用-归还"的资源
4. 优化状态更新
// ❌ 错误示例:频繁更新
@Composable
fun BadStateExample(searchQuery: String) {
LaunchedEffect(searchQuery) {
performSearch(searchQuery) // 每次输入都触发搜索
}
}
// ✅ 正确示例:使用防抖
@Composable
fun GoodStateExample(searchQuery: String) {
LaunchedEffect(Unit) {
snapshotFlow { searchQuery }
.debounce(300L)
.collect { query ->
performSearch(query)
}
}
}
错误的方式
想象你在餐厅点餐:
// 服务员的做法
fun 错误的点餐方式() {
// 客人每说一个字,就跑去厨房一次
LaunchedEffect(客人的话) {
// 客人:"我要一份..." 跑厨房一次
// 客人:"我要一份炒..." 又跑厨房一次
// 客人:"我要一份炒饭..." 再跑厨房一次
通知厨房(客人的话)
}
}
这样做的问题:
- 太频繁了
- 浪费资源
- 效率低下
正确的方式
聪明的服务员会这样做:
fun 正确的点餐方式() {
LaunchedEffect(Unit) {
// 等客人说完一句话才去厨房
snapshotFlow { 客人的话 }
.debounce(300L) // 等待300毫秒,确保客人说完
.collect { 完整的订单 ->
// 客人说完"我要一份炒饭"后
// 才一次性去通知厨房
通知厨房(完整的订单)
}
}
}
在搜索场景中:
- 错误方式:用户每输入一个字母都发起搜索请求
- 正确方式:等用户输入暂停300毫秒后,才发起搜索请求
这样可以:
- 减少不必要的请求
- 提高性能
- 提供更好的用户体验
就像等客人把话说完再行动,而不是听到一个字就跑一次。
防抖动的解释
让我用生活例子解释"防抖"(debounce):
1. 不使用防抖的情况
想象你在电梯里:
// ❌ 没有防抖:每次按键都立即响应
fun 电梯按键() {
当按下时 {
立即发送信号() // 每次按都会触发
}
}
- 你快速按了5次"关门"按钮
- 电梯会响应5次
- 很浪费,其实一次就够了
2. 使用防抖后
// ✅ 使用防抖:等待一小段时间,确认没有新的按键才响应
fun 智能电梯按键() {
当按下时 {
等待300毫秒() // 如果300毫秒内又有人按,就重新计时
如果没人再按了 {
发送信号() // 只响应最后一次按键
}
}
}
- 你快速按了5次"关门"按钮
- 电梯会等你按完
- 只响应最后一次按键
- 更智能,更节省资源
在搜索场景中:
// 使用防抖的搜索框
LaunchedEffect(Unit) {
snapshotFlow { searchQuery }
.debounce(300L) // 等待300毫秒
.collect { query -> // 如果300毫秒内没有新输入,才搜索
performSearch(query)
}
}
比如用户输入"android":
- 输入"a" -> 等待
- 输入"an" -> 重新等待
- 输入"and" -> 重新等待
- 输入"andr" -> 重新等待
- 输入"andro" -> 重新等待
- 输入"androi" -> 重新等待
- 输入"android" -> 等待300ms后,执行搜索
这样可以:
- 减少不必要的搜索请求
- 提高应用性能
- 提供更好的用户体验
就像智能电梯一样,等你真正按完了才动作,而不是每按一次就反应一次。
5. 协程作用域管理
// ❌ 错误示例:作用域管理不当
@Composable
fun BadScopeExample() {
val scope = CoroutineScope(Dispatchers.Main) // 错误:生命周期不跟随组件
Button(onClick = {
scope.launch {
doSomething()
}
}) { /*...*/ }
}
// ✅ 正确示例:使用 rememberCoroutineScope
@Composable
fun GoodScopeExample() {
val scope = rememberCoroutineScope() // 正确:生命周期跟随组件
Button(onClick = {
scope.launch {
doSomething()
}
}) { /*...*/ }
}
错误的方式
想象你请了一个临时工:
// ❌ 错误方式
fun 临时工例子() {
val 临时工 = 随便找个人() // 这个人跟你的店没关系
当顾客来时 {
让临时工去干活 {
处理顾客需求()
}
}
}
问题在于:
- 这个临时工不属于你的店
- 就算店关门了,他还在那干活
- 浪费资源,可能造成混乱
正确的方式
雇佣正式员工:
// ✅ 正确方式
fun 正式员工例子() {
val 正式员工 = 雇佣店员() // 这个人跟随店铺的营业时间
当顾客来时 {
让正式员工去干活 {
处理顾客需求()
}
}
}
好处是:
- 正式员工跟随店铺营业时间
- 店关门时,员工也下班了
- 资源管理更合理
在代码中:
CoroutineScope(Dispatchers.Main)
就像临时工,不会跟随组件生命周期rememberCoroutineScope()
就像正式员工,会跟随组件的创建和销毁
简单说:
- 使用
rememberCoroutineScope
能确保:- 组件在用时,协程在运行
- 组件销毁时,协程也会停止
- 不会造成资源浪费和内存泄露
就像雇佣正式员工比随便找临时工更可靠一样。
6. 组合多个 Effect
// ❌ 错误示例:Effect 分散
@Composable
fun BadMultipleEffects(id: String) {
LaunchedEffect(id) { loadData() }
LaunchedEffect(id) { trackAnalytics() }
LaunchedEffect(id) { updateUI() }
}
// ✅ 正确示例:合并相关 Effect
@Composable
fun GoodMultipleEffects(id: String) {
LaunchedEffect(id) {
// 并行执行多个任务
coroutineScope {
launch { loadData() }
launch { trackAnalytics() }
launch { updateUI() }
}
}
}
7. 错误处理
// ✅ 推荐示例:添加错误处理
@Composable
fun ErrorHandlingExample() {
LaunchedEffect(Unit) {
try {
// 执行可能失败的操作
riskyOperation()
} catch (e: Exception) {
// 错误处理
handleError(e)
} finally {
// 清理工作
cleanup()
}
}
}
8. 性能优化建议
flowchart TD
A[Effect 优化] --> B[减少重组]
A --> C[资源管理]
A --> D[状态更新]
B --> B1[使用稳定的 key]
B --> B2[合并 Effect]
C --> C1[及时清理]
C --> C2[使用 DisposableEffect]
D --> D1[防抖/节流]
D --> D2[批量更新]
9. 实际应用示例
@Composable
fun OptimizedFeature() {
var data by remember { mutableStateOf<Data?>(null) }
val scope = rememberCoroutineScope()
// 1. 数据加载
LaunchedEffect(Unit) {
try {
data = loadData()
} catch (e: Exception) {
handleError(e)
}
}
// 2. 资源管理
DisposableEffect(Unit) {
val listener = createListener()
onDispose { listener.release() }
}
// 3. 状态更新
val filteredData by remember(data) {
derivedStateOf {
data?.filter { it.isValid }
}
}
// 4. 用户操作
Button(
onClick = {
scope.launch {
updateData()
}
}
) {
Text("Update")
}
}
关键优化点总结:
- 使用稳定的 key 避免不必要的重组
- 合理合并和拆分 Effect
- 正确管理资源和清理工作
- 优化状态更新频率
- 使用正确的协程作用域
- 添加适当的错误处理
- 注意性能影响
这些优化可以帮助:
- 提高应用性能
- 减少资源消耗
- 提升代码可维护性
- 避免常见问题