在应用启动期间,您的应用会给用户留下第一印象。应用在启动时必须快速加载并显示用户使用应用所需的信息。如果应用启动时间过长,用户可能会因为等待太久而退出应用。
建议使用 Macrobenchmark 库来测量启动时间。该库提供概览信息和详细的系统跟踪信息,方便您确切了解启动期间发生的情况。
系统跟踪记录提供有关设备上所发生情况的实用信息,可帮助您了解应用在启动期间执行的操作,并确定需要优化的方面。
如需分析应用启动,请执行以下操作:
- 设置在应用启动期间记录跟踪数据的环境。
- 了解系统跟踪。
- 使用 Android Studio 性能分析器或 Perfetto 浏览跟踪报告。
分析和优化应用启动的步骤
应用在启动期间通常需要加载对最终用户至关重要的特定资源。非必需资源可等到启动完成后再加载。
为权衡性能,请考虑以下因素:
使用 Macrobenchmark 库测量每项操作所用的时间,并找出需要很长时间才能完成的区块。
确认资源密集型操作是否对应用启动至关重要。如果操作可以等到应用完全启动后再执行,则有助于最大限度地减少启动期间的资源约束。
确认您是否希望相应操作在应用启动期间执行。很多时候,系统可能会从旧版代码或第三方库调用不必要的操作。
尽可能将长时间运行的操作移至后台。在启动期间,后台进程仍可能会影响 CPU 使用率。
在对操作进行全面研究后,您可以在所需的加载时间与将其纳入应用启动的必要性之间做出权衡。在更改应用的工作流时,请务必留意可能会发生回归问题或破坏性更改。
您可以反复优化和测量,直到对应用的启动时间感到满意为止。如需了解详情,请参阅使用指标检测和诊断问题。
测量和分析主要操作所用的时间
在获得完整的应用启动跟踪记录后,您可以查看跟踪记录并衡量 bindApplication
或 activityStart
等主要操作所用的时间。建议使用 Perfetto 或 Android Studio 性能分析器来分析这些跟踪记录。
通过查看应用启动期间所用的总时间,可以找出具有以下特点的操作:
- 占用大量时间,可以进行优化。每一毫秒都关乎性能。例如,您可以查看
Choreographer
绘制时间、布局膨胀时间、库加载时间、Binder
事务或资源加载时间。一般而言,请先检查所有耗时超过 20 毫秒的操作。 - 阻塞主线程。如需了解详情,请参阅浏览 Systrace 报告。
- 不需要在启动期间运行。
- 可以等到第一帧绘制完毕后再执行。
进一步研究各个跟踪记录,找出性能上有待改进的方面。
找出主线程上开销大的操作
最好避免在主线程上执行开销大的操作,例如文件 I/O 和网络访问。这一点在应用启动期间也很重要,因为在主线程上执行开销大的操作可能会导致应用无响应,并延迟其他关键操作。StrictMode.ThreadPolicy
可以帮助您找出那些在主线程上执行大开销操作的情况。最好在调试 build 中启用 StrictMode
,以尽早发现问题,具体如下例所示:
Kotlin
class MyApplication : Application() { override fun onCreate() { super.onCreate() ... if (BuildConfig.DEBUG) StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build() ) ... } }
Java
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); ... if(BuildConfig.DEBUG) { StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build() ); } ... } }
您可以使用 StrictMode.ThreadPolicy
在所有调试 build 上启用线程政策,并在检测到违反线程政策的行为时触发应用崩溃,这样就不容易遗漏违反线程政策的行为。
TTID 和 TTFD
如需了解应用生成第一帧所用的时间,请测量初步显示所用时间 (TTID)。不过,此指标并不一定能反映应用从启动到用户可与之互动所经历的时间。完全显示所用时间 (TTFD) 指标在测量和优化达到完全可用的应用状态所需的代码路径方面更为有用。
如需了解应用界面完全绘制时的报告策略,请参阅提高启动时间精确性。
请针对 TTID 和 TTFD 进行优化,因为两者在各自的领域都很重要。较短的 TTID 有助于用户知道应用是真的在启动。而保持较短的 TTFD,则是确保用户能快速与应用互动的关键。
分析整体线程状态
您可以选择应用启动时间并查看整体线程切片。主线程需要始终能及时响应。
通过 Android Studio 性能分析器和 Perfetto 等工具可详细了解主线程以及每个阶段所用的时间。如需详细了解如何直观呈现 Perfetto 跟踪记录,请参阅 Perfetto 界面文档。
找出主线程处于休眠状态的主要区块
如果应用长时间处于休眠状态,可能是因为应用的主线程要等待作业完成。对于多线程的应用,请确定主线程在等待的线程,并考虑优化相关操作。这还有助于确保关键路径不会因为不必要的锁定争用而出现延迟。
减少主线程阻塞和不可中断的休眠状态
找出主线程每次进入阻塞状态的实例。Perfetto 和 Studio 性能分析器会在线程状态时间轴上用橙色指示器指明这种情况。找出这类操作,查明这些操作是符合预期的,还是可以避免的,并根据需要进行优化。
与 IO 相关的可中断休眠是可以改进的。其他执行 IO 的进程(即使是无关的应用)都能与顶端应用在执行的 IO 争用资源。
缩短启动时间
找到可优化之处后,您可以考虑采用下列解决方案来缩短启动时间:
- 延迟并异步加载内容,从而缩短 TTID。
- 尽量减少调用 binder 的调用函数。如果这类函数必不可少,请确保优化其调用,可采用缓存值的方式,而不要重复调用,还可以将非阻塞性作业移至后台线程中。
- 为了让应用启动看起来更快,可以尽快对用户显示需要最少渲染进程的内容,让他们不必空等界面的其余部分载入。
- 创建启动配置文件并将其添加到应用。
- 使用 Jetpack 应用启动库简化应用启动期间的组件初始化。
分析界面性能
应用启动过程包括启动画面和首页的加载时间。为了优化应用启动,请检查跟踪记录,了解绘制界面所用的时间。
限制初始化作业
某些帧的加载时间可能比其他帧更长。这类帧可视为在应用启动期间具有较高的绘制开销。
如需优化初始化作业,请执行以下操作:
- 优先考虑并改进缓慢的布局传递。
- 通过添加自定义跟踪事件来分析来自 Perfetto 的警告和来自 Systrace 的提醒,以减少开销较高的绘制和延迟。
测量帧数据
您可以通过多种方式测量帧数据。以下是五种主要的帧数据收集方法:
- 使用
dumpsys gfxinfo
进行本地收集:并非 dumpsys 数据中观察到的所有帧都会导致应用呈现速度缓慢,或者对最终用户造成任何影响。不过,观察这项指标在不同发布周期之间的变化,有助于了解总体的性能趋势。如需详细了解如何使用gfxinfo
和framestats
将界面性能测量值集成到您的测试实践中,请参阅 Android 应用测试基础知识。 - 使用 JankStats 进行字段收集:使用 JankStats 库从应用的特定部分收集帧呈现时间,并记录和分析数据。
- 在测试中使用 Macrobenchmark(在后台使用 Perfetto)
- Perfetto FrameTimeline: 在 Android 12(API 级别 31)中,您可以收集帧时间轴 指标 从 Perfetto 跟踪记录中找出导致帧丢失的工作如果您想诊断丢帧的原因,不妨从这步入手。
- 使用 Android Studio 性能分析器检测卡顿
查看主 activity 的加载时间
应用的主 activity 可能包含从多个来源加载的大量信息。请检查首页 Activity
布局,特别是查看首页 activity 的 Choreographer.onDraw
方法。
- 使用
reportFullyDrawn
向系统报告您的应用现已完全绘制,达到优化目的。 - 搭配使用
StartupTimingMetric
和 Macrobenchmark 库来测量 activity 和应用启动情况。 - 查看丢帧情况。
- 找出在渲染或测量方面很耗时的布局。
- 找出加载用时较长的资源。
- 找出在启动期间原本没必要膨胀的布局。
考虑使用以下可优化主 activity 加载时间的解决方案:
- 尽量简化初始布局。如需了解详情,请参阅优化布局层次结构。
- 添加自定义跟踪点,以获取有关丢帧和复杂布局的更多信息。
- 尽量缩减启动期间加载的位图资源的数量和大小。
在布局不会立即
VISIBLE
的地方使用ViewStub
。ViewStub
是一个不可见、零大小的视图,可用于在运行时延迟膨胀布局资源。如需了解详情,请参阅ViewStub
。如果您使用的是 Jetpack Compose,则可以使用状态获取与
ViewStub
类似的行为,以延迟加载某些组件:var shouldLoad by remember {mutableStateOf(false)} if (shouldLoad) { MyComposable() }
修改
shouldLoad
,在条件块内加载可组合函数:LaunchedEffect(Unit) { shouldLoad = true }
这会触发重组,并包含第一个代码段中的条件代码块内的代码。
为您推荐
- 注意:当 JavaScript 处于关闭状态时,系统会显示链接文字
- 捕获 Macrobenchmark 指标
- 应用性能测量概览 * 冻结的帧