Improving performance
Tools and libraries
Baseline Profiles
在应用程序或库中实施 Baseline Profiles 是提高性能的最有效方法。 它可以显着优化应用程序启动时间,减少缓慢的渲染,并提高最终用户的性能。
App Startup Library
App Startup Library 可以让你进一步优化应用启动体验。 库开发人员和应用程序开发人员都可以使用 App Startup Library 来简化启动序列并优化启动操作。
Optimize for low-RAM devices
性能改进从头开始。 通过针对入门级设备进行优化,可以提高所有设备类别的效率。 用户在使用内存受限的设备时更有可能遇到应用程序启动延迟、应用程序无响应 (ANR) 或应用程序崩溃等问题。 考虑到该细分市场来开发、测试和基准测试您的应用程序,为应用程序创建一个高性能的基础。
Android(Go 版)是 Android 平台操作系统的配置,可为低 RAM 设备提供优化的体验。 要了解有关提高入门级设备稳定性和性能的更多信息,请参阅优化 Android(Go 版)。
Baseline Profiles
Baseline Profiles 通过避免对包含的代码路径进行解释和 just-in-time(JIT) 编译步骤,将代码执行速度从首次启动时提高了约 30%。
通过在应用程序或库中传送 Baseline Profile,Android Runtime(ART) 可以通过 Ahead-of-Time(AOT) 编译优化指定的代码路径,从而为每个新用户和每个应用程序更新提供性能增强。 此 Profile Guided Optimization(PGO) 可让应用程序从首次启动起优化启动、减少交互卡顿并提高用户的整体运行时性能。
Benefits of Baseline Profiles
Baseline Profiles 使所有用户交互(例如应用程序启动、在屏幕之间导航或滚动内容)从第一次运行时开始变得更加顺畅。 通过提高应用程序的速度和响应能力,Baseline Profiles 可以带来更多的每日活跃用户和更高的平均回访率。
Baseline Profiles 通过提供常见的用户交互来帮助指导应用程序启动之外的优化,从而从首次启动时改善应用程序 runtime。 Guided AOT 编译不依赖于用户设备,每个版本都可以在开发计算机(而不是移动设备)上完成一次。 通过使用 Baseline Profile 发布版本,应用程序优化的速度比单独依赖云配置文件要快得多。
Startup profiles
Startup profiles 与 Baseline Profiles 类似,但不同之处在于它们在编译时使用,而不是用于设备上优化。 Startup profiles 用于优化 DEX 文件的布局以缩短启动时间。 Startup profiles 中标识的代码放入主 classes.dex
文件中,其他代码放入单独的 DEX 文件中。 这可以通过减少应用程序启动期间的页面错误数量来缩短启动时间。
Get started
要开始优化现有应用程序的性能,请参阅 Create Baseline Profiles。
Minimum recommended stable versions
依赖链提供稳定和开发版本。 要生成并安装 Baseline Profile,请使用以下受支持版本或更高版本的 Android Gradle plugin、Macrobenchmark 库和 Profile Installer。 这些依赖关系在不同的时间是必需的,并且作为工具链一起工作以实现最佳的 Baseline Profile。
- Android Gradle plugin:
com.android.tools.build:8.1.0
- Macrobenchmark library:
androidx.benchmark:benchmark-macro-junit4:1.1.1
- Profile Installer:
androidx.profileinstaller:profileinstaller:1.3.1
Profile generation example
以下是一个示例类,用于创建应用程序启动的 Baseline Profile,以及使用推荐的 Macrobenchmark 库的多个导航和滚动事件:
1 |
|
What to include
在应用程序中使用 Baseline Profiles 时,可以包含应用程序启动代码和常见的用户交互,例如屏幕之间的导航或滚动。 还可以收集注册、登录或支付等整个流程。 认为重要的任何用户旅程都可以通过提高其运行时性能而从 Baseline Profiles 中受益。
如果正在尝试使用不同的方法来提高性能,请考虑在实验的两个方面都包含 Baseline Profiles。 通过这样做,可以确保所有用户一致地运行已编译的代码,从而使结果更易于解释。
库可以提供自己的 Baseline Profiles 并随版本一起发布以提高应用程序性能。
How Baseline Profiles work
在开发应用程序或库时,请考虑定义 Baseline Profiles 以涵盖渲染时间或延迟很重要的常见用户交互。 它们的工作原理如下:
- 为应用程序生成人类可读的 profile rules,并在应用程序中编译为二进制形式。 可以在
asset/dexopt/baseline.prof
中找到它们。 然后,可以照常将 AAB 上传到 Google Play。 - Google Play 会处理该 profile 并将其与 APK 一起直接发送给用户。 在安装过程中,ART 对 profile 中的方法执行 AOT 编译,从而使这些方法执行速度更快。 如果 profile 包含应用程序启动或帧渲染期间使用的方法,用户可能会体验到更快的启动时间并减少卡顿。
- 此流程与 Cloud Profiles 聚合配合,根据应用程序随时间的实际使用情况微调性能。
Cloud Profiles
Cloud Profiles 提供了另一种形式的 PGO —— 由 Google Play Store 聚合并分发以供安装时编译 —— 与 Baseline Profiles 一起。
虽然 Cloud Profiles 是由现实世界中的用户与应用程序的交互驱动的,但它们在更新后需要几天到几周的时间才能分发,从而限制了它们的可用性。 在配置文件完全分发之前,应用程序性能对于新应用程序或更新应用程序的用户来说并不是最佳的。 此外,Cloud Profiles 仅支持运行 Android 9(API 级别 29)或更高版本的 Android 设备,并且仅适用于拥有足够大用户群的应用程序。
跨 Android 版本的编译行为
Android 平台版本使用不同的应用程序编译方法,每种方法都有相应的性能权衡。 Baseline Profiles 通过为所有安装提供配置文件来改进以前的编译方法。
9 (API level 28) and higher – Partial AOT (Baseline + Cloud Profile) – Play 在应用安装期间使用 Baseline Profiles 来优化 APK 和 Cloud profiles(如果可用)。 安装后,ART 配置文件将上传到 Play、聚合,然后在其他用户安装或更新应用程序时作为云配置文件提供给他们。
Solutions for possible issues
以下是可能的问题和解决方案,或者正在开发解决方法的问题:
- 从 app bundle 构建 APK 时, Baseline profiles 未正确打包。 要解决此问题,请应用
com.android.tools.build:gradle:7.3.0
或更高版本 - 仅针对主
classes.dex
文件正确打包 Baseline profiles。 这会影响具有多个 .dex 文件的应用程序。 要解决此问题,请应用com.android.tools.build:gradle:7.3.0
或更高版本。 - 不允许在
user
(非 root)构建上重置 ART profile 缓存。 为了解决这个问题,androidx.benchmark:benchmark-macro-junit4:1.1.0
包含一个修复程序,可以在基准测试期间重新安装应用程序 - Android Studio Profiler 在分析应用程序时不会安装 Baseline Profiles
- 非 Gradle 构建系统(例如 Bazel 或 Buck)不支持将 Baseline Profiles 编译进输出 APK。
- 非 Google Play 商店应用分发渠道可能不支持在安装时使用 Baseline Profiles。 通过这些渠道安装的应用程序的用户只有在后台 dexopt 运行后才能看到好处——这很可能在一夜之间。
- 构建编译器仅接受
src/main
文件夹中的一个benchmark-prof.txt
,并且不反映不同风格或构建类型的文件。 这一点正在积极改进中。 - 电池优化可能会干扰 profile 安装。 为了帮助确保有效安装 profile,请禁用基准设备中的任何电池优化。
- 基准测试和生产环境之间的性能改进可能有所不同。 发生这种情况是因为本地基准测试在启用或禁用 Baseline Profiles 的情况下衡量性能。 在生产应用程序中,当将应用程序的新部分添加到 Baseline Profiles 时,测量是增量的,其中部分已经通过贡献库进行了分析。
app startup
在应用程序启动过程中,会给用户留下第一印象。 应用程序启动应该花费尽可能少的时间来加载和显示用户开始使用应用程序所需的信息。
我们建议使用 Macrobenchmark 库来衡量启动情况。 该库为您提供总体信息和详细的系统跟踪,以准确了解启动过程中发生的情况。
System traces 提供有关设备上发生的情况的有用信息,让您了解应用程序在启动期间正在执行的操作,并帮助您识别潜在的优化区域。
Steps to analyze and optimize startup
应用程序通常需要在启动期间加载对最终用户至关重要的特定资源。 其他时候,可以等到启动完成后再加载非必要的资源。 请考虑以下列表以帮助做出正确的性能权衡:
- 使用 Macrobenchmark 库来测量每个操作所花费的时间,并识别需要很长时间才能完成的块。
- 确认资源密集型操作对于应用程序启动至关重要。 如果操作可以等到应用程序完全绘制,则可以帮助最大限度地减少启动时的资源限制。
- 确保希望此操作在应用程序启动时运行。 很多时候,不必要的操作会从遗留代码或第三方库中调用。
- 如果可能的话,尝试将长时间运行的操作移至后台。 请注意,后台进程在启动期间仍然会影响 CPU 使用率。
- 现在您已经充分研究了该操作,您可以在加载时间和将其包含在应用程序启动中的必要性之间做出权衡决定。 请记住在更改应用程序的工作流程时考虑回归或重大更改的可能性。
- 优化并重新测量,直到您对应用程序的启动时间感到满意。
Measure and analyze time spent in major operations
获得完整的应用程序启动 trace 后,请查看 trace 并测量主要操作(如 bindApplication
或 activityStart
)所花费的时间。 我们建议使用 Perfetto 或 Android Studio Profiler 来分析这些 trace。
查看应用程序启动期间花费的总时间,以确定以下操作:
- 占用较大的时间范围并且可以优化。 每一毫秒都对性能至关重要。 例如,查找 choreographer draw times、layout inflation times、library load times、binder transactions 或 resource load times。 作为一般开始,请查看所有耗时超过 20 毫秒的操作。
- 阻塞主线程。 有关详细信息,请参阅浏览 systrace 报告
- 不应在启动期间运行。
- 可以等到第一帧绘制完成后。
TTID and TTFD
测量 Time to Initial Display(TTID),即应用程序生成第一帧所花费的时间。 但是,这并不一定反映用户可以开始与您的应用交互的时间。 Time to Full Display(TTFD) 指标在测量和优化拥有完全可用的应用程序状态所需的代码路径时更有用。
有关应用程序 UI 完全绘制时的报告策略,请参阅提高启动计时准确性。
您应该针对 TTID 和 TTFD 进行优化,因为两者在各自的领域都很重要。 较低的 TTID 可帮助用户看到应用程序实际上正在启动。 保持 TTFD 简短对于确保用户可以快速开始与应用程序交互非常重要。
分析整体线程状态
选择应用程序启动时间并查看 overall thread slices。 Studio Profiler Traceviewer 和 Perfetto 等工具提供了主线程的详细概述以及每个阶段花费了多少时间。 主线程应该始终响应。
识别主线程 sleeping 状态的主要块
如果花费大量时间 sleeping,这可能是应用程序的主线程等待工作完成的结果。 如果您有一个多线程应用程序,请确定主线程正在等待的线程,并考虑优化这些操作。 它还有助于确保没有不必要的锁争用导致关键路径延迟。
减少主线程阻塞和不间断 sleep
查找进入阻塞状态的主线程的每个实例。 Perfetto 和 Studio Profiler 在线程状态时间轴上通过橙色指示器显示这一点。 确定操作,探索这些操作是否是预期的或可以避免的,并在必要时进行优化。
与 IO 相关的可中断 sleep 确实是一个很好的改进机会。 其他执行 IO 的进程,即使它们是不相关的应用程序,也可以与顶级应用程序正在执行的 IO 竞争。
Improve startup time
确定优化机会后,您可以探索一些可能的解决方案来帮助缩短启动时间:
- 延迟加载内容并异步加载,以加快 TTID。
- 最小化进行 binder 调用的调用函数。 如果它们是不可避免的,请确保通过缓存值来优化这些调用,而不是重复调用或将非阻塞工作移至后台线程。
- 为了使应用程序启动速度更快,可以尽快向用户显示需要最少渲染的内容,直到屏幕的其余部分完成加载。
Analyze UI performance
应用程序启动包括 splash screen 和主页的加载时间。 为了优化应用程序启动,可以检查跟踪以了解绘制 UI 所需的时间。
限制初始化工作
某些 frame 可能比其他 frame 需要更长的时间来加载。 这些对于应用程序来说被认为是昂贵的 draw。
- 优先考虑缓慢的布局过程并选择这些进行改进。
- 来自 Perfetto 的每个警告和来自 Systrace 的警报都可以通过添加自定义跟踪事件进行调查,以减少昂贵的绘制和延迟。
Measure frame data
测量帧数据的方法有多种。 四种主要的收集方法是:
- 使用
dumpsys gfxinfo
进行本地收集. 请注意,并非 dumpsys 数据中观察到的所有帧都可能导致应用程序渲染缓慢或对最终用户产生任何影响。 然而,这是一个很好的衡量标准,可以查看不同的发布周期以了解性能的总体趋势。 要了解有关使用 gfxinfo 和 framestats 将 UI 性能测量集成到测试实践中的更多信息,请转到测试 UI 性能 - 使用 JankStats 进行字段收集. 使用 JankStats 库从应用程序的特定部分收集帧渲染时间,并使用记录和分析数据。
- 在使用 Macrobenchmark(Perfetto under the hood)的测试中
- Perfetto FrameTimeline. 在 Android 12(API 级别 31)上,您可以从 Perfetto 跟踪中收集帧时间线指标,以了解导致帧丢失的工作。 这可能是诊断丢帧原因的第一步。
- Android Studio Profiler for jank detection
Check main activity load time
应用程序的 main activity 可能包含从多个来源加载的大量信息。 检查 home Activity
布局,具体查看 home Activity 的 Choreographer.onDraw 方法。
- 使用
reportFullyDrawn
向系统报告您的应用程序现在已完全绘制以进行优化。 - 使用 StartupTimingMetric 和 Macrobenchmark 库来测量 activity 和应用程序启动。
- Look at frame drops.
- 识别需要很长时间渲染或测量的布局
- 识别需要很长时间加载的 assets。
- 识别启动过程中不必要的布局。
以下是一些可供探索的其他可能解决方案:
- 使您的初始布局尽可能简单。 有关更多信息,请参阅优化布局层次结构。
- 在布局不能立即
VISIBLE
的情况下使用ViewStub
。 ViewStub 是一个不可见的、零大小的 View,可用于在运行时延迟加载布局资源。 有关详细信息,请参阅 ViewStub。 - 添加自定义跟踪点以提供有关丢帧和复杂布局的更多信息。
- 最小化启动期间加载的位图资源的数量和大小。
App Standby Buckets
Android 9(API 级别 28)及更高版本支持 App Standby Buckets。 App Standby Buckets 可帮助系统根据应用程序的最近使用时间和频率对应用程序的资源请求进行优先级排序。 根据应用程序使用模式,每个应用程序都会被放置在五个优先级 buckets 之一中。 系统根据应用程序所在的 buckets 来限制每个应用程序可用的设备资源。
Priority buckets
系统动态地将每个应用程序分配到优先级 bucket,并根据需要重新分配应用程序。 该系统可能依赖于预加载的应用程序,该应用程序使用机器学习来确定每个应用程序的使用可能性,并将应用程序分配到适当的 bucket。
如果设备上不存在该系统应用程序,则系统默认根据应用程序的最近使用时间对应用程序进行排序。 更活跃的应用程序会被分配给具有更高优先级的 bucket,从而为应用程序提供更多的系统资源。 特别是,bucket 决定应用程序 job 运行的频率以及应用程序触发警报的频率。 这些限制仅在设备使用电池供电时适用。 当设备充电时,系统不会施加这些限制。
注意:每个制造商都可以针对如何将 non-active 应用程序分配到 bucket 设置自己的标准。 不要试图影响您的应用分配到的 bucket。 相反,应专注于确保您的应用程序在其所在的 bucket 中表现良好。要找出您的应用程序所在的 bucket,请调用 UsageStatsManager.getAppStandbyBucket()
。
The priority buckets are the following:
- Active: app is being used or was used very recently.
- Working set: app is in regular use.
- Frequent: app is often used but not daily.
- Rare: app isn’t frequently used.
- Restricted: app consumes a lot of system resources or might exhibit undesirable behavior.
除了这些优先级 bucket 之外,还有一个特殊的 never bucket,适用于已安装但从未运行的应用程序。 系统对这些应用程序施加了严格的限制。
注意:Doze 豁免列表中的应用程序不受基于 App Standby Bucket 的限制。
以下描述针对非预测情况。 相比之下,当预测使用机器学习来预测行为时,会根据用户的下一步操作而不是根据最近的使用情况来选择 bucket。 例如,最近使用的应用程序可能最终会出现在 rare 的存储桶中,因为机器学习预测该应用程序可能会在几个小时内不会被使用。
App startup time
理解不同的 app startup 状态
应用程序启动可以以三种状态之一进行,每种状态都会影响应用程序对用户可见所需的时间:cold start, warm start, or hot start。 在 cold start 中,您的应用程序从头开始。 在其他状态下,系统需要将正在运行的应用程序从后台带到前台。 我们建议您始终基于 cold start 的假设进行优化。 这样做还可以提高 warm start 和 hot start 的性能。
为了优化您的应用程序以实现快速启动,了解系统和应用程序级别发生的情况以及它们在每种状态下如何交互非常有用。
Cold start
cold start 是指应用程序从头开始:直到这次启动,系统进程才创建应用程序的进程。 cold start 发生在以下情况:您的应用程序自设备启动以来首次启动,或者自系统终止应用程序以来首次启动。 这种类型的启动在最小化启动时间方面提出了最大的挑战,因为系统和应用程序比其他启动状态有更多的工作要做。
cold start 开始时,系统执行三个任务。 这些任务是:
- Loading and launching the app.
- Displaying a blank starting window for the app immediately after launch.
- Create the app process.
一旦系统创建了应用程序进程,应用程序进程就会负责接下来的阶段:
- Creating the app object.
- Launching the main thread.
- Creating the main activity.
- Inflating views.
- Laying out the screen.
- Performing the initial draw.
一旦应用程序进程完成第一次绘制,系统进程就会交换当前显示的背景窗口,将其替换为 main activity。 此时,用户可以开始使用该应用程序。
创建应用程序和创建 activity 期间可能会出现性能问题。
Application creation
当 application 启动时,空白的启动窗口将保留在屏幕上,直到系统第一次完成应用程序的绘制。 此时,系统进程会交换应用程序的启动窗口,允许用户开始与应用程序交互。
如果在自己的应用程序中重写了 Application.onCreate()
,系统将调用 app object 上的 onCreate()
方法。 然后,应用程序会生成主线程(也称为 UI 线程),并为其创建 main Activity。
从此时起,系统级和应用程序级进程将按照应用程序生命周期阶段进行。
Activity creation
应用程序进程创建 Activity 后,该 Activity 会执行以下操作:
- Initializes values.
- Calls constructors.
- Calls the callback method, such as
Activity.onCreate()
, appropriate to the current lifecycle state of the activity.
通常,onCreate()
方法对加载时间影响最大,因为它执行开销最高的工作:loading and inflating views,以及初始化 activity 运行所需的对象。
Warm start
warm start 包含 cold start 期间发生的某些操作子集; 同时,它比 hot start 带来更多的开销。 有许多潜在状态可以被视为 warm start。 例如:
- 用户退出您的应用程序,但随后重新启动它。 该进程可能会继续运行,但应用程序必须通过调用
onCreate()
从头开始重新创建 Activity。 - 系统从内存中逐出您的应用程序,然后用户重新启动它。 进程和 activity 需要重新启动,但任务可以从传递到
onCreate()
的 saved instance state bundle 中受益。
Hot start
应用程序的 hot start 比 cold start 更简单且开销更低。 在 hot start 中,系统所做的就是将 activity 带到前台。 如果应用程序的所有 activity 仍驻留在内存中,则应用程序可以避免重复对象初始化、layout inflation 和渲染。
但是,如果响应内存修剪事件(例如 onTrimMemory()
)而清除了某些内存,则需要重新创建这些对象以响应 hot start 事件。
hot start 显示与 cold start 场景相同的屏幕行为:
系统进程会显示空白屏幕,直到应用程序完成对 Activity 的渲染。
使用 metrics 来检测和诊断问题
为了正确诊断启动时间性能,您可以跟踪显示应用程序启动时间的 metrics。 Android 提供了多种方法来让您知道您的应用程序有问题,并帮助您诊断问题。 Android Vitals 可以提醒您出现问题,诊断工具可以帮助您诊断问题。