[译] Compose之稳定性(Stability)的解释(二)

简介: [译] Compose之稳定性(Stability)的解释

Compose编译器报告


Compose编译器可以输出其稳定性推断的结果以供检查。结合检查报告,你可以确定哪些可组合项是可跳过的,哪些不是。这篇文章总结了如何使用这些报告,但有关这些报告的详细信息,请参阅 技术文档

⚠️ 警告:只有当你确实遇到与稳定性相关的性能问题时,才应使用此技术。试图让你的整个 UI 都可以跳过是过早优化,可能会导致未来的维护困难。在针对稳定性进行优化之前,请确保你遵循我们关于 Compose 性能的 最佳实践

默认情况下不启用编译器报告。通过使用compiler flag来开启Compose编译器报告,具体设置因项目而异,但对于大多数项目,你可以将以下脚本粘贴到根 build.gradle 文件中。


/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
subprojects {
 tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
  kotlinOptions {
    if (project.findProperty("composeCompilerReports") == "true") {
      freeCompilerArgs += [        "-P",        "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +        project.buildDir.absolutePath + "/compose_compiler"        ]
      }
      if (project.findProperty("composeCompilerMetrics") == "true") {
        freeCompilerArgs += [          "-P",          "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +          project.buildDir.absolutePath + "/compose_compiler"        ]
      }
    }
  }
}

要调试可组合项的稳定性,你可以运行以下task:


./gradlew assembleRelease -PcomposeCompilerReports=true

⚠️ 警告:确保始终在发布版本(release build )上运行它以确保准确的结果。

此任务将输出三个文件。 (包括来自Jetsnack的示例输出)

-classes.txt — 关于此模块中类的稳定性的报告。 案例

-composables.txt — 关于此模块中可组合项的可重启性和可跳过性的报告。案例.

-composables.csv — 上述文本文件的 csv 版本,用于导入电子表格或通过脚本处理。 案例.

如果你改为运行 composeCompilerMetrics 任务,你将获得项目中可组合项数量的总体统计信息和其他类似信息。这在这篇文章中没有涉及,因为它对调试没有那么有用。 打开 composables.txt 文件,你将看到该模块的所有可组合函数,并且每个函数都将标记它们是否可重新启动、可跳过及其参数的稳定性。这是来自Jetsnack 的一个假设示例,它是 Compose 示例应用程序之一。


restartable skippable scheme(“[androidx.compose.ui.UiComposable]”) fun SnackCollection(
   stable snackCollection: SnackCollection
   stable onSnackClick: Function1<Long, Unit>
   stable modifier: Modifier? = @static Companion
   stable index: Int = @static 0
   stable highlight: Boolean = @static true
)

SnackCollection 可组合项完全可重启可跳过稳定。在可能的情况下,这通常是你想要的,尽管远非强制性的(博文末尾有更多详细信息)。

但是,让我们看另一个例子。


restartable scheme(“[androidx.compose.ui.UiComposable]”) fun HighlightedSnacks(
   stable index: Int
   unstable snacks: List<Snack>
   stable onSnackClick: Function1<Long, Unit>
   stable modifier: Modifier? = @static Companion
)

HighlightedSnacks 可组合项是不可跳过的——只要在重组期间调用它,即使它的参数都没有改变,它也会重组。 这是由不稳定的参数snacks引起的。

现在我们来到 classes.txt 文件来检查 Snack 的稳定性。


unstable class Snack {
   stable val id: Long
   stable val name: String
   stable val imageUrl: String
   stable val price: Long
   stable val tagline: String
   unstable val tags: Set<String>
   <runtime stability> = Unstable
}

作为参考,这是 Snack 的声明方式


data class Snack(
    val id: Long,
    val name: String,
    val imageUrl: String,
    val price: Long,
    val tagline: String = "",
    val tags: Set<String> = emptySet()
)

snacks不稳定的。它的绝大部分参数都是稳定的,但tags被认为是不稳定的。但这是为什么呢? Set 看起来是不可变的,它不是 MutableSet。 不幸的是,Set(以及 List 和其他标准集合类,稍后会详细介绍)在 Kotlin 中被定义为接口,这意味着底层实现可能仍然是可变的。例如,你可以写:


val set: Set<String> = mutableSetOf(“foo”)

变量是常量,它声明的类型不是可变的,但它的实现仍然是可变的。 Compose 编译器无法确定此类的不变性,因为它只看到声明的类型,因此将其声明为不稳定的。现在让我们看看如何使它稳定。


让不稳定稳定(Stabilizing the unstable)


不稳定类导致了性能问题时,尝试使其稳定是个好主意。首先要尝试的是让类完全不可变。

不可变——表示一种类型,其中任何属性的值在构造对象后都不会改变,并且所有方法都是引用透明的。所有基本类型(StringIntFloat 等)都被认为是不可变的。

换句话说,将所有 var 属性设为 val,并将所有这些属性设为不可变类型。

如果你无法实现上述要求,那你将不得不对任何可变属性使用 Compose State。

稳定——表示一种类型是可变的,但如果任何公共属性或方法行为会产生与先前调用不同的结果,Compose 运行时将收到通知(译者:虽然对象内部的数值虽然会发生变化,但是这种变化可以被Compose识别)。

这意味着在实践中,任何可变属性都应该由 Compose 状态支持,例如 mutableStateOf(…)

回到 Snack 示例,该类看起来是不可变的,那么我们如何解决它呢?

你可以采取以下方法:


Kotlinx 不可变集合(Immutable Collections)


Compose 编译器的 1.2 版包括对 Kotlinx Immutable Collections的支持。这些集合保证是不可变的,并且将由编译器推断为不可变的。该库仍处于 alpha 阶段,因此预计其 API 可能会发生变化。你应该评估这对你的项目是否可以接受。

tags的类型改变为下面这种类型可以让Snack稳定


val tags: ImmutableSet<String> = persistentSetOf()


使用Stable或者Immutable注释


根据上述规则,类也可以使用 @Stable 或 @Immutable 进行注释。

⚠️ 警告:非常需要注意的是,这是一个约定,要遵循相应的注解规则。它本身不会使类不可变/稳定。错误地使用注释可能会导致重组失败。

注释一个类会覆盖编译器对你的类的推断,这样它类似于kotlin的!!运算符。你应该非常小心这些注释的使用,因为如果你弄错了,覆盖编译器行为可能会导致你出现无法预料的错误。如果可以在没有注释的情况下使您的类稳定,那么你应该努力以这种方式实现稳定。

正确注释Snack的方式如下:


/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
@Immutable
data class Snack(
  val id: Long,
  val name: String,
  val imageUrl: String,
  val price: Long,
  val tagline: String = "",
  val tags: Set<String> = emptySet()
)

无论选择哪种方法,Snack 类都将被推断为稳定的。

但是,回到 HighlightedSnacks 可组合项,HighlightedSnacks 仍未标记为可跳过:


unstable snacks: List<Snack>

当涉及到集合类型时,参数面临与类相同的问题,List 总是被确定为不稳定的,即使它是稳定类型的集合。 你也不能将单个参数标记为稳定,也不能将可组合项注释为始终可跳过。所以,你可以做什么?同样,也有很多种方法解决这个问题。

使用Kotlinx 不可变集合而不是List


/* Copyright 2022 Google LLC.
  SPDX-License-Identifier: Apache-2.0 */
  @Composable
  private fun HighlightedSnacks(
    index: Int,
    snacks: ImmutableList<Snack>,
    onSnackClick: (Long) -> Unit,
    modifier: Modifier = Modifier
  )

如果你不能使用不可变集合,你可以在最简单的情况下将List包装在带注释的稳定类中,以将其标记为对 Compose 编译器不可变。


@Immutable
    data class SnackCollection(
      val snacks: List<Snack>
    )

然后,你可以将其用作可组合项中的参数类型。


@Composable
      private fun HighlightedSnacks(
        index: Int,
        snacks: SnackCollection,
        onSnackClick: (Long) -> Unit,
        modifier: Modifier = Modifier
      )

在采用其中任何一种方法后,HighlightedSnacks 可组合项现在既可以跳过也可以重新启动。


restartable skippable scheme(“[androidx.compose.ui.UiComposable]”) fun HighlightedSnacks(
   stable index: Int
   stable snacks: ImmutableList<Snack>
   stable onSnackClick: Function1<Long, Unit>
   stable modifier: Modifier? = @static Companion
)

HighlightedSnacks 现在将在其入参均未更改时跳过重组。


多模块


你可能遇到的另一个常见问题与多模块架构有关。 Compose 编译器推断一个类是否稳定,前提是它引用的所有非原始类型都被显式标记为稳定的,而且位于Compose 编译器构建的模块中。如果你的数据层(data layer)和UI层(UI layer,)是分开的(这是推荐的方法),这可能是你会遇到的问题。要解决此问题,你可以:

  • 在你的数据层模块上启用 Compose 编译器,或在适当的地方使用 @Stable 或 @Immutable 标记你的类。
  • 这将涉及向数据层添加 Compose 依赖项,你只需要添加Compose运行时的依赖而不用添加Compose-UI依赖。
  • 将你的数据层的类包装在你的UI层的特定包装类中。

同样的问题也会发生在外部module上,除非它们使用的是 Compose 编译器。

这是一个已知的限制,我们目前正在研究针对多模块架构和外部库的更好解决方案。


所有的重组都应该被跳过吗?


不。

追求应用中每个可组合项的完全可跳过性是不成熟的优化。可跳过实际上会增加其自身的少量开销,这可能不值得,如果你确定可重启的开销大于其价值,你甚至可以将可组合项注释为 不可重启。在许多其他情况下,可跳过不会有任何实际好处,只会导致难以维护代码。例如:

  • 不经常重组或根本不重组的可组合项。
  • 只是被称为可跳过但是实际上项目中没有跳过的场景的可组合项。


总结


这篇博文中有很多信息,所以让我们总结一下。

  • Compose 查看可组合项的每个参数的稳定性,以确定在重组期间是否可以跳过它。
  • 如果你注意到你的可组合项没有被跳过并且它导致了性能问题,你应该首先检查不稳定的明显原因,例如 var 参数。
  • 你可以使用编译器报告来确定所推断的关于你的类的稳定性。
  • ListSet 和 Map 这样的集合类总是被确定为不稳定的,因为不能保证它们是不可变的。你可以改用 Kotlinx 不可变集合,或将你的类注释为 @Immutable@Stable
  • 来自未运行 Compose 编译器的module的类始终被确定为不稳定。添加 compose 运行时的依赖,并在你的模块中将它们标记为稳定,或根据需要将类包装在 UI model类中
  • 每个可组合项都应该是可跳过的吗?不。

有关 Compose 性能的更多调试技巧,请查看我们的最佳实践指南I/O talk


目录
打赏
0
0
0
0
1
分享
相关文章
Compose Multiplatform 官宣进入 alpha 阶段
Compose Multiplatform 官宣进入 alpha 阶段
85 0
LangChain-10(2) 加餐 编写Agent获取本地Docker运行情况 无技术含量只是思路
LangChain-10(2) 加餐 编写Agent获取本地Docker运行情况 无技术含量只是思路
33 4
LangChain-10(2) 加餐 编写Agent获取本地Docker运行情况 无技术含量只是思路
容器化技术,特别是Docker,已经成为现代软件开发和部署的重要工具。
容器化技术,特别是Docker,已经成为现代软件开发和部署的重要工具。
性能工具之Taurus进阶场景使用
【5月更文挑战第9天】性能工具之Taurus进阶场景使用
207 3
性能工具之Taurus进阶场景使用
深入浅出:使用Docker容器化改善Java应用的开发与部署流程
在快速迭代与持续集成的软件开发周期中,确保应用在各种环境中一致运行是一个挑战。本文介绍了如何利用Docker容器技术,来容器化Java应用,以实现环境一致性、简化配置和加速部署过程。我们将从Docker的基础知识开始,探讨其与传统虚拟机的区别,进而深入到如何创建Dockerfile,构建镜像,以及运行和管理容器。此外,文章还将涵盖使用Docker Compose来管理多容器应用的策略,以及如何利用容器化改善CI/CD流程。通过本文,读者将获得关于如何高效地利用Docker改善Java应用开发与部署流程的实践指导。
402 1
深入浅出 Compose Compiler(5) 类型稳定性 Stability
深入浅出 Compose Compiler(5) 类型稳定性 Stability
125 0
深入浅出 Compose Compiler(5) 类型稳定性 Stability
|
9月前
|
深入浅出:使用Docker容器化改善Java应用部署
在本篇文章中,我们探讨了Docker作为一种领先的容器化技术,如何为Java应用提供一个轻量级、可移植的运行环境,从而简化部署流程并提高开发效率。通过具体示例,我们将指导读者理解Docker的基本概念,展示如何创建一个Java应用的Docker镜像,并详细说明如何配置和运行容器。此外,我们还将讨论使用Docker的优势,包括环境一致性、便捷的版本控制和易于扩展等,以及如何克服在容器化过程中可能遇到的挑战。本文旨在为有意采用容器化技术改善Java应用部署流程的开发者提供一个全面而深入的指南。
243 0
多阶段构建:精妙优化Docker镜像大小和性能
在容器化应用的世界中,Docker镜像大小和性能优化是至关重要的。多阶段构建是一项强大的技术,通过精心设计Dockerfile,可以在构建镜像时去除不必要的组件,从而显著减小镜像大小,提高性能。本文章将深入讨论多阶段构建的各个方面,并通过更为丰富和实际的示例代码,帮助大家全面了解和掌握这一重要技术。
如何使用本地 Docker 更好地开发?我们总结了这八条经验
如果你像我们一样需要运行许多不同的应用程序,那么将开发环境容器化可以极大地提高工作效率。这里有一些可以优化本地 Docker 环境的技巧。
346 0
如何使用本地 Docker 更好地开发?我们总结了这八条经验

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等