全新 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 状态。这重现了传统基于视图的导航的行为,但粒度是针对各个可组合项屏幕的。

相关推荐
alexhilton3 天前
Compose Unstyled:Compose UI中失传的设计系统层
android·kotlin·android jetpack
柿蒂3 天前
从动态缩放自定义View,聊聊为什么不要把问题复杂化
android·ai编程·android jetpack
RainyJiang3 天前
布局与测量性能优化:让Compose从"嵌套地狱"到"扁平化管理"
android·android jetpack
柿蒂4 天前
产品需求驱动下的技术演进:动态缩放View的不同方案
android·kotlin·android jetpack
Wgllss5 天前
完整烟花效果,Compose + 协程 + Flow + Channel 轻松实现
android·架构·android jetpack
alexhilton6 天前
运行时着色器实战:实现元球(Metaballs)动效
android·kotlin·android jetpack
Wgllss9 天前
Kotlin 享元设计模式详解 和对象池及在内存优化中的几种案例和应用场景
android·架构·android jetpack
alexhilton10 天前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
bytebeats12 天前
Jetpack Compose 1.9: 核心新特性简介
android·android jetpack