0%

Android性能优化之启动优化

概述

应用启动的类型可分为三种:

  • 冷启动:耗时最长,从点击应用图标到创建进程再到 UI 显示且用户可操作的全部过程。
  • 热启动:耗时最短,直接从后台切入前台,只会走 Activity 的部分生命周期方法(onRestart, onResume)。
  • 温启动:耗时介于上面两种启动方式之间,不会重走进程的创建,以及 Application 的创建和生命周期流程,只会重走 Activity 的生命周期。

关于冷启动涉及到的内容可以参考 Android-Activity启动原理, Android-Application启动原理, Android系统启动流程Android-Window机制原理。冷启动的流程简要如下:

  • 启动 App,加载空白的 StartingWindow 页面。
  • Zygote 创建 App 进程,接着启动主线程。
  • 创建 Application 实例并执行相关回调方法。
  • 创建 Activity 实例并执行相关回调方法。
  • 加载布局,绘制视图。

除去一些系统控制的步骤,我们可以优化的地方在于 Application 和 Activity 的生命周期阶段,避免做耗时任务。另外可以从绘制的角度考虑,提供的启动页 Activity 布局越简单则绘制越快,用户看到视图第一帧的耗时就越小。

启动耗时检测

Adb

1
2
3
4
5
6
$ adb shell am start -W $pkg/$activity

// 输出
ThisTime: 1952
TotalTime: 1952
WaitTime: 2039

这三个参数的含义分别是:

  • ThisTime: 启动一连串 Activity 的最后一个 Activity 的启动耗时,一般和 TotalTime 时间一样。
  • TotalTime: 创建进程 + Application 初始化 + Activity 初始化到界面显示的耗时。
  • WaitTime: 调用 context.startActivity 启动(包括 AMS 的工作) Activity 到第一帧完全显示的总耗时。

CPU Profiler

可以使用 CPU Profiler 结合 Debug 类来线下检测启动耗时,关于 CPU Profiler 的使用参考 Android性能优化之工具篇

Systrace

可以使用 Systrace 结合 Trace 类来线下检测启动耗时,关于 Systrace 的使用参考 Android性能优化之工具篇

函数插桩

为了能在线上统计到应用的启动耗时,可以通过函数插桩来统计一些初始化任务的耗时,在 Application 和 Activity 关键生命周期节点打点上报。另外可以使用 AOP 来非侵入式地实现这一功能,关于 AOP 可以参考 AOP

StartingWindow

通过 windowBackground 属性控制 StartingWindow 的背景,将被启动的 Activity 主题的 windowBackground 设置成 StartingWindow 需要的背景(图片等),然后在 Activity.onCreate 之前再通过 setTheme 方法设置回来。

这种方法只会在表面上产生一种启动很快的感觉,因为 StartingWindow 默认的白色被更改成了我们需要展示的背景图。

初始化任务优化

懒加载

对于一些启动时不必要初始化的任务或第三方库,可以在用时才懒加载。

异步初始化

将一些不必要在主线程初始化的耗时任务放在子线程异步加载。如果初始化任务需要在某个阶段完成则可以通过 CountDownLatch 来确保异步初始化任务完成后才执行下一个阶段工作。

直接使用线程池来实现异步初始化可能会有一些问题,不太好优雅地处理初始化任务之间的依赖关系,维护成本比较高。因此可以对此功能做一个封装:

  • 将任务抽象成一个 Task 类;
  • 根据任务的依赖关系将 Task 排序,生成一个链式关系,每个链都在子线程中顺序执行;
  • 多个 Task 链在不同的线程串行执行。

延迟初始化

通过 Handler.postDelayed() 来延迟初始化的方案不太好控制具体的延迟时长,如果控制不当,可能造成 APP 对用户操作事件的响应延迟。因此可以利用 Handle 机制中 IdleHandler 的特性,在主线程消息队列空闲时执行一些初始化任务,关于 IdleHandler 的原理可以参考 Android消息机制

预加载类

在 Application 中提前异步加载一些加载耗时比较长的类,可以通过替换系统的 ClassLoader 来打印类加载的时间,选择一些加载耗时比较长的类去提前异步加载。

加载类可以使用两种方法:

  • Class.forName(): 只加载类本身及其静态变量的引用类。
  • new Bean(): 会额外加载类成员变量的引用类。

启动页绘制优化

参考 Android性能优化之布局与绘制优化,对启动闪屏页和主页的绘制进行优化,减少绘制时间。