[译] 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 选项,以及在互操作场景中选择正确的选项对于正确处理组合的重要性。


相关文章
|
分布式计算 API Linux
通义千问API:找出两篇文章的不同
本章我们将介绍如何利用大模型开发一个文档比对小工具,我们将用这个工具来给互联网上两篇内容相近但版本不同的文档找找茬,并且我们提供了一种批处理文档比对的方案
|
3月前
|
弹性计算 安全 Linux
阿里云服务器购买教程:38元1年轻量云服务器和99元1年云服务器,购买全流程详解
2026年阿里云有两款入门级云服务器:38元轻量应用服务器(新用户专享)与99元经济型e实例(新老用户同享)。前者配置2核2G、40GB ESSD云盘、200M带宽,适合建站与开发测试;后者配置相同但带宽3M,支持优惠续费。本文为大家介绍购买资格、注册流程、镜像选择等相关购买步骤,助力用户低成本上云。
1047 11
|
6月前
|
编解码 自然语言处理 搜索推荐
通义百聆语音交互模型开源,创新架构可节省近50%GPU计算!
通义百聆开源新一代语音模型Fun-Audio-Chat-8B,支持语音对语音交互,具备出色共情与情绪感知能力,对话自然流畅。在多项基准测试中超越同级模型,采用高效低算力架构,GPU计算成本降低近50%。支持角色扮演与个性化语音定制,适用于情感陪伴、智能客服等场景,现已开放下载。
823 9
|
4月前
|
人工智能 监控 Linux
Antigravity-Manager:AI 多账号管家 + API 反代
Antigravity-Manager 是一款开源跨平台AI账号管家,支持OpenAI/Claude/Gemini等多平台账号统一管理、一键切换、配额监控与自动故障规避;内置协议转换与反代代理,可无缝集成Claude Code CLI等工具,实现本地化、稳定、智能的AI调用网关。(239字)
3080 1
|
7月前
|
监控 数据可视化 数据挖掘
1688运营实战指南:1688系统化学习与进阶技巧
本文系统解析1688平台运营核心策略,涵盖店铺建设、流量获取、转化提升、数据分析与客户管理五大模块,助力企业打造专业形象,实现精准引流、高效转化与持续复购,推动B2B电商业务稳步增长。
|
6月前
|
人工智能 自然语言处理 API
普通人也能玩转AI:通义千问大模型API接入与应用开发初体验
通义千问API让普通人也能轻松开发AI应用。本文介绍其接入步骤、基础调用示例及智能客服、内容创作等应用场景,助你快速上手大模型开发,开启人机协作新篇章。(238字)
|
9月前
|
Linux 虚拟化 iOS开发
macOS Sonoma 14.8 (23J21) Boot ISO 原版可引导镜像下载
macOS Sonoma 14.8 (23J21) Boot ISO 原版可引导镜像下载
8259 0
macOS Sonoma 14.8 (23J21) Boot ISO 原版可引导镜像下载
|
Linux
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
733 7