android应用启动性能优化

Mar 15 2016

冷启动与热启动

android 中应用一般分为:“冷启动”与“热启动”。

冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。

热启动:当启动应用时,后台已有该应用的进程(例:按 back 键、home 键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。

冷启动因为系统会重新创建一个进程分配给它,所以会先创建并初始化 Application 类,然后创建并初始化应用的主 Activity 类,最后显示在用户的屏幕上。

热启动则不需要创建新进程,也不会重新初始化 Application 类,直接创建Activity 最后显示。

android应用启动流程

安卓应用从用户按下图标到应用界面展示在用户眼前一般都是经过如下过程:

系统为应用创建新进程 -> 创建 Application 类 -> 调用 application 的 onCreate 方法 -> 创建 Activity 类 -> 调用 Activity 的 onCreate, onStar, onResume 方法 -> contentView 的 measure/layout/draw -> 显示主界面

安卓应用程序框架层中,是由 ActivityManagerService 来负责创建新的进程,它是由系统在启动过程中创建的。AMS 通过从 Zygote fork 出子进程作为应用程序进程,具体启动过程比较复杂,不在本文的讨论讨论范围之内。

优化冷启动时间

前面有提到冷启动与热启动的区别在于热启动不需要创建新进程和不需要初始化 Application 类,由于创建新进程由框架接管,我们可以做文章的就只有 Application 的 onCreate 方法了。

由于每次冷启动都需要重新初始化 Application 类,所以在它的 onCreate 方法中应该避免加入耗时的操作。一般应用的一些全局的 Manager 需要在 Application 的启动过程中初始化,应尽量使用异步初始化。有一个比较容典型的例子是 SharedPreference 的初始化。SharedPreference 的实现是基于 xml 文件来实现的,每次初始化 SharedPreference 对象的时候都需要把对应的文件中的内容全部读到内存中,如果文件比较大的话整个过程是比较耗时的,最好通过异步线程来进行这个操作。还有一个例子是使用 Dagger 进行依赖注入时,在 onCreate 进行对象注入也是应该避免的。这篇文章里有提到对应的2个解决方法,懒加载和异步创建。

避免冷启动

除了减轻 Application 的 onCreate 中的操作之外,避免冷启动也是一个行之有效的方法。大家可以看一下微信和手机qq 的主界面按 back 键后再次点击 icon 是不需要重新初始化应用的。奥秘就在于他们的主 Activity 中拦截了 back 键的事件,然后执行自己的逻辑把主 Activity 压入栈中,效果和按下 home 键是一样的。

我们来以微信为例做一个实验,首先将系统中的活动程序全部杀死,然后打开微信会看到其经典的月球 loading 画面,点击 back 键,命令行运行下面这条命令可以将系统的 activity 栈中的信息打印到控制台中

1
adb shell dumpsys activity activities

可以看到打印出来的信息中包含如下几行:

1
2
3
4
5
6
7
Running activities (most recent first):
TaskRecord{394254cd #5931 A=com.tencent.mm U=0 sz=1}
Run #1: ActivityRecord{294e944 u0 com.tencent.mm/.ui.LauncherUI t5931}
TaskRecord{3e45135 #5911 A=com.android.incallui U=0 sz=1}
Run #0: ActivityRecord{34f40af3 u0 com.android.incallui/.InCallActivity t5911}

mLastPausedActivity: ActivityRecord{294e944 u0 com.tencent.mm/.ui.LauncherUI t5931}

微信的应用程序前台进程并没有被杀死,它的主 Activity 仅仅是被暂停了,所以启动的时候能够迅速的恢复主界面,而不会看到 loading 的界面。

那么微信是如何做到的呢,具体实现有两种:一种是通过调用系统 Launcher 的 Intent,另一种是调用 Activity 的 AmoveTaskToBack 方法将 Activity 的 Task 压入 Activity 任务栈。

1
2
3
4
5
6
public class MainActivity extends Activity {
@Override
public void onBackPressed() {
moveTaskToBack(true);
}
}

这样用户再次点击 Launcher 上的 icon 时,只是恢复之前被暂停的 Activity 而已,直接从 Activity 的 onResume 方法开始调用,而不需要去创建新进程并初始化 Application 和 Activity。当然,如果用户点击 back 键到恢复主 Activity 之间打开了过多的应用,系统内存吃紧的话还是会把我们的应用进程干掉的。



Kommentare: