方法指南

更深入的性能考虑因素

阅读用时:8 分钟
3 作者
Ben Weiss, Breana Tate, Jossi Wolf

请先冷静下来,让我们为您详细介绍效果背景信息。

欢迎来到“效果聚焦周”的第 3 天。今天,我们将继续分享有关应用性能重要方面的详细信息和指导。我们将介绍配置文件引导型优化、Jetpack Compose 性能改进以及在幕后工作时的注意事项。让我们立刻开始吧。

配置文件引导的优化

基准配置文件启动配置文件是提升 Android 应用的启动和运行时性能的基础。它们属于一组称为“配置文件引导的优化”的性能优化。

当应用打包时,d8 dexer 会获取类和方法,并填充应用的 classes.dex 文件。当用户打开应用时,系统会依次加载这些 dex 文件,直到应用可以启动为止。通过提供启动配置文件,您可以让 d8 知道要在第一个 classes.dex 文件中打包哪些类和方法。此结构可让应用加载更少的文件,从而提高启动速度。

基准配置文件可有效地将即时 (JIT) 编译步骤从用户设备转移到开发者机器上。生成的预先 (AOT) 编译代码已证明可缩短启动时间并减少渲染问题。

Trello 和基准配置文件

我们向 Trello 应用的工程师询问了基准配置文件对其应用性能的影响。在主要用户历程中应用基准配置文件后,Trello 发现应用启动时间显著缩短了 25%。

image.png

Trello 通过使用基准配置文件,将应用的启动时间缩短了 25%。

Meta 的基准配置文件

此外,Meta 的工程师最近还发表了一篇文章,介绍了他们如何利用基准配置文件加速 Android 应用

image.png

在 Meta 的各个应用中,团队发现应用基准配置文件后,各种关键指标的改进幅度高达 40%。

这类技术改进也有助于您提高用户满意度和业务成功度。与产品负责人、首席技术官和决策者分享此信息也有助于提升应用性能。

基准配置文件使用入门

如需生成基准配置文件或启动配置文件,请编写一个用于测试应用的 macrobenchmark 测试。在测试期间,系统会收集配置文件数据,这些数据将在应用编译期间使用。这些测试是使用新的 UiAutomator API 编写的,我们将在明天介绍该 API。

编写这样的基准非常简单,您可以在 GitHub 上查看完整示例。

  @Test

fun profileGenerator() {

    rule.collect(

        packageName = TARGET_PACKAGE,

        maxIterations = 15,

        stableIterations = 3,

        includeInStartupProfile = true

    ) {

        uiAutomator {

            startApp(TARGET_PACKAGE)

        }

    }


}

注意事项

首先,为用户最常访问的路径编写宏基准测试基准配置文件和启动配置文件。这意味着用户进入应用的主要入口点,通常是登录后。然后,继续编写更多测试用例,以便更全面地了解基准配置文件。您无需使用基准配置文件涵盖所有内容。坚持使用最常用的路径,并衡量实际效果。我们将在明天的博文中详细介绍这一点。

配置文件引导型优化入门

如需了解基准配置文件的底层运作方式,请观看 Android 开发者峰会的这段视频:

如需深入了解,请观看 Android 构建时间 节目中有关 Profile Guided Optimization 的一集:

我们还提供了有关基准配置文件启动配置文件的详尽指南,供您进一步阅读。

Jetpack Compose 性能改进

Android 的界面框架已让工程团队的性能投资获得回报。从 Jetpack Compose 版本 1.9 开始,在内部长滚动基准测试期间,滚动卡顿已降至 0.2%。

jankyFrames.png

这些改进之所以能够实现,是因为最新版本中包含多项功能。

可自定义的缓存窗口

默认情况下,延迟布局仅在滚动方向上提前组合一个项,并且在某项滚动到屏幕外后,系统会将其舍弃。您现在可以通过视口或 dp 大小的一部分来指定要保留的商品数量。这有助于您的应用预先执行更多工作,并在帧之间启用可暂停的合成后,更高效地利用可用时间。

如需开始使用可自定义的缓存窗口,请实例化 LazyLayoutCacheWindow 并将其传递给您的延迟列表或延迟网格。使用不同的缓存窗口大小(例如视口的 50%)来衡量应用的效果。最佳值取决于内容的结构和商品的大小。

  val dpCacheWindow = LazyLayoutCacheWindow(ahead = 150.dp, behind = 100.dp)

val state = rememberLazyListState(cacheWindow = dpCacheWindow)

LazyColumn(state = state) {

    // column contents

}

可暂停的组合

借助此功能,您可以暂停合成,并将其工作拆分到多个帧中。这些 API 已在 1.9 中推出,现在在 1.10 中默认用于延迟布局预提取。对于合成时间较长的复杂项目,您应该会看到最大的好处。

image.png

更多 Compose 性能优化

在 Compose 的 1.9 和 1.10 版本中,团队还进行了一些不太明显的优化。

改进了多个在后台使用协程的 API。例如,在使用 Draggable 和 Clickable 时,开发者应会发现响应时间缩短,分配次数增加。

布局矩形跟踪方面的优化改进了 onVisibilityChanged()onLayoutRectChanged() 等修饰符的性能。即使不明确使用这些 API,也能加快布局阶段的速度。

另一项性能改进是在通过 onPlaced() 观测位置时使用缓存值。

在后台预提取文本

从 1.9 版开始,Compose 新增了在后台线程上预提取文本的功能。这使您能够预热缓存,从而实现更快的文本布局,并且与应用渲染性能相关。在布局期间,必须将文本传递到 Android 框架,以便填充字词缓存。默认情况下,此方法在界面线程上运行。将预提取和填充字词缓存分流到后台线程可以加快布局速度,尤其是在处理较长文本时。如需在后台线程上预提取,您可以将自定义执行程序传递给任何在后台使用 BasicText 的可组合项,方法是将 LocalBackgroundTextMeasurementExecutor 传递给 CompositionLocalProvider,如下所示。

  val defaultTextMeasurementExecutor = Executors.newSingleThreadExecutor()

CompositionLocalProvider(

    LocalBackgroundTextMeasurementExecutor provides DefaultTextMeasurementExecutor

) {

    BasicText("Some text that should be measured on a background thread!")


}

根据文本的不同,这可以提升文本渲染性能。为确保它能提升应用的渲染性能,请进行基准比较并比较结果。

后台工作性能注意事项

后台工作是许多应用的重要组成部分。您可能正在使用 WorkManager 或 JobScheduler 等库来执行以下任务:

  • 定期上传分析事件
  • 在后端服务与数据库之间同步数据
  • 处理媒体(即调整图片大小或压缩图片)

执行这些任务时面临的一大挑战是如何平衡性能和能效。WorkManager 可帮助您实现这种平衡。它旨在提高能效,并允许将工作推迟到最佳执行窗口,而最佳执行窗口会受到多种因素的影响,包括您指定的限制或系统施加的限制。

不过,WorkManager 并非一体适用的解决方案。Android 还提供了一些经过能耗优化的 API,这些 API 专门针对某些常见核心用户历程 (CUJ) 而设计。  

如需查看其中一些后台工作的列表,请参阅后台工作着陆页,其中包括更新微件和在后台获取位置信息。

用于调试后台工作的本地调试工具:常见场景

如需调试后台工作并了解任务可能延迟或失败的原因,您需要了解系统如何调度任务。

为此,WorkManager 提供了多种相关 工具来帮助您在本地进行调试并优化性能(其中一些工具也适用于 JobScheduler)!以下是使用 WorkManager 时可能会遇到的一些常见场景,以及可用于调试这些场景的工具说明。

调试预定工作未执行的原因

预定工作延迟或根本不执行可能是由多种因素造成的,包括未满足指定的限制条件或系统施加了限制条件

调查预定工作未运行的原因的第一步是确认工作已成功安排。确认调度状态后,确定是否存在任何未满足的限制条件或前提条件导致工作无法执行。

有多种工具可用于调试此场景。

后台任务检查器

后台任务检查器是一款强大的工具,直接集成到 Android Studio 中。它以直观的方式呈现所有 WorkManager 任务及其关联的状态(正在运行、已入队、失败、成功)。

如需使用后台任务检查器调试预定工作未执行的原因,请参阅列出的工作状态。“已加入队列”状态表示您的工作已安排,但仍在等待运行。

优势:除了提供一种查看所有任务的简便方式之外,如果您有链式工作,此工具尤其有用。后台任务检查器提供了一个图表视图,可直观显示之前的任务失败是否会影响后续任务的执行。

image.png

后台任务检查器列表视图

image.png

后台任务检查器图表视图

adb shell dumpsys jobscheduler

命令会返回所有有效 JobScheduler 作业(包括 WorkManager Worker)的列表,以及指定的约束条件和系统施加的约束条件。它还会返回作业历史记录。

如果您想以其他方式查看安排的工作和相关限制,请使用此功能。对于 WorkManager 2.10.0 之前的 WorkManager 版本,adb shell dumpsys jobscheduler 将返回具有此名称的工作器列表:

  [package name]/androidx.work.impl.background.systemjob.SystemJobService

如果您的应用有多个工作器,更新到 WorkManager 2.10.0 后,您将能够看到工作器名称,并轻松区分不同的工作器:

  #WorkerName#@[package name]/androidx.work.impl.background.systemjob.SystemJobService

优点: 此命令有助于了解是否存在任何系统强制执行的限制 ,而这些限制是您无法通过后台任务检查器确定的。例如,这会返回应用的待机存储分区,从而影响预定工作的完成时间窗口。

启用调试日志记录

您可以启用自定义日志记录,以查看详细的 WorkManager 日志,其中会附加 WM—

优势:这样一来,您就可以了解工作何时安排、约束条件何时满足以及生命周期事件,并且可以在开发应用时参考这些日志。

WorkInfo.StopReason

如果您发现特定工作器的性能不可预测,可以使用 WorkInfo.getStopReason 以编程方式观察工作器在上一次运行尝试中停止的原因。 

建议您配置应用,以使用 getWorkInfoByIdFlow 观察 WorkInfo,从而确定您的工作是否受到后台限制、约束、频繁超时甚至用户停止的影响。

优势:您可以使用 WorkInfo.StopReason 收集有关工作器性能的实地数据。

调试 Android Vitals 标记的 WorkManager 归因的高唤醒锁定持续时间

Android Vitals 包含“过度使用部分唤醒锁定”指标,用于突出显示导致电池耗电的唤醒锁定。您可能会惊讶地发现,WorkManager 会获取唤醒锁来执行任务,如果唤醒锁超出 Google Play 设置的阈值,可能会影响应用的曝光度。如何调试工作造成的唤醒锁定时间过长的问题?您可以使用以下工具。

Android Vitals 信息中心

首先,在 Android Vitals 过多唤醒锁定信息中心内确认,唤醒锁定时间过长由 WorkManager 导致,而不是由闹钟或其他唤醒锁定导致。您可以参阅识别由其他 API 创建的唤醒锁文档,了解哪些唤醒锁是因 WorkManager 而持有的。

Perfetto

Perfetto 是一款用于分析系统轨迹的工具。如果专门使用它来调试 WorkManager,您可以查看“设备状态”部分,了解工作开始时间、运行时长以及对耗电量的影响。

在“设备状态:作业”轨迹下,您可以查看已执行的任何 worker 及其关联的唤醒锁定。

deviceState.png

Perfetto 中的“设备状态”部分,显示了 CleanupWorker 和 BlurWorker 的执行情况。

资源

如需简要了解可能遇到的其他场景的可用调试方法,请参阅 Debug WorkManager 页面

如需亲手尝试这些方法并详细了解如何调试 WorkManager,请参阅 高级 WorkManager 和测试 Codelab。

后续步骤

今天,我们不仅介绍了代码缩减,还探讨了 Android 运行时和 Jetpack Compose 如何实际渲染应用。无论是使用基准配置文件预编译关键路径,还是使用新的 Compose 1.9 和 1.10 功能来平滑滚动状态,这些工具都侧重于提升应用的流畅度。此外,我们还深入探讨了调试后台工作的最佳实践。

向 Android 提问

我们将在周五举办一场关于效果的实时 AMA。现在就使用 #AskAndroid 提出问题,并获得专家的解答。

面临的挑战

我们在周一向您发起了启用 R8 的挑战。今天,我们希望您为自己的应用生成一个基准配置文件

借助 Android Studio Otter,基准配置文件生成器模块向导可让您比以往更轻松地完成此操作。选择最关键的用户历程(即使只是应用启动和登录),然后生成配置文件。

获得该版本后,运行 Macrobenchmark 以比较 CompilationMode.None 与 CompilationMode.Partial

使用 #optimizationEnabled 在社交媒体上分享您的启动时间改进情况。

明天收看

您已使用 R8 缩减了应用大小,并使用配置文件引导型优化功能优化了运行时。但如何向利益相关者证明这些成功呢?您如何在回归问题影响生产环境之前发现它们?

欢迎明天参加第 4 天:性能分级指南,我们将详细介绍如何衡量您的成功,从 Play Vitals 中的现场数据到使用 Perfetto 进行深入的本地跟踪。

继续阅读