应用启动分析和优化

在应用启动期间,您的应用会给用户留下第一印象。应用在启动时必须快速加载并显示用户使用应用所需的信息。如果您的应用启动时间过长,用户可能会因为等待时间过长而退出应用。

我们建议使用 Macrobenchmark 库测量启动时间。该库提供概览信息和详细的系统跟踪信息,以便您确切了解启动期间发生的情况。

系统跟踪记录提供有关设备上所发生情况的实用信息,可帮助您了解应用在启动期间执行的操作,并确定存在优化空间的方面。

如需分析应用启动,请执行以下操作:

分析和优化启动的步骤

应用在启动期间通常需要加载对最终用户至关重要的特定资源。非必需资源可等到启动完成后再加载。

为做出适当的性能权衡,请考虑以下因素:

  • 使用 Macrobenchmark 库测量每项操作所用的时间,并确定需要很长时间才能完成的区块。

  • 确认资源密集型操作是否对应用启动至关重要。如果操作可以等到应用完整绘制后再执行,则有助于最大限度地减小启动期间的资源约束。

  • 确认您是否希望相应操作在应用启动期间执行。很多时候,系统可能会从旧版代码或第三方库调用不必要的操作。

  • 尽可能将长时间运行的操作移至后台。在启动期间,后台进程仍可能会影响 CPU 使用率。

在对操作进行全面研究后,您可以在所需的加载时间与将其纳入应用启动的必要性之间做出权衡。在更改应用的工作流时,请务必留意可能会发生回归问题或破坏性更改。

您可以反复优化和测量,直到您对应用的启动时间感到满意为止。如需了解详情,请参阅使用指标检测和诊断问题

测量和分析主要操作所用的时间

在获得完整的应用启动跟踪记录后,您可以查看跟踪记录并衡量 bindApplicationactivityStart 等主要操作所用的时间。我们建议使用 PerfettoAndroid 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 数据中观察到的所有帧都会导致应用呈现速度缓慢,或者对最终用户造成任何影响。不过,观察这项指标在不同发布周期之间的变化,有助于了解总体的性能趋势。如需详细了解如何使用 gfxinfoframestats 将界面性能测量值集成到您的测试实践中,请参阅 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 的地方使用 ViewStubViewStub 是一个不可见、零大小的视图,可用于在运行时延迟膨胀布局资源。如需了解详情,请参阅 ViewStub

    如果您使用的是 Jetpack Compose,则可以使用状态获取与 ViewStub 类似的行为,以延迟加载某些组件:

    var shouldLoad by remember {mutableStateOf(false)}
    
    if (shouldLoad) {
     MyComposable()
    }
    

    修改 shouldLoad,在条件块内加载可组合函数:

    LaunchedEffect(Unit) {
     shouldLoad = true
    }
    

    这会触发重组,并包含第一个代码段中的条件代码块内的代码。