Compose原理五之副作用

60 阅读11分钟

一、什么是副作用(Side Effects)

1、1 问题背景

在 Compose 中,Composable函数应该是纯函数——相同输入产生相同输出,不应该有副作用。但实际开发中,我们经常需要:

  • 🌐 网络请求:在界面显示时加载数据
  • 📊 事件订阅:监听传感器、数据库变化
  • 定时任务:启动倒计时、轮询
  • 🗑️ 资源清理:取消订阅、释放资源

这些操作都属于副作用(Side Effects)——它们会影响外部状态或系统资源。

1、2 为什么需要 Side Effects API?

// ❌ 错误示例:直接在 Composable 中执行副作用
@Composable
fun BadExample() {
    // 问题1:每次重组都会启动新协程,导致协程泄漏
    CoroutineScope(Dispatchers.IO).launch {
        loadData()
    }
    
    // 问题2:没有清理机制,离开组合后订阅依然存在
    sensorManager.registerListener(...)
}

Compose 的重组特性导致以上代码存在严重问题:

  1. 重组频繁:可能每帧都重组,副作用会重复执行
  2. 生命周期不确定:可能随时从组合中移除
  3. 资源泄漏:没有清理机制导致内存泄漏

副作用解决了这些问题

  • ✅ 提供生命周期管理
  • ✅ 自动清理资源
  • ✅ 控制副作用执行时机
  • ✅ 避免重复执行

副作用API包括LaunchedEffectDisposableEffectrememberCoroutineScoperememberUpdatedStateSideEffect

二、核心概念:RememberObserver

除了 SideEffect,其它的都基于 RememberObserver 接口实现。

2、1 RememberObserver 接口

// 位置:androidx.compose.runtime.RememberObserver
interface RememberObserver {
    /**
     * 当对象成功进入组合时调用(进入组合)
     */
    fun onRemembered()
    
    /**
     * 当对象离开组合时调用
     */
    fun onForgotten()
    
    /**
     * 当对象创建后但组合被放弃时调用
     */
    fun onAbandoned()
}

三、LaunchedEffect

3、1 为什么不能用 remember + launch?

用kotlin开发,就会用协程发起网络请求,直接在Compose函数中使用协程发起网络请求,下面的代码存在两个问题:

  • 问题1:userId 变化时不会取消旧协程。
  • 问题2:离开组合时协程不会被取消
// ❌ 错误做法
@Composable
fun BadExample(userId: String) {
    val scope = remember { CoroutineScope(Dispatchers.IO) }
    
    // 问题1:userId 变化时不会取消旧协程
    scope.launch {
        loadData(userId)
    }
    
    // 问题2:离开组合时协程不会被取消
}

正确的做法是使用LaunchedEffect

// ✅ 正确做法
@Composable
fun GoodExample(userId: String) {
    LaunchedEffect(userId) {
        // ✅ userId 变化自动取消旧协程
        // ✅ 离开组合自动取消
        loadData(userId)
    }
}

LaunchedEffect在首次组合会创建协程,重组的时候,如果key没有发生变化,就不会创建协程。像初始化等只需执行一次的代码,就需要使用LaunchedEffect

3、2 LaunchedEffect原理

看到了熟悉的remember函数,之前文章中介绍过remember函数的原理。

public fun LaunchedEffect(key1: Any?, block: suspend CoroutineScope.() -> Unit) {
    val applyContext = currentComposer.applyCoroutineContext
    // 调用remember,创建LaunchedEffectImpl
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

3、3 LaunchedEffectImpl

LaunchedEffectImpl继承RememberObserver,创建了协程,首次组合,开启协程;离开组合,取消协程。

internal class LaunchedEffectImpl(
    private val parentCoroutineContext: CoroutineContext,
    private val task: suspend CoroutineScope.() -> Unit,
) : RememberObserver, CoroutineExceptionHandler {
    // 创建协程
    private val scope =
        CoroutineScope(
            parentCoroutineContext +
                if (parentCoroutineContext[CompositionErrorContextImpl] != null) {
                    this
                } else {
                    EmptyCoroutineContext
                }
        )
    private var job: Job? = null

    /**
     * 首次组合开启协程
     */
    override fun onRemembered() {
        // This should never happen but is left here for safety
        job?.cancel("Old job was still running!")
        job = scope.launch(block = task)
    }

    /**
     * 组件从树中移除,取消协程
     */
    override fun onForgotten() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }

    /**
     * 被抛弃后取消协程
     */
    override fun onAbandoned() {
        job?.cancel(LeftCompositionCancellationException())
        job = null
    }

    // CoroutineExceptionHandler implementation to save on allocations
    override val key: CoroutineContext.Key<*>
        get() = CoroutineExceptionHandler.Key

    override fun handleException(context: CoroutineContext, exception: Throwable) {
        context[CompositionErrorContextImpl]?.apply {
            exception.attachComposeStackTrace(this@LaunchedEffectImpl)
        }
        parentCoroutineContext[CoroutineExceptionHandler]?.handleException(context, exception)
            ?: throw exception
    }
}

LaunchedEffectImpl什么时候被创建?答案就在remember函数

3、4 remember

如果是首次组合或者没有发生改变,就创建LaunchedEffectImpl对象,调用updateRememberedValueLaunchedEffectImpl对象保存到slots数组。updateRememberedValue调用了updateCachedValue

@Composable
public inline fun <T> remember(
    key1: Any?,
    crossinline calculation: @DisallowComposableCalls () -> T,
): T {
    // currentComposer.changed(key1) key是否发生改变
    return currentComposer.cache(currentComposer.changed(key1), calculation)
}

public inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
    @Suppress("UNCHECKED_CAST")
    return rememberedValue().let {
        if (invalid || it === Composer.Empty) {
            val value = block()
            updateRememberedValue(value)
            value
        } else it
    } as T
}

3、5 updateCachedValue

changeListWriter.remember会调用到pushRemember

// Composer.kt
internal fun updateCachedValue(value: Any?) {
    val toStore = if (value is RememberObserver) {
        // 首次组合,添加到changeList
        if (inserting) { changeListWriter.remember(value) }
        // 添加到set
        abandonSet.add(value)
        // 包装下`LaunchedEffectImpl`对象
        RememberObserverHolder(value, rememberObserverAnchor())
    } else value
    // 保存到slots数组
    updateValue(toStore)
}

3、6 pushRemember

添加到operationsRemember里面,在Compose原理三之SlotTable中介绍过operations

fun pushRemember(value: RememberObserver) {
    operations.push(Remember) {
        setObject(Remember.Value, value)
    }
}

最终LaunchedEffectImpl对象被保存到了slots数组和operations里面。

3、7 什么时候调用RememberObserveronRemembered

在应用变更阶段,所有变更应用完成后立即调用 当组合完成后,就需要应用变更,应用变更在Compose原理三之SlotTable中介绍过。

// Composition.kt
private fun applyChangesInLocked(changes: ChangeList) {
    rememberManager.prepare(abandonSet, composer.errorContext)
    try {
        // 第一步:应用所有变更
        trace("Compose:applyChanges") {
            val applier = pendingPausedComposition?.pausableApplier ?: applier
            val rememberManager = pendingPausedComposition?.rememberManager ?: rememberManager
            applier.onBeginChanges()

            // 应用变更
            slotTable.write { slots ->
                changes.executeAndFlushAllPendingChanges(
                    applier, slots, rememberManager, composer.errorContext,
                )
            }
            applier.onEndChanges()
        }

        // 第二步:派发 onRemembered 回调
        // Side effects run after lifecycle observers so that any remembered objects
        // that implement RememberObserver receive onRemembered before a side effect
        // that captured it and operates on it can run.
        rememberManager.dispatchRememberObservers()  // ⬅️ 这里调用 onRemembered
        
        // 第三步:执行 SideEffect
        rememberManager.dispatchSideEffects()
    } finally {
        // 第四步:派发 onAbandoned
        if (this.lateChanges.isEmpty() && pendingPausedComposition == null) {
            rememberManager.dispatchAbandons()
        }
        rememberManager.clear()
    }
}

具体派发逻辑RememberEventDispatcher.kt中:

// RememberEventDispatcher.kt
fun dispatchRememberObservers() {
    val abandoning = abandoning ?: return
    ignoreLeavingSet = null
    
    // 1️⃣ 先派发 onForgotten(倒序)
    if (leaving.isNotEmpty()) {
        trace("Compose:onForgotten") {
            val releasing = releasing
            for (i in leaving.size - 1 downTo 0) {  // ⬅️ 倒序遍历
                val instance = leaving[i]
                withComposeStackTrace(instance) {
                    if (instance is RememberObserverHolder) {
                        val wrapped = instance.wrapped
                        abandoning.remove(wrapped)  // 从 abandon 集合移除
                        wrapped.onForgotten()       // ⬅️ 调用 onForgotten
                    }
                    if (instance is ComposeNodeLifecycleCallback) {
                        // node callbacks are in the same queue as forgets to ensure ordering
                        if (releasing != null && instance in releasing) {
                            instance.onRelease()
                        } else {
                            instance.onDeactivate()
                        }
                    }
                }
            }
        }
    }

    // 2️⃣ 再派发 onRemembered(正序)
    if (remembering.isNotEmpty()) {
        trace("Compose:onRemembered") { 
            dispatchRememberList(remembering) 
        }
    }
}

private fun dispatchRememberList(list: MutableVector<RememberObserverHolder>) {
    val abandoning = abandoning ?: return
    list.forEach { instance ->
        val wrapped = instance.wrapped
        abandoning.remove(wrapped)          // 从 abandon 集合移除
        withComposeStackTrace(instance) { 
            wrapped.onRemembered()          // ⬅️ 调用 onRemembered
        }
    }
}

对象如何被添加到remembering列表?Operation.kt中定义了多种操作:

// Operation.kt - Remember 操作
object Remember : Operation(objects = 1) {
    inline val Value
        get() = ObjectParameter<RememberObserverHolder>(0)

    override fun OperationArgContainer.execute(
        applier: Applier<*>,
        slots: SlotWriter,
        rememberManager: RememberManager,
        errorContext: OperationErrorContext?,
    ) {
        rememberManager.remembering(getObject(Value))  // ⬅️ 记录需要派发 onRemembered
    }
}

// Operation.kt - AppendValue 操作
object AppendValue : Operation(objects = 2) {
    override fun OperationArgContainer.execute(
        applier: Applier<*>,
        slots: SlotWriter,
        rememberManager: RememberManager,
        errorContext: OperationErrorContext?,
    ) {
        val anchor = getObject(Anchor)
        val value = getObject(Value)
        if (value is RememberObserverHolder) {
            rememberManager.remembering(value)  // ⬅️ 记录需要派发 onRemembered
        }
        slots.appendSlot(anchor, value)
    }
}

3、8 什么时候调用RememberObserveronForgotten

当对象从组合中移除时调用,发生在应用变更阶段派发 onRemembered 之前。

3、8、1 组件重组时旧值被替换执行onForgotten

Operation.kt中的 UpdateValue 操作:

// Operation.kt - UpdateValue 操作
object UpdateValue : Operation(ints = 1, objects = 1) {
    inline val Value
        get() = ObjectParameter<Any?>(0)

    inline val GroupSlotIndex
        get() = 0

    override fun OperationArgContainer.execute(
        applier: Applier<*>,
        slots: SlotWriter,
        rememberManager: RememberManager,
        errorContext: OperationErrorContext?,
    ) {
        val value = getObject(Value)
        val groupSlotIndex = getInt(GroupSlotIndex)
        if (value is RememberObserverHolder) {
            rememberManager.remembering(value)  // 新值加入 remembering
        }
        when (val previous = slots.set(groupSlotIndex, value)) {
            is RememberObserverHolder -> {
                rememberManager.forgetting(previous)  // ⬅️ 旧值加入 leaving
            }
            is RecomposeScopeImpl -> previous.release()
        }
    }
}

3、8、2 组件从树中移除执行onForgotten

// Composer.kt
fun SlotWriter.removeCurrentGroup(rememberManager: RememberManager) {
    // To ensure this order, we call `enters` as a pre-order traversal
    // of the group tree, and then call `leaves` in the inverse order.

    forAllDataInRememberOrder(currentGroup) { _, slot ->
        // even that in the documentation we claim ComposeNodeLifecycleCallback should be only
        // implemented on the nodes we do not really enforce it here as doing so will be expensive.
        if (slot is ComposeNodeLifecycleCallback) {
            rememberManager.releasing(slot)
        }
        if (slot is RememberObserverHolder) {
            rememberManager.forgetting(slot)  // ⬅️ 加入 leaving 列表
        }
        if (slot is RecomposeScopeImpl) {
            slot.release()
        }
    }

    removeGroup()
}

3、8、3 销毁组件树执行onForgotten

// Composition.kt
override fun dispose() {
    synchronized(lock) {
        if (!disposed) {
            disposed = true
            composable = {}
            // composition because this composition is being discarded. It is important that
            // this is done after applying deferred changes above to avoid sending `
            // onForgotten` notification to objects that are still part of movable content that
            // will be moved to a new location.
            val nonEmptySlotTable = slotTable.groupsSize > 0
            if (nonEmptySlotTable || abandonSet.isNotEmpty()) {
                rememberManager.use(abandonSet, composer.errorContext) {
                    if (nonEmptySlotTable) {
                        applier.onBeginChanges()
                        // 移除整个 SlotTable,触发所有对象的 forgetting
                        slotTable.write { writer -> 
                            writer.removeCurrentGroup(rememberManager) 
                        }
                        applier.clear()
                        applier.onEndChanges()
                        dispatchRememberObservers()  // ⬅️ 派发 onForgotten
                    }
                    dispatchAbandons()
                }
            }
            composer.dispose()
        }
    }
    parent.unregisterComposition(this)
}

3、8、4 组件树停用执行onForgotten

// Composition.kt
override fun deactivate() {
    synchronized(lock) {
        checkPrecondition(pendingPausedComposition == null) {
            "Deactivate is not supported while pausable composition is in progress"
        }
        val nonEmptySlotTable = slotTable.groupsSize > 0
        if (nonEmptySlotTable || abandonSet.isNotEmpty()) {
            trace("Compose:deactivate") {
                rememberManager.use(abandonSet, composer.errorContext) {
                    if (nonEmptySlotTable) {
                        applier.onBeginChanges()
                        slotTable.write { writer ->
                            writer.deactivateCurrentGroup(rememberManager)  // ⬅️ 停用处理
                        }
                        applier.onEndChanges()
                        dispatchRememberObservers()  // ⬅️ 派发 onForgotten
                    }
                    dispatchAbandons()
                }
            }
        }
        // ... 清理资源
    }
}

onForgotten采用倒序派发,与onRemembered顺序相反。

// RememberEventDispatcher.kt
// onForgotten 采用倒序派发,与 onRemembered 顺序相反
if (leaving.isNotEmpty()) {
    trace("Compose:onForgotten") {
        for (i in leaving.size - 1 downTo 0) {  // ⬅️ 倒序遍历
            val instance = leaving[i]
            if (instance is RememberObserverHolder) {
                wrapped.onForgotten()
            }
        }
    }
}

3、9 onAbandoned() - 什么时候调用?

第一步:记录到 abandonSet

Composer.kt中的 updateCachedValue() 方法:

// Composer.kt
@PublishedApi
@OptIn(InternalComposeApi::class)
internal fun updateCachedValue(value: Any?) {
    val toStore =
        if (value is RememberObserver) {
            val holder = RememberObserverHolder(value, rememberObserverAnchor())
            if (inserting) {
                changeListWriter.remember(holder)
            }
            abandonSet.add(value)  // ⬅️ 所有 RememberObserver 先加入 abandonSet
            holder
        } else value
    updateValue(toStore)
}

第二步:成功时从abandonSet移除

// RememberEventDispatcher.kt
private fun dispatchRememberList(list: MutableVector<RememberObserverHolder>) {
    val abandoning = abandoning ?: return
    list.forEach { instance ->
        val wrapped = instance.wrapped
        abandoning.remove(wrapped)  // ⬅️ 成功 remembered 时从 abandonSet 移除
        withComposeStackTrace(instance) { 
            wrapped.onRemembered() 
        }
    }
}

第三步:组合失败时派发onAbandoned

// RememberEventDispatcher.kt
fun dispatchAbandons() {
    val abandoning = abandoning ?: return
    if (abandoning.isNotEmpty()) {
        trace("Compose:abandons") {
            val iterator = abandoning.iterator()
            // remove elements one by one to ensure that abandons will not be dispatched
            // second time in case [onAbandoned] throws.
            while (iterator.hasNext()) {
                val instance = iterator.next()
                iterator.remove()
                instance.onAbandoned()  // ⬅️ 对 abandonSet 中剩余的对象调用 onAbandoned
            }
        }
    }
}

3、9、1 组合过程抛出异常触发onAbandoned

// Composition.kt
private inline fun <T> trackAbandonedValues(block: () -> T): T {
    var success = false
    return try {
        block().also { success = true }
    } finally {
        if (!success && abandonSet.isNotEmpty()) {
            // 组合失败,派发 onAbandoned
            rememberManager.use(abandonSet, composer.errorContext) { 
                dispatchAbandons() 
            }
        }
    }
}

3、9、2 主动放弃组合变更触发onAbandoned

// Composition.kt
override fun abandonChanges() {
    pendingModifications.set(null)
    changes.clear()
    lateChanges.clear()

    if (abandonSet.isNotEmpty()) {
        rememberManager.use(abandonSet, composer.errorContext) { 
            dispatchAbandons()  // ⬅️ 派发 onAbandoned
        }
    }
}

四、DisposableEffect

DisposableEffect使用场景

4、1 监听器注册/注销,使用DisposableEffect

@Composable
fun LocationUpdates() {
    val context = LocalContext.current
    val locationManager = remember { 
        context.getSystemService(Context.LOCATION_SERVICE) as LocationManager 
    }
    
    DisposableEffect(Unit) {
        val listener = object : LocationListener {
            override fun onLocationChanged(location: Location) {
                // 处理位置更新
            }
        }
        
        // 注册监听器
        locationManager.requestLocationUpdates(
            LocationManager.GPS_PROVIDER,
            1000L, 10f, listener
        )
        
        // 返回清理回调
        onDispose {
            // 取消注册监听器
            locationManager.removeUpdates(listener)
        }
    }
}

4、2 生命周期观察者,使用DisposableEffect

@Composable
fun LifecycleAwareComponent() {
    val lifecycle = LocalLifecycleOwner.current.lifecycle
    
    DisposableEffect(lifecycle) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_START -> println("Started")
                Lifecycle.Event.ON_STOP -> println("Stopped")
                else -> {}
            }
        }
        
        lifecycle.addObserver(observer)
        
        onDispose {
            lifecycle.removeObserver(observer)
        }
    }
}

4、2 资源管理,使用DisposableEffect

@Composable
fun MediaPlayerComponent(videoUrl: String) {
    DisposableEffect(videoUrl) {
        val player = MediaPlayer().apply {
            setDataSource(videoUrl)
            prepare()
            start()
        }
        
        onDispose {
            player.stop()
            player.release()
        }
    }
}

4、3 DisposableEffect原理

DisposableEffect继承RememberObserver,进入组合时执行effect,获取清理回调,离开组合时执行清理。

// Effects.kt
@Composable
@NonRestartableComposable
public fun DisposableEffect(
    key1: Any?,
    effect: DisposableEffectScope.() -> DisposableEffectResult,
) {
    remember(key1) { DisposableEffectImpl(effect) }
}

private class DisposableEffectImpl(
    private val effect: DisposableEffectScope.() -> DisposableEffectResult
) : RememberObserver {
    private var onDispose: DisposableEffectResult? = null

    override fun onRemembered() {
        // 进入组合时执行 effect,获取清理回调
        onDispose = InternalDisposableEffectScope.effect()
    }

    override fun onForgotten() {
        // 离开组合时执行清理
        onDispose?.dispose()
        onDispose = null
    }

    override fun onAbandoned() {
        // 组合废弃时不需要清理(onRemembered 未被调用)
    }
}

public class DisposableEffectScope {
    public inline fun onDispose(
        crossinline onDisposeEffect: () -> Unit
    ): DisposableEffectResult =
        object : DisposableEffectResult {
            override fun dispose() {
                onDisposeEffect()
            }
        }
}

五、rememberCoroutineScope

rememberCoroutineScope会创建协程,是作为LaunchedEffect的补充,LaunchedEffect只能在Compose函数中调用,如果想在点击事件中开启协程,那就不能使用LaunchedEffect,因为点击事件回调不在Compose作用域内。

// ❎错误用法
@Composable
fun SearchBar() {
    Button(onClick = {
        // 点击事件回调不在Compose作用域内,不能使用LaunchedEffect
        LaunchedEffect(Unit) {
        
        }
   }
}

// ✅正确用法
@Composable
fun SearchBar() {
    val scope = rememberCoroutineScope()
    
    
    Button(onClick = {
        scope.launch {
            // 开启协程执行相关操作
        }
   }
}

原理很简单,RememberedCoroutineScope继承CoroutineScope,离开组合时取消所有协程。

// Effects.kt
@Composable
public inline fun rememberCoroutineScope(
    crossinline getContext: @DisallowComposableCalls () -> CoroutineContext = {
        EmptyCoroutineContext
    }
): CoroutineScope {
    val composer = currentComposer
    return remember { createCompositionCoroutineScope(getContext(), composer) }
}

internal class RememberedCoroutineScope(
    private val parentContext: CoroutineContext,
    private val overlayContext: CoroutineContext,
) : CoroutineScope, RememberObserver {
    
    @Volatile 
    private var _coroutineContext: CoroutineContext? = null

    override val coroutineContext: CoroutineContext
        get() {
            // 懒加载:首次访问时才创建
            var localCoroutineContext = _coroutineContext
            if (localCoroutineContext == null) {
                synchronized(lock) {
                    localCoroutineContext = _coroutineContext
                    if (localCoroutineContext == null) {
                        val childJob = Job(parentContext[Job])
                        localCoroutineContext = parentContext + childJob + overlayContext
                    }
                    _coroutineContext = localCoroutineContext
                }
            }
            return localCoroutineContext!!
        }

    override fun onForgotten() {
        cancelIfCreated()  // 离开组合时取消所有协程
    }
}

六、rememberUpdatedState

6、1 来个例子

下面的代码为什么总是打印 "First"?首次组合的时候,调用CountdownWithCallback,传递onFinishLaunchedEffect调用onFinish。重组的时候,调用CountdownWithCallback,传递新的onFinish,由于LaunchedEffect的key没有发生改变,LaunchedEffect只执行一次,调用的还是第一次传递过来的onFinish,总是打印 "First"。

// ❎错误用法
@Composable
fun CountdownWithCallback(onFinish: () -> Unit) {
    LaunchedEffect(Unit) {  // 只启动一次
        delay(5000)
        onFinish()  // ← 捕获的是首次组合时的 onFinish
    }
}

@Composable
fun Parent() {
    var message by remember { mutableStateOf("First") }
    
    CountdownWithCallback(
        onFinish = { println(message) }  // 总是打印 "First"
    )
    
    Button(onClick = { message = "Second" }) {
        Text("Change Message")
    }
}

使用rememberUpdatedState就能解决问题。

6、2 rememberUpdatedState原理

remember { mutableStateOf(newValue) } - 首次创建状态对象

.apply { value = newValue } - 每次重组更新值。

rememberUpdatedState原理超级简单,每次重组的时候,更新新值。

// SnapshotState.kt
@Composable
public fun <T> rememberUpdatedState(newValue: T): State<T> =
    remember { mutableStateOf(newValue) }.apply { value = newValue }

首次组合的时候,调用CountdownWithCallback,传递onFinishLaunchedEffect调用onFinish。重组的时候,调用CountdownWithCallback,传递新的onFinish。虽然LaunchedEffect的key没有发生改变,LaunchedEffect只执行一次,但是rememberUpdatedState把新的onFinish赋值给了currentOnFinish,每次打印的时候,都是调用最新的回调。

// ✅正确用法
@Composable
fun CountdownWithCallback(onFinish: () -> Unit) {
    val currentOnFinish by rememberUpdatedState(onFinish)
    
    LaunchedEffect(Unit) {
        delay(5000)
        currentOnFinish()  // ← 调用最新的回调
    }
}

@Composable
fun Parent() {
    var message by remember { mutableStateOf("First") }
    
    CountdownWithCallback(
        onFinish = { println(message) } 
    )
    
    Button(onClick = { message = "Second" }) {
        Text("Change Message")
    }
}

七、SideEffect

onRemembered执行完成后,就会执行SideEffect

// Composition.kt
private fun applyChangesInLocked(changes: ChangeList) {
    rememberManager.prepare(abandonSet, composer.errorContext)
    try {
        // 第一步:应用所有变更
        trace("Compose:applyChanges") {
            val applier = pendingPausedComposition?.pausableApplier ?: applier
            val rememberManager = pendingPausedComposition?.rememberManager ?: rememberManager
            applier.onBeginChanges()

            // 应用变更
            slotTable.write { slots ->
                changes.executeAndFlushAllPendingChanges(
                    applier, slots, rememberManager, composer.errorContext,
                )
            }
            applier.onEndChanges()
        }

        // 第二步:派发 onRemembered 回调
        // Side effects run after lifecycle observers so that any remembered objects
        // that implement RememberObserver receive onRemembered before a side effect
        // that captured it and operates on it can run.
        rememberManager.dispatchRememberObservers()  // ⬅️ 这里调用 onRemembered
        
        // 第三步:执行 SideEffect
        rememberManager.dispatchSideEffects()
    } finally {
        // 第四步:派发 onAbandoned
        if (this.lateChanges.isEmpty() && pendingPausedComposition == null) {
            rememberManager.dispatchAbandons()
        }
        rememberManager.clear()
    }
}

// 执行 SideEffect
fun dispatchSideEffects() {
    if (sideEffects.isNotEmpty()) {
        trace("Compose:sideeffects") {
            sideEffects.forEach { sideEffect -> sideEffect() }
            sideEffects.clear()
        }
    }
}

7、1 使用场景

场景1:同步状态到非Compose对象

@Composable
fun SyncToAnalytics(userId: String) {
    // 每次 userId 变化都会执行
    SideEffect {
        // 同步到 Analytics SDK(非 Compose 管理)
        Analytics.setUserId(userId)
    }
}

场景2:触发外部API

@Composable
fun UpdateCanvas(color: Color) {
    val canvas = remember { CustomCanvas() }
    
    // 每次颜色变化都重绘
    SideEffect {
        canvas.setBackgroundColor(color)
    }
}

八、总结

  • 副作用是在应用变更后执行
  • LaunchedEffect在首次组合应用变更的时候会创建协程,重组的时候,如果key没有发生变化,就不会创建协程。像初始化等只需执行一次的代码,就需要使用LaunchedEffect
  • DisposableEffect在组件离开组件树时执行清理。
  • rememberCoroutineScope可以在Compose函数作用域之外创建协程
  • rememberUpdatedState原理每次重组的时候,更新新值。
  • SideEffect可以同步状态到非Compose对象,可以触发外部API