通常来说,在Android中应用的启动方式分为两种:冷启动和热启动。
1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,会从已有的进程中来启动应用,这种方式就是热启动。
1、冷启动:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,在创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
2、热启动:热启动因为会从已有的进程中来启动,所有热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。
上面说的启动是点击app的应用图标来启动的,而另外一种方式是进入最近使用的列表界面来启动应用,这种不应该叫启动,应该叫恢复。
我们看一下Google官方对应用启动优化的概述:
应用启动最慢、挑战最大的就是冷启动:系统和APP本身都有更多的工作要从头开始!
应用在冷启动之前,要执行三个任务:
1. 加载启动App;
2. App启动之后立即展示出一个空白的Window;
3. 创建App的进程;
而这三个任务执行完毕之后会马上执行一下任务:
1. 创建App对象;
2. 启动Main Thread;
3. 创建启动的Activity对象;
4. 加载View;
5. 布置屏幕;
6. 进行第一次绘制;
而一旦App进程完成了第一次绘制,系统进程就会用MainActivity替换已经展示的Background Window,此时用户就可以使用App了。
作为普通应用,App进程的创建等环节我们是无法主动控制的,可以优化的也就是Application、Activity创建以及回调等过程。同样,Google也给出了启动加速的方向:
1. 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
2. 避免在启动时做密集沉重的初始化(Heavy app initialization);
3. 定位问题:避免I/0操作、反序列化、网络操作、布局嵌套等。
备注:方向1属于治标不治本,只是表面上快;方向2\3可以真实的加快启动速度。
在上面这个启动流程中,任何一个地方有耗时操作都会拖慢我们应用的启动速度,而应用启动时间使用毫秒度量的,对于毫秒级别的快慢度量,我们还是需要去精确的测量到底应用启动花了多少时间,而根据这个时间来做衡量。
从点击应用的图标开始创建出一个新的进程知道我们看到了界面的第一帧,这段时间就是应用的启动时间。
我们要测量的也就是这段时间,测量这段时间可以通过adb shell命令的方式进行测量,这种方式测量最为精确,命令如下:
adb shell am start -W [packageName]/[packageName.MainActivity]
如:
adb shell am start -W com.×××.manager/com.×××.manager.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.×××.manager/.MainActivity }
Status: ok
Activity: com.×××.manager/.MainActivity
ThisTime: 128
TotalTime: 128
WaitTime: 171
Complete
执行成功后将返回三个测量到的时间:
1、ThisTime:是指调用过程中,最后一个Activity启动时间到这个Activity的startActivityAndWait()调用结束(最后一个启动的Activity的启动耗时)。一般和TotalTime时间一样,除非在应用启动时开了一个透明的Activity预先处理一些事再显示出主Activity,这样将比TotalTime小。
2、TotalTime:是指调用过程中,第一个Activity的启动时间到最后一个Activity的startActivityAndWait()结束(自己的所有Activity的启动耗时)。应用的启动时间,包括创建进程+Application初始化+Activity初始化到界面显示。如果启动过程中,只有一个Activity,则TotalTime等于ThisTime。
3、WaitTime:是startActivityAndWait()方法的耗时(总耗时);一般比TotalTime大,包括系统响应的耗时。
总结:如果只关心某个应用自身启动耗时,参考TotalTime;如果关心系统启动应用耗时,参考WaitTime;如果关心应用有界面Activity启动耗时,参考ThisTime。
我们知道在进程已经存在的情况下,只需要重新初始化MainActivity,这样的启动比较快,不过大多数情况下应用的启动都是冷启动,因为用户都会在任务列表中手动关闭遗留的应用进程。
按照官方文档说明:使用Activity的windowBackground主题属性来为启动的Activity提供一个简单的drawable。
Layout XML file:
Manifest file:
这样在启动的时候,会先展示一个界面,这个界面就是Manifest中设置的style,等Activity加载完毕后,再去加载Activity的界面,而在Activity的界面中,我们将主题重新设置为正常的主题,从而产生一种快的感觉。不过如上文总结这种方式其实并没有真正的加速启动过程,而是通过交互体验来优化了展示的效果。
备注:截图同样来自官方文档《Launch-Time Performance》。
懒加载机制,在窗口完成以后进行加载,具体可参考:
Android性能优化 -- 应用启动优化之DelayLoad
利用TraceView可以清楚我们每一个方法的耗时时间,极大的帮助了我们做优化工作。
1、UI渲染优化,去除重复绘制,减少UI重复绘制时间,打开设置中GPU过渡绘制开关,各界面过渡绘制不应超过2.5x;也就是打开此调试开关后,界面整体呈现浅色,特别复杂的界面,红色区域也不应该超过全屏幕的四分之一。
2、主线程中的所有SharedPreference能否在非UI线程中进行,SharedPreferences的apply()方法需要注意,因为commit()方法会阻塞IO,这个函数虽然执行很快,但是系统会有另外一个线程来负责写操作,当apply频率高的时候,该线程就会比较占用CPU资源。
3、对于首次启动的黑屏问题,对于“黑屏”是否可以设计一个.9图片替换掉,间接减少用户等待时间。
4、对于网络错误界面,友好提示界面,使用ViewStub的方式,减少UI一次性绘制的压力。
5、对于任务优先级不高的情况,可以使用懒加载机制:
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
6、Multidex的使用,也是拖慢启动速度的元凶,必须要做优化。
保证一个准则:可以异步的都异步,不可以异步的尽量延迟。让应用先启动,再操作。