使用 RememberObserver 和 RetainObserver 组合状态回调

在 Jetpack Compose 中,对象可以实现 RememberObserver,以便在与 remember 搭配使用时接收回调,从而了解何时开始和停止在组合层次结构中被记住。同样,您可以使用 RetainObserver 接收有关与 retain 搭配使用的对象的状态信息。

对于使用组合层次结构中的生命周期信息的对象,我们建议您遵循一些最佳实践,以验证您的对象在平台中是否表现良好,并防范滥用行为。具体而言,请使用 onRemembered(或 onRetained)回调(而不是构造函数)来启动工作,在对象不再被记住或保留时取消所有工作,并避免泄露 RememberObserver 和 RetainObserver 的实现,以避免意外调用。下一部分将更深入地介绍这些建议。

使用 RememberObserver 和 RetainObserver 进行初始化和清理

Thinking in Compose 指南介绍了组合背后的构思模型。使用 RememberObserver 和 RetainObserver 时,请务必注意组合的以下两种行为:

  • 重组是乐观的操作,可能会被取消
  • 所有可组合函数都不应有附带效应

在 onRemembered 或 onRetained 期间(而非构造期间)运行初始化副作用

当对象被记住或保留时,计算 lambda 会作为组合的一部分运行。出于与您不应在组合期间执行附带效应或启动协程相同的原因,您也不应在传递给 remember、retain 及其变体的计算 lambda 中执行附带效应。 这包括作为记住或保留对象的构造函数的一部分。

相反,在实现 RememberObserver 或 RetainObserver 时,请验证所有效果和启动的作业是否都在 onRemembered 回调中分派。这与 SideEffect API 的时间安排相同。它还可确保这些效应仅在应用组合时执行,从而在重组被放弃或推迟时防止孤立作业和内存泄漏。

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    init {
        // Not recommended: This will cause work to begin during composition instead of
        // with other effects. Move this into onRemembered().
        coroutineScope.launch { loadData() }
    }

    override fun onRemembered() {
        // Recommended: Move any cancellable or effect-driven work into the onRemembered
        // callback. If implementing RetainObserver, this should go in onRetained.
        coroutineScope.launch { loadData() }
    }

    private suspend fun loadData() { /* ... */ }

    // ...
}

在被遗忘、停用或废弃时拆除

为避免资源泄露或后台作业孤立,还必须处置记住的对象。对于实现 RememberObserver 的对象,这意味着在 onRemembered 中初始化的任何内容都必须在 onForgotten 中具有匹配的释放调用。

由于组合可以取消,因此实现 RememberObserver 的对象在组合中被放弃后,也必须自行清理。如果对象由 remember 在被取消或失败的组合中返回,则该对象会被放弃。(这种情况最常发生在使用 PausableComposition 时,也可能发生在使用 Android Studio 的可组合项预览工具进行热重载时。)

当记住的对象被放弃时,它只会收到对 onAbandoned 的调用(而不会收到对 onRemembered 的调用)。如需实现 abandon 方法,请处置在对象初始化到对象本应收到 onRemembered 回调之间创建的所有内容。

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    // ...

    override fun onForgotten() {
        // Cancel work launched from onRemembered. If implementing RetainObserver, onRetired
        // should cancel work launched from onRetained.
        job.cancel()
    }

    override fun onAbandoned() {
        // If any work was launched by the constructor as part of remembering the object,
        // you must cancel that work in this callback. For work done as part of the construction
        // during retain, this code should will appear in onUnused.
        job.cancel()
    }
}

将 RememberObserver 和 RetainObserver 实现设为私有

编写公共 API 时,请谨慎扩展 RememberObserver 和 RetainObserver,以创建公开返回的类。用户可能不会在您期望的时间记住您的对象,或者可能以不同于您预期的方式记住您的对象。因此,我们建议不要为实现 RememberObserver 或 RetainObserver 的对象公开构造函数或工厂函数。请注意,这取决于类的运行时类型,而不是声明的类型。记住实现 RememberObserver 或 RetainObserver 但被转换为 Any 的对象仍会导致该对象接收回调。

不建议:

abstract class MyManager

// Not Recommended: Exposing a public constructor (even implicitly) for an object implementing
// RememberObserver can cause unexpected invocations if it is remembered multiple times.
class MyComposeManager : MyManager(), RememberObserver { ... }

// Not Recommended: The return type may be an implementation of RememberObserver and should be
// remembered explicitly.
fun createFoo(): MyManager = MyComposeManager()

推荐:

abstract class MyManager

class MyComposeManager : MyManager() {
    // Callers that construct this object must manually call initialize and teardown
    fun initialize() { /*...*/ }
    fun teardown() { /*...*/ }
}

@Composable
fun rememberMyManager(): MyManager {
    // Protect the RememberObserver implementation by never exposing it outside the library
    return remember {
        object : RememberObserver {
            val manager = MyComposeManager()
            override fun onRemembered() = manager.initialize()
            override fun onForgotten() = manager.teardown()
            override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ }
        }
    }.manager
}

记住对象时的注意事项

除了前面有关 RememberObserver 和 RetainObserver 的建议之外,我们还建议您注意并避免意外地重新记住对象,以确保性能和正确性。以下部分将更深入地探讨特定的重新记住场景以及应避免这些场景的原因。

仅记住对象一次

重新记住某个对象可能会有危险。在最好的情况下,您可能只是在浪费资源来记住一个已经记住的值。但是,如果某个对象实现了 RememberObserver 且意外地被记住两次,则它收到的回调会比预期的多。这可能会导致问题,因为 onRemembered 和 onForgotten 逻辑将执行两次,而 RememberObserver 的大多数实现都不支持这种情况。如果第二次 调用发生在与 原始 remember 具有不同生命周期的不同作用域中,则 RememberObserver.onForgotten 的许多实现会在对象使用完毕之前将其处置。

val first: RememberObserver = rememberFoo()

// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }

此建议不适用于以传递方式再次记住的对象(例如,使用另一个记住的对象的记住的对象)。编写如下所示的代码很常见,这是允许的,因为系统会记住不同的对象,因此不会导致意外的回调加倍。

val foo: Foo = rememberFoo()

// Acceptable:
val bar: Bar = remember { Bar(foo) }

// Recommended key usage:
val barWithKey: Bar = remember(foo) { Bar(foo) }

假设函数参数已被记住

函数不应记住其任何参数,因为这可能会导致 RememberObserver 的双重回调调用,而且没有必要。如果必须记住输入参数,请验证该参数是否未实现 RememberObserver,或要求调用方记住其参数。

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Not Recommended: Input should be remembered by the caller.
    val rememberedParameter = remember { parameter }
}

这不适用于通过传递方式记住的对象。记住派生自函数实参的对象时,请考虑将其指定为 remember 的键之一:

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Acceptable:
    val derivedValue = remember { Bar(parameter) }

    // Also Acceptable:
    val derivedValueWithKey = remember(parameter) { Bar(parameter) }
}

不保留已记住的对象

与重新记住对象类似,您应避免保留记住的对象,以尝试延长其生命周期。这是状态生命周期中建议的结果:retain 不应与生命周期与 retain 提供的生命周期不匹配的对象一起使用。由于 remembered 对象的生命周期比 retained 对象短,因此您不应保留记住的对象。相反,建议您将对象保留在源网站,而不是记住它。