启动分析
启动过程分析
-
T1预览窗口显示。系统在拉起微信进程之前,会先根据微信的Theme属性创建预览窗口。
当然如果我们禁用预览窗口或者将预览窗口指定为透明,用户在这段时间依然看到的是桌面。
-
T2闪屏显示。在微信进程和闪屏窗口页面创建完毕,并且完成一系列inflate view、onmeasure、onlayout等准备工作后,用户终于可以看到熟悉的“小地球”。
-
T3主页显示。在完成主窗口创建和页面显示的准备工作后,用户可以看到微信的主界面。
-
T4界面可操作。在启动完成后,微信会有比较多的工作需要继续执行,例如聊天和朋友圈界面的预加载、小程序框架和进程的准备等。在这些工作完成后,用户才可以真正开始愉快地聊天。
启动问题分析
- 问题1:点击图标很久都不响应
如果我们禁用了预览窗口或者指定了透明的皮肤,那用户点击了图标之后,过了几秒还是停留在桌面,看起来就像没有点击成功,这在中低端机中更加明显。
- 问题2:首页显示太慢
现在应用启动流程越来越复杂,所有准备工作都需要集中在启动阶段完成。
- 问题3:首页显示后无法操作。
启动优化
优化工具
Traceview性能损耗太大,得出的结果并不真实;Nanoscope非常真实,不过暂时只支持Nexus 6P和x86模拟器,无法针对中低端机做测试;Simpleperf的火焰图并不适合做启动流程分析;systrace可以很方便地追踪关键系统调用的耗时情况,但是不支持应用程序代码的耗时分析。
在卡顿优化中提到“systrace + 函数插桩
”似乎是比较理想的方案,而且它还可以看到系统的一些关键事件,例如GC、System Server、CPU调度等。
优化方式
分为闪屏优化、业务梳理、业务优化、线程优化、GC优化和系统调用优化。
- 闪屏优化
今日头条把预览窗口实现成闪屏的效果
,这样用户只需要很短的时间就可以看到“预览闪屏”。这种完全“跟手”的感觉在高端机上体验非常好,但对于中低端机,会把总的的闪屏时间变得更长。
微信做的另外一个优化是合并闪屏和主页面的Activity
,减少一个Activity会给线上带来100毫秒左右的优化。但是如果这样做的话,管理时会非常复杂
,特别是有很多例如PWA、扫一扫这样的第三方启动流程的时候。
- 业务梳理
通过梳理之后,剩下的都是启动过程一定要用的模块。这个时候,我们只能硬着头皮去做进一步的优化。优化前期需要“抓大放小”,先看看主线程究竟慢在哪里。最理想是通过算法进行优化,例如一个数据解密操作需要1秒,通过算法优化之后变成10毫秒。退而求其次,我们要考虑这些任务是不是可以通过异步线程预加载实现,但需要注意的是过多的线程预加载会让我们的逻辑变得更加复杂。
业务优化做到后面,会发现一些架构和历史包袱会拖累我们前进的步伐。比较常见的是一些事件会被各个业务模块监听,大量的回调导致很多工作集中执行
,部分框架初始化“太厚”,例如一些插件化框架,启动过程各种反射、各种Hook
,整个耗时至少几百毫秒。
- 线程优化
当然我们也希望每个线程都开足马力向前跑,而不是作为接力棒
。所以线程的优化主要在于减少CPU调度带来的波动,让应用的启动时间更加稳定。
从具体的做法来看,线程的优化一方面是控制线程数量,线程数量太多会相互竞争CPU资源
,因此要有统一的线程池,并且根据机器性能来控制数量。
线程切换的数据我们可以通过卡顿优化中学到的sched文件查看,这里特别需要注意nr_involuntary_switches被动切换的次数。
proc/[pid]/sched:
nr_voluntary_switches:
主动上下文切换次数,因为线程无法获取所需资源导致上下文切换,最普遍的是IO。
nr_involuntary_switches:
被动上下文切换次数,线程被系统强制调度导致上下文切换,例如大量线程在抢占CPU。
- GC优化
我们可以通过systrace单独查看整个启动过程GC的时间。
python systrace.py dalvik -b 90960 -a com.sample.gc
不知道你是否还记得我在“内存优化”中提到Debug.startAllocCounting,我们也可以使用它来监控启动过程总GC的耗时情况,特别是阻塞式同步GC的总次数和耗时。
// GC使用的总耗时,单位是毫秒
Debug.getRuntimeStat("art.gc.gc-time");
// 阻塞式GC的总耗时
Debug.getRuntimeStat("art.gc.blocking-gc-time");
如果我们发现主线程出现比较多的GC同步等待,那就需要通过Allocation工具做进一步的分析。启动过程避免进行大量的字符串操作,特别是序列化跟反序列化过程。一些频繁创建的对象
,例如网络库和图片库中的Byte数组、Buffer可以复用。如果一些模块实在需要频繁创建对象,可以考虑移到Native实现。
Java对象的逃逸也很容易引起GC问题
,我们在写代码的时候比较容易忽略这个点。我们应该保证对象生命周期尽量的短,在栈上就进行销毁。
- 系统调用优化
通过systrace的System Service类型,我们可以看到启动过程System Server的CPU工作情况。在启动过程,我们尽量不要做系统调用,例如PackageManagerService操作、Binder调用等待。
在启动过程也不要过早地拉起应用的其他进程,System Server和新的进程都会竞争CPU资源
。特别是系统内存不足的时候,当我们拉起一个新的进程,它可能会触发系统的low memory killer机制,导致系统杀死和拉起(保活)大量的进程,从而影响前台进程的CPU。