文章作者:Chris Arriola
原文链接:ViewCompositionStrategy Demystified | by Chris Arriola)
©️一切版权归作者所有,本译文仅用于技术交流请勿用于商业用途,未经允许禁止转载,违者后果自负]
在 Jetpack Compose 中,组合
(Composition)是一种树状结构,通过可运行的可组合项生成,用于描述app 的UI。当不再需要组合
时,Jetpack Compose 将不再跟踪其状态,并且组合
会被释放以便释放资源。
View
重组策略(下面称ViewCompositionStrategy
)定义应如何释放组合,默认情况下,ViewCompositionStrategy.Default
会在底层 ComposeView
从窗口分离时释放组合,除非它是池容器(例如RecyclerView
)的一部分。然而,如果你在逐步在项目中使用Compose的时候,这种默认策略可能会导致某些场景下的状态丢失。例如,如果你在基于Fragment
的Compose程序中看到了诸如滚动位置重置之类的奇怪故障,则可能是你使用了错误的重组策略(你应该改用一种基于生命周期的策略)。
本文中,我将介绍什么ViewCompositionStrategy
、为什么需要它、以及如何为你的用例选择正确的策略以避免状态丢失。
简介
1.DisposeOnDetachedFromWindow
当组合依赖的ComposeView从窗口分离时,组合将被释放。这种策略目前已经被DisposeOnDetachedFromWindowOrReleasedFromPool所取代了。
使用场景:
- ComposeView是View层次结构中的唯一元素,或者是View/Compose的混合结构中的一员(不在Fragment中)。
2.DisposeOnDetachedFromWindowOrReleasedFromPool (默认)
当组合不在池容器中(例如RecyclerView)时,它类似于DisposeOnDetachedFromWindow
,如果组合在池容器中时,它将在池容器本身与窗口分离式或者Item被丢弃时(即池已满)分离。(译者注:两种情况,第一种是RecyclerView从窗口分离时,第二种是由于池满了,RecyclerView将要抛弃一些Item对应的View时)。
使用场景:
- ComposeView是View层次结构中的唯一元素,或者是View/Compose的混合结构中的一员(不在Fragment中)。
ComposeView
是池容器里面的一个item,例如RecyclerView
。
3.DisposeOnLifecycleDestroyed
当ComposeView对应的Lifecycle被销毁时,组合将被处理。
使用场景:
- 作为Fragment中的View的ComposeView。
4.DisposeOnViewTreeLifecycleDestroyed
当ViewTreeLifecycleOwner
提供的Lifecycle
被销毁时,组合将被释放(译者注:viewTree提供的Lifecycle
和组件提供的Lifecycle
生命周期不一致,例如Fragment#mView
和Fragment
提供的Lifecycle不一样,这里不展开,请读者自行查阅)。
使用场景:
- 作为
Fragment
中的View
的ComposeView
。 - 生命周期未知的
View
中的ComposeView
。
使用 ViewCompositionStrategy释放组合
当组合进入释放阶段,当满足特定条件时,ViewCompositionStrategy
会自动释放组合。一旦组合被释放后,资源将被清理,也不会继续追踪状态。
应用的具体策略将决定何时应自动释放组合。如果没有策略,你将不得不显式调用 ComposeView
的 disposeComposition
来释放它内部的组合。
幸好,当你创建 ComposeView
(或从 ComponentActivity
调用 setContent
)时,已经应用了 ViewCompositionStrategy.Default
定义的默认策略,目前的默认策略是DisposeOnDetachedFromWindowOrReleasedFromPool
,因此在绝大多数情况下,你不必显式去声明它。但是,你可以通过 setViewCompositionStrategy
来更改为不同的策略。
val composeView = ComposeView(context = context) composeView.setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed )
纯Compose应用程序 vs View/Compose混合应用程序
在单Activity纯Compose的App中,通常只有一个组合处于活跃状态,我之所以说通常是因为有一些例外——例如subcomposition(译者注:译者也没搞懂这个具体指的是什么)——但这超出了这篇文章的范围。第一次的重组发生于Activity
被创建时(is created)。Activity
通过调用setContent
来执行可组合项,并且组合保持活跃直到组合的内容与窗口分离——这种分离发生于Activity
被销毁时。这是ComposeView
默认的ViewCompositionStrategy
,如果你正在写一个纯Compose的App,这个策略就是你应该使用的。
只有一个组合的纯Compose应用
ComposeView
的每个实例都维护自己独立的组合。因此,如果你正在逐步将基于View的App迁移到 Compose,你可能有多个组合。例如,如果你有一个通过Fragment
进行分页的 ViewPager2
,并且每个片段的内容都在 Compose中,则每个 ComposeView
都将是一个单独的组合。
混合View/Compose APP,其中 `ViewPager2` 中的每个 `ComposeView` 都维护一个单独的组合
每个组合与具有生命周期的组件(例如 Activity
或 Fragment
)之间的交互是你可能必须更改默认 ViewCompositionStrategy
以便在正确的时间释放的原因。
不同的 ViewCompositionStrategy 类型
DisposeOnDetachedFromWindow
当策略设置为DisposeOnDetachedFromWindow
时,ComposeView
内部的组合将被释放在如下情况:
ComposeView
从窗口分离。
那么View
的分离发生在什么时候呢?
一般来说,当View
离开屏幕而且用户不可再见时,就会发生这种情况。下面是一些例子:
- 通过
ViewGroup#removeView
,将View
从层次结构中移除。 View
是transition的一部分时。- 当
Activity
被销毁时——onStop之后,但onDestroy之前。
请注意,你可以通过 addOnAttachStateChangeListener
设置 View.OnAttachStateChangeListener
来监听窗口附加/分离事件。
在 Compose UI 版本 1.2.0-beta02 之前,此策略是默认策略,因为它是大多数用例的首选策略。但是,从 1.2.0-beta02 版本开始,此默认值已被 DisposeOnDetachedFromWindowOrReleasedFromPool
取代。
DisposeOnDetachedFromWindowOrReleasedFromPool (默认)
当 ComposeView
在池化容器(例如 RecyclerView
)中使用时,View
元素会不断地附加和重新附加到窗口,因为元素会随着 UI 滚动而被回收。这意味着如果您使用 DisposeOnDetachedFromWindow
,ComposeViews
的底层组合也会不断地进行初始化和释放。频繁释放和重新创建组合会损害滚动性能,尤其是在快速浏览列表时。
为了改进这一点,DisposeOnDetachedFromWindowOrReleasedFromPool
在以下情况下处理组合:
ComposeView
与窗口分离,除非它是池容器(如RecyclerView
)的一部分。当组合在池容器中时,它将在底层池容器本身与窗口分离时或项目被丢弃时(即池已满时)释放。
换句话说,DisposeOnDetachedFromWindowOrReleasedFromPool
类似于 DisposeOnDetachedFromWindow
但有额外的功能。
如果您对它的工作原理和引入原因感到好奇,请查看Jetpack Compose Interop: Using Compose in a RecyclerView。
DisposeOnLifecycleDestroyed
当策略设置为DisposeOnLifecycleDestroyed
时,必须提供Lifecycle或者LifecycleOwner,同时组合将在如下情况被释放:
Lifecycler
被销毁。当ComposeView
与已知的LifecycleOwner
是一对一的关系时(例如一个Fragment View),此策略是合适的。
例如,下面的代码片段在 Fragment
的Lifecycle
被销毁时释放组合:
class MyFragment : Fragment() { // … override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ) = ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnLifecycleDestroyed( lifecycle = this@MyFragment.lifecycle ) ) // … } }
在你希望将组合的生命周期与已知Lifecycle
相关联的情况下,此策略很有用。一个典型的例子就是一个Fragment
的View
,当该View
从窗口中分离(也就是说,Fragment在屏幕上不再可见),并且Fragment可能还没有被销毁(onDestory未被调用)。这种情况会发生在你滑动ViewPager2的Fragment时。如果你使用上述的任何一种策略,组合将被过早的释放,从而导致潜在的状态丢失(例如,丢失LazyColumn
中的滚动状态)。
你可能会问自己的一个问题是:“如果我有一个 ComposeView
作为 RecyclerView
中的一个item,同时RecyclerView
位于 Fragment
中应该怎么办?”。
应该用ComposeView
的直接祖先来决定应用哪种策略——因此,由于ComposeView
是RecyclerView
中的一个item,你将使用DisposeOnDetachedFromWindowOrReleasedFromPool
,否则使用DisposeOnLifecycleDestroyed
。
DisposeOnViewTreeLifecycleDestroyed
DisposeOnViewTreeLifecycleDestroyed
和前一个策略很像,但是它是前一个策略的替代方法。如果希望组合的生命周期和Lifecycle
联系起来,但Lifecycle
尚不清楚,可以使用此策略。组合将会在如下情况释放:
View
添加到的ViewTreeLifecyleOwner
被销毁时。当ComposeView
与已知的ViewTreeLifecycleOwner
是一对一的关系时(例如一个Fragment View),此策略是合适的。
例如,下面的代码片段显示了一个继承自 AbstractComposeView
的自定义View。 组合将在最近的 ViewTreeLifecycleOwner
被销毁时被释放,因为 ViewCompositionStrategy
被修改为 DisposeOnViewTreeLifecycleDestroyed
:
class MyCustomView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { init { // Related lifecycle may not be known at this point yet setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed ) } @Composable override fun Content() { // Compose } }
本质上,这是通过使用 ViewTreeLifecycleOwner.get
API 查找负责管理 ComposeView
的关联 LifecycleOwner
来实现的。 你可能有一个问题,我应该什么时候使用 DisposeOnLifecycleDestroyed
与 DisposeOnViewTreeLifecycleDestroyed
?如果 Lifecycle
对象已知,则使用 DisposeOnLifecycleDestroyed
;否则,使用 DisposeOnViewTreeLifecycleDestroyed
。
总结
我们介绍了要使用的所有不同类型的 ViewCompositionStrategy
选项,以及在互操作场景中选择正确的选项对于正确处理组合的重要性。