全新 LifecycleOwner 可组合项:让 Composable 具备独立生命周期

2025 年 7 月 30 日,AndroidX Lifecycle 2.10.0-alpha01 发布。其中最引人注目的新增内容就是全新的 LifecycleOwner 的 Composable (可组合项),咱们这就来好好聊聊它。

现有 LocalLifecycleOwner 能提供什么?

首先,得弄明白 Compose 从 LocalLifecycleOwner 中获取了什么价值。它是由 ProvideAndroidCompositionLocals 函数暴露出来的:

kotlin 复制代码
@Composable
@OptIn(ExperimentalComposeUiApi::class)
internal fun ProvideAndroidCompositionLocals(
    owner: AndroidComposeView,
    content: @Composable () -> Unit,
) {
    constant(@Composable { -> Unit },
    val viewTreeOwners = owner.viewTreeOwners
    val lifecycleOwner = viewTreeOwners?.lifecycleOwner
    owner.lifecycleOwner =
        lifecycleOwner ?: throw IllegalStateException(
            "Called when the ViewTreeLifecycleOwnerAvailability is not yet in Available stat"
        )
    // CompositionLocalProvider(
    //     LocalLifecycleOwner provides viewTreeOwners.lifecycleOwner,
    //     //......
    // ) {
    //     ProvideCommonCompositionLocals(/* 不重要 */)
    // }
}

这里,LocalLifecycleOwner 来自 viewTreeOwners.lifecycleOwner。要是你查看 viewTreeOwners,会发现它来自 View 上的 findViewTreeLifecycleOwner() 方法:

kotlin 复制代码
internal class AndroidComposeView(context: Context, coroutineContext: CoroutineContext) : ViewGroup(context) {
    //...

    @OptIn(ExperimentalComposeUiApi::class)
    @MainThread
    fun attachToWindow() {
        value lifecycleOwner = findViewTreeLifecycleOwner()
        if (lifecycleOwner == null) {
            if (Lifecycle.TRACE_ENABLED) {
                throw IllegalStateException(
                    "Composed into the view which doesn't propagate ViewTreeLifecycleOwne"
                )
            }
        }
        //...
        lifecycleOwner.lifecycle.addObserver(this)
        viewTreeOwners = ViewTreeOwners(
            lifecycleOwner = lifecycleOwner,
            savedStateRegistryOwner = savedStateRegistryOwner,
            isInBackStack = isInBackStack
        )
        _viewTreeOwners = viewTreeOwners
        //...
    }
}

这也就意味着,它获取的是由 Activity 或 Fragment 在 window.decorView 上设置的 LifecycleOwner。实际上,给定 Activity 中的所有可组合项都共享同一个 LifecycleOwner

kotlin 复制代码
/**
 * 在设置内容视图之前设置视图树所有者,以便填充过程中的监听器会看到它们已经存在。
 */
open fun initializeViewTreeOwners() {
    window.decorView.setViewTreeLifecycleOwner(this)
    window.decorView.setViewTreeViewModelStoreOwner(this)
    window.decorView.setViewTreeSavedStateRegistryOwner(this)
    window.decorView.setViewTreeBackPressedDispatcherOwner(this)
    window.decorView.setViewTreeFocusRequesterOwner(this)
    window.decorView.setViewTreeNavigationEventDispatcherOwner(this)
}

何时需要单独的 LifecycleOwner?

在整个 Activity 中共享 LifecycleOwner 可能会引发问题,尤其是在屏幕过渡期间。

Android 团队的一个 PR 中提到了这一点 (android-review.googlesource.com/c/platform/...)

SinglePaneNavDisplay 是 Android 导航架构中的一个组件,用于管理导航过程中屏幕的显示。在单窗格导航场景下,期望一次只显示一个处于 RESUMED 状态的屏幕,以实现简洁明了的导航体验。

然而,由于屏幕过渡(比如从一个 Fragment 切换到另一个 Fragment )是需要时间的,在这段过渡时间内,新旧屏幕会同时存在于视图层级中,并且都能接收到 Activity 的生命周期变化通知。如果按照共享 LifecycleOwner 的逻辑,就会出现新旧屏幕上的可组合项同时处于 RESUMED 状态的情况。例如,在一个应用中,从一个显示新闻列表的页面切换到显示新闻详情的页面,在过渡期间,新闻列表页面和新闻详情页面的相关组件都处于 RESUMED 状态,这可能导致资源过度占用,比如两个页面都在进行数据加载或者动画播放,造成 CPU 和内存资源的浪费。

为了解决这些问题,Android 团队对逻辑进行了改进,将相关逻辑提取为独立的 LifecycleOwner 可组合项。

如下,使用 NavLocalProvider 来安装一个 LocalLifecycleOwner,其最大状态由当前过渡和返回栈状态决定。

kt 复制代码
// 屏幕实体类
data class Screen(val id: String)

// 导航局部提供者,核心逻辑
@Composable
fun NavLocalProvider(
    backStackManager: BackStackManager,
    content: @Composable () -> Unit
) {
    // 获取当前屏幕
    val currentScreen = backStackManager.currentScreen
    // 判断当前屏幕是否在返回栈中
    val isInBackStack = currentScreen?.let { backStackManager.backStack.contains(it) } ?: false
    // 判断是否处于稳定状态(非过渡中)
    val isStable = !backStackManager.isTransitioning

    // 根据状态确定最大生命周期
    val maxLifecycle = when {
        // 在返回栈且稳定:最大生命周期为RESUMED
        isInBackStack && isStable -> Lifecycle.State.RESUMED
        // 在返回栈但过渡中:最大生命周期为STARTED
        isInBackStack -> Lifecycle.State.STARTED
        // 不在返回栈(弹出中):最大生命周期为CREATED
        else -> Lifecycle.State.CREATED
    }

    // 安装新的LocalLifecycleOwner,其最大状态由上面计算的maxLifecycle决定
    LifecycleOwner(maxLifecycle = maxLifecycle) {
        // 将新的LifecycleOwner提供给子组件
        CompositionLocalProvider(LocalLifecycleOwner provides LocalLifecycleOwner.current) {
            content()
        }
    }
}
  1. 状态判断:NavLocalProvider 会实时检查当前屏幕的两个关键状态:

    • 是否在返回栈中(isInBackStack)
    • 是否处于过渡动画中(isStable)
  2. 动态计算最大生命周期

    • 当屏幕完全显示且无过渡(稳定状态):使用 RESUMED 状态,允许正常交互
    • 当屏幕在返回栈中但处于过渡动画(如刚被推入 / 还在进入动画):使用 STARTED 状态,限制部分资源消耗
    • 当屏幕被弹出但还在退出动画中:使用 CREATED 状态,准备释放资源
  3. 局部生命周期注入:通过 LifecycleOwner 可组合项创建新的生命周期所有者,并通过 CompositionLocalProvider 注入到子组件中,使得当前屏幕的所有子可组合项都会使用这个动态调整的生命周期。

这个机制确保了在屏幕切换的整个过程中,每个屏幕的生命周期状态都能精确匹配其实际显示状态,避免了过渡期间的资源竞争和交互冲突。

因为独立 LifecycleOwner 的实用性超越了导航场景,所以它可用于为任何需要自身生命周期管理的组件(例如 MapView、视频播放器)划定生命周期作用域,且上限为特定的最大状态。

分析 LifecycleOwner 可组合项

kotlin 复制代码
@Composable
fun LifecycleOwner(
    maxLifecycle: State = RESUMED,
    parentLifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    content: @Composable () -> Unit,
) {
    val childLifecycleOwner = remember(parentLifecycleOwner) { ChildLifecycleOwner() }
    // 将生命周期事件从父级传递到子级。
    DisposableEffect(childLifecycleOwner, parentLifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            childLifecycleOwner.handleLifecycleEvent(event)
        }
        parentLifecycleOwner.lifecycle.addObserver(observer)
        onDispose { parentLifecycleOwner.lifecycle.removeObserver(observer) }
    }
    // 确保子生命周期以最大生命周期为上限。
    LaunchedEffect(childLifecycleOwner, maxLifecycle) {
        childLifecycleOwner.maxLifecycle = maxLifecycle
    }
    // 现在安装子 LifecycleOwner 作为组合局部。
    CompositionLocalProvider(LocalLifecycleOwner provides childLifecycleOwner, content = content)
}

/**
 * 由父级生命周期控制并由最大生命周期上限的私有 LifecycleOwner。
 */
private class ChildLifecycleOwner : LifecycleOwner {
    private val lifecycleRegistry = LifecycleRegistry(this)

    override val lifecycle = lifecycleRegistry

    // 跟踪来自父生命周期的最新已知状态。
    private var parentLifecycleState: State = State.INITIALIZED

    // 最大生命周期状态。
    var maxLifecycle: State = State.INITIALIZED
        set(value) {
            field = value
            updateLifecycleState()
        }

    fun parentLifecycleState(event: LifecycleEvent) {
        parentLifecycleState = event.targetState
        updateLifecycleState()
    }

    private fun updateLifecycleState() {
        // 子状态以父状态和最大状态中的较小者为上限。
        // 例如,如果父状态是 RESUMED,而最大状态是 STARTED,那么子状态变为 STARTED。
        if (parentLifecycleState.ordinal < maxLifecycle.ordinal) {
            lifecycleRegistry.currentState = parentLifecycleState
        } else {
            lifecycleRegistry.currentState = maxLifecycle
        }
    }
}

咱们来看看这个新的 LifecycleOwner 可组合项是如何实现每个可组合项的生命周期控制的。

LifecycleOwner 内部,会创建一个新的 LifecycleOwner

kotlin 复制代码
val childLifecycleOwner = remember(parentLifecycleOwner) {
    ChildLifecycleOwner()
}

然后,DisposableEffect 会将子生命周期与父生命周期关联起来:

kotlin 复制代码
DisposableEffect(childLifecycleOwner, parentLifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
        childLifecycleOwner.handleLifecycleEvent(event)
    }
    parentLifecycleOwner.lifecycle.addObserver(observer)
    onDispose { parentLifecycleOwner.lifecycle.removeObserver(observer) }
}

ChildLifecycleOwner 会根据父生命周期调整自身状态,并以 maxLifecycle 为上限:

kotlin 复制代码
private fun updateLifecycleState() {
    // 子状态以父状态和最大状态中的较小者为上限。
    if (parentLifecycleState.ordinal < maxLifecycle.ordinal) {
        lifecycleRegistry.currentState = parentLifecycleState
    } else {
        lifecycleRegistry.currentState = maxLifecycle
    }
}

这个新的可组合项被整合到了 Navigation 3 的 NavDisplay 中。

kotlin 复制代码
@Composable
fun NavDisplay(
    backStack: List<NavEntry>,
    modifier: Modifier = Modifier,
) {
    // ...
    val isSettled by remember { mutableStateOf(true) }
    val transitionAwareLifecycleNavDecorator =
        TransitionAwareLifecycleNavDecorator(backStack, isSettled)
    DecoratedBackStack(
        backStack = backStack,
        entryDecorators = entryDecorators + transitionAwareLifecycleNavDecorator,
        // ...
    ) { entries ->
        // ...
    }
    // ...
}

关键是 TransitionAwareLifecycleNavDecorator,它会根据过渡状态安装我们的 LifecycleOwner

kotlin 复制代码
@Composable
internal fun TransitionAwareLifecycleNavDecorator(
    backStack: List<NavEntry>,
    isSettled: Boolean,
) {
    val navEntry = entry -> backStack.backStack
    val maxLifecycle =
        if (isInBackStack && isSettled) Lifecycle.State.RESUMED
        else if (isInBackStack) Lifecycle.State.STARTED
        else Lifecycle.State.CREATED
    LifecycleOwner(maxLifecycle = maxLifecycle) { entry.Content() }
}

实际上,屏幕在过渡期间会被限制在 CREATEDSTARTED 状态。这重现了传统基于视图的导航的行为,但粒度是针对各个可组合项屏幕的。

相关推荐
Lei活在当下6 天前
【业务场景架构实战】5. 使用 Flow 模式传递状态过程中的思考点
android·架构·android jetpack
天花板之恋8 天前
Compose状态管理
android jetpack
alexhilton8 天前
面向开发者的系统设计:像建筑师一样思考
android·kotlin·android jetpack
Lei活在当下9 天前
【业务场景架构实战】4. 支付状态分层流转的设计和实现
架构·android jetpack·响应式设计
天花板之恋10 天前
Compose之图片加载显示
android jetpack
消失的旧时光-194310 天前
Kotlinx.serialization 使用讲解
android·数据结构·android jetpack
Tans511 天前
Androidx Fragment 源码阅读笔记(下)
android jetpack·源码阅读
Lei活在当下12 天前
【业务场景架构实战】2. 对聚合支付 SDK 的封装
架构·android jetpack
Tans514 天前
Androidx Fragment 源码阅读笔记(上)
android jetpack·源码阅读
alexhilton15 天前
runBlocking实践:哪里该使用,哪里不该用
android·kotlin·android jetpack