[译] 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


相关文章
|
7月前
|
移动开发 API Android开发
Compose Multiplatform 官宣进入 alpha 阶段
Compose Multiplatform 官宣进入 alpha 阶段
69 0
|
数据采集 机器学习/深度学习 编解码
MMdetection框架速成系列 第02部分:整体算法流程+模型搭建流程+detection训练与测试核心组件+训练部分与测试部分的核心算法
众所周知,目标检测算法比较复杂,细节比较多,难以复现,而我们推出的 MMDetection 开源框架则希望解决上述问题。目前 MMdetection 已经复现了大部分主流和前沿模型,例如 Faster R-CNN 系列、Mask R-CNN 系列、YOLO 系列和比较新的 DETR 等等,模型库非常丰富,star 接近 13k,在学术研究和工业落地中应用非常广泛。
1617 0
|
17天前
|
存储 测试技术 持续交付
Docker与CI/CD的集成策略及其对软件开发效率和质量的提升作用
本文探讨了Docker与CI/CD的集成策略及其对软件开发效率和质量的提升作用。首先介绍了CI/CD的基本概念,接着阐述了Docker在环境一致性、快速部署、资源隔离和轻量化方面的优势。文章还详细讨论了构建、测试和部署阶段的具体集成方法,以及集成后带来的效率提升、可靠性增强、加速交付和易于管理等好处。最后,通过案例分析展示了集成的实际效果,强调了Docker与CI/CD结合的重要性和未来前景。
31 2
|
2月前
|
Shell Docker 容器
LangChain-10(2) 加餐 编写Agent获取本地Docker运行情况 无技术含量只是思路
LangChain-10(2) 加餐 编写Agent获取本地Docker运行情况 无技术含量只是思路
19 4
LangChain-10(2) 加餐 编写Agent获取本地Docker运行情况 无技术含量只是思路
|
4月前
|
机器学习/深度学习 测试技术 持续交付
ONNX 与持续集成/持续部署 (CI/CD):构建可信赖的 ML 生命周期管理
【8月更文第27天】随着机器学习 (ML) 模型的广泛应用,确保模型的正确性、稳定性和可追踪性变得尤为重要。持续集成/持续部署 (CI/CD) 是软件开发中的重要实践,旨在通过自动化测试和部署流程来提高软件质量和开发效率。将 ONNX 集成到 CI/CD 流程中可以实现模型版本管理、自动化测试和部署,从而构建一个可信赖的机器学习生命周期管理系统。本文将探讨如何将 ONNX 模型与 CI/CD 流程结合,以实现模型的自动化管理。
91 5
|
7月前
|
XML 存储 测试技术
性能工具之Taurus进阶场景使用
【5月更文挑战第9天】性能工具之Taurus进阶场景使用
158 3
性能工具之Taurus进阶场景使用
|
7月前
|
Java 编译器 测试技术
深入浅出 Compose Compiler(5) 类型稳定性 Stability
深入浅出 Compose Compiler(5) 类型稳定性 Stability
98 0
深入浅出 Compose Compiler(5) 类型稳定性 Stability
|
7月前
|
Kubernetes Java Docker
多阶段构建:精妙优化Docker镜像大小和性能
在容器化应用的世界中,Docker镜像大小和性能优化是至关重要的。多阶段构建是一项强大的技术,通过精心设计Dockerfile,可以在构建镜像时去除不必要的组件,从而显著减小镜像大小,提高性能。本文章将深入讨论多阶段构建的各个方面,并通过更为丰富和实际的示例代码,帮助大家全面了解和掌握这一重要技术。
|
机器学习/深度学习 存储 监控
DVC 使用案例(三):机器学习持续集成与持续交互( CI/CD )
将 DevOps 方法应用于机器学习 (MLOps) 和数据管理 (DataOps) 越来越普遍。对于一个完善的 MLOps 平台来说,需要囊括资源编排(为模型训练提供服务器)、模型测试(验证模型推理)、模型部署到生产,以及模型监控和反馈等机器学习生命周期各个环节。 DVC 可以管理数据/模型和重现 ML 流水线,而 CML 可以协助编排、测试以及监控。
|
安全 编译器 Android开发
[译] Compose之稳定性(Stability)的解释(一)
[译] Compose之稳定性(Stability)的解释
248 0
[译] Compose之稳定性(Stability)的解释(一)