[译] Compose之解密ViewCompositionStrategy

简介: [译] Compose之解密ViewCompositionStrategy

文章作者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#mViewFragment提供的Lifecycle不一样,这里不展开,请读者自行查阅)。

使用场景:

  • 作为Fragment中的ViewComposeView
  • 生命周期未知的View中的ComposeView


使用 ViewCompositionStrategy释放组合


当组合进入释放阶段,当满足特定条件时,ViewCompositionStrategy会自动释放组合。一旦组合被释放后,资源将被清理,也不会继续追踪状态。

应用的具体策略将决定何时应自动释放组合。如果没有策略,你将不得不显式调用 ComposeViewdisposeComposition 来释放它内部的组合。

幸好,当你创建 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,这个策略就是你应该使用的。

image.png

只有一个组合的纯Compose应用

ComposeView的每个实例都维护自己独立的组合。因此,如果你正在逐步将基于View的App迁移到 Compose,你可能有多个组合。例如,如果你有一个通过Fragment进行分页的 ViewPager2,并且每个片段的内容都在 Compose中,则每个 ComposeView 都将是一个单独的组合。

image.png

混合View/Compose APP,其中 `ViewPager2` 中的每个 `ComposeView` 都维护一个单独的组合

每个组合与具有生命周期的组件(例如 ActivityFragment)之间的交互是你可能必须更改默认 ViewCompositionStrategy 以便在正确的时间释放的原因。


不同的 ViewCompositionStrategy 类型


DisposeOnDetachedFromWindow


当策略设置为DisposeOnDetachedFromWindow时,ComposeView内部的组合将被释放在如下情况:

ComposeView从窗口分离。

那么View的分离发生在什么时候呢?

一般来说,当View离开屏幕而且用户不可再见时,就会发生这种情况。下面是一些例子:

  • 通过ViewGroup#removeView,将View从层次结构中移除。
  • Viewtransition的一部分时。
  • Activity被销毁时——onStop之后,但onDestroy之前。

请注意,你可以通过 addOnAttachStateChangeListener 设置 View.OnAttachStateChangeListener 来监听窗口附加/分离事件。

在 Compose UI 版本 1.2.0-beta02 之前,此策略是默认策略,因为它是大多数用例的首选策略。但是,从 1.2.0-beta02 版本开始,此默认值已被 DisposeOnDetachedFromWindowOrReleasedFromPool 取代。


DisposeOnDetachedFromWindowOrReleasedFromPool (默认)


ComposeView 在池化容器(例如 RecyclerView)中使用时,View 元素会不断地附加和重新附加到窗口,因为元素会随着 UI 滚动而被回收。这意味着如果您使用 DisposeOnDetachedFromWindowComposeViews 的底层组合也会不断地进行初始化和释放。频繁释放和重新创建组合会损害滚动性能,尤其是在快速浏览列表时。

为了改进这一点,DisposeOnDetachedFromWindowOrReleasedFromPool 在以下情况下处理组合:

ComposeView与窗口分离,除非它是池容器(如 RecyclerView)的一部分。当组合在池容器中时,它将在底层池容器本身与窗口分离时或项目被丢弃时(即池已满时)释放。

换句话说,DisposeOnDetachedFromWindowOrReleasedFromPool 类似于 DisposeOnDetachedFromWindow 但有额外的功能。

如果您对它的工作原理和引入原因感到好奇,请查看Jetpack Compose Interop: Using Compose in a RecyclerView


DisposeOnLifecycleDestroyed


当策略设置为DisposeOnLifecycleDestroyed时,必须提供Lifecycle或者LifecycleOwner,同时组合将在如下情况被释放:

Lifecycler被销毁。当 ComposeView 与已知的 LifecycleOwner 是一对一的关系时(例如一个Fragment View),此策略是合适的。

例如,下面的代码片段在 FragmentLifecycle被销毁时释放组合:


class MyFragment : Fragment() {
    // …
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ) = ComposeView(requireContext()).apply {
        setViewCompositionStrategy(
            ViewCompositionStrategy.DisposeOnLifecycleDestroyed(
                lifecycle = this@MyFragment.lifecycle
            )
        )
        // …
    }
}

在你希望将组合的生命周期与已知Lifecycle相关联的情况下,此策略很有用。一个典型的例子就是一个FragmentView,当该View从窗口中分离(也就是说,Fragment在屏幕上不再可见),并且Fragment可能还没有被销毁(onDestory未被调用)。这种情况会发生在你滑动ViewPager2的Fragment时。如果你使用上述的任何一种策略,组合将被过早的释放,从而导致潜在的状态丢失(例如,丢失LazyColumn中的滚动状态)。

你可能会问自己的一个问题是:“如果我有一个 ComposeView 作为 RecyclerView 中的一个item,同时RecyclerView位于 Fragment 中应该怎么办?”。

应该用ComposeView的直接祖先来决定应用哪种策略——因此,由于ComposeViewRecyclerView中的一个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 来实现的。 你可能有一个问题,我应该什么时候使用 DisposeOnLifecycleDestroyedDisposeOnViewTreeLifecycleDestroyed?如果 Lifecycle 对象已知,则使用 DisposeOnLifecycleDestroyed;否则,使用 DisposeOnViewTreeLifecycleDestroyed


总结


我们介绍了要使用的所有不同类型的 ViewCompositionStrategy 选项,以及在互操作场景中选择正确的选项对于正确处理组合的重要性。


相关文章
|
2月前
|
存储 Kubernetes 持续交付
Docker Compose
【10月更文挑战第3天】
56 6
|
7月前
|
负载均衡 应用服务中间件 API
Docker-compose 简单介绍
Docker-compose 简单介绍
|
7月前
|
Shell 应用服务中间件 nginx
6.Docker Compose
6.Docker Compose
|
XML 负载均衡 API
Docker Compose(一)
Docker Compose
160 0
|
应用服务中间件 Docker 容器
|
7月前
|
小程序 API 容器
100 行写一个 Compose 版华容道
100 行写一个 Compose 版华容道
176 0
100 行写一个 Compose 版华容道
|
7月前
|
NoSQL 网络协议 Linux
Docekr三剑客之 Docekr compose
Docekr三剑客之 Docekr compose
|
7月前
|
应用服务中间件 nginx Docker
docker-compose
docker-compose
125 0
|
网络协议 关系型数据库 开发工具
Docker-compose简单了解
Docke Docker-compose
97 1
|
NoSQL Redis Docker
docker-compose 介绍
docker compose是一个命令行工具,是用于定义和运行多容器Docker应用程序的工具;通过Compose,开发者可以使用YML文件来配置应用程序需要的所有服务。
105 2