App启动优化

最近在做App启动优化,参考了一些文章,刚好也看了张绍文的Android开发高手课,就最近做的一些事做一个总结输出。

1.重要性

就像Android开发高手课讲到的在一些场景就能感觉到,比如扫码支付,微信和支付宝的打开时间就会影响用户的使用和留存。

2 启动分析

首先我们需要分析App启动过程中的几个阶段。

2.1 启动过程

  • 首先是点击图标,创建App进程。
  • Application的创建。
  • 闪屏Activity的创建。

2.2 启动问题分析

从启动流程的几个关键阶段,我们可以推测出用户启动过程会遇到的几个问题。这几个问题其实也是大多数应用在启动时可能会遇到的。

  • 点击App图标,出现长时间的白屏。
  • 首页显示太慢。

3 优化

分析出问题后,我们就可以针对性的解决问题。

3.1 优化工具

优化工具的作用就是统计方法耗时。Traceview 性能损耗太大,得出的结果并不真实;systrace 可以很方便地追踪关键系统调用的耗时情况,但是不支持应用程序代码的耗时分析;还有一种就是类似hugo的方案实现字节码插桩,统计方法耗时,我们之前就用这种方式,但是hugo只支持对项目代码的插桩,无法对lib代码插桩。
所以综合来看“systrace + 函数插桩”似乎是比较理想的方案, 然后实现对所有代码都可插桩的插件。至于Trace的使用,可自行了解。
至于插桩的方案,可对一些方法进行白名单出来,这样Trace文件图会容易看出问题,只有准确的数据评估才能指引优化的方向,这一步是非常非常重要的。我见过太多同学在没有充分评估或者评估使用了错误的方法,最终得到了错误的方向。辛辛苦苦一两个月,最后发现根本达不到预期的效果

3.2 优化方案

创造好工具后,我们就可以干活了

进程启动优化
App进程孵化流程我们无法干预, 但是尽量减少App体积还是能优化进程的启动;Android提供了window background来设置预览背景, 当Activity还未启动,直接显示预览背景,让用户先看到页面,避免白屏,这种完全“跟手”的感觉在高端机上体验非常好,但对于中低端机,会把总的的闪屏时间变得更长,第二种是微信的方案,将预览闪屏页背景设置透明, 但这样会导致在桌面会需要顿一下才会开启App。这两种方案都较为常规,在网上也都有成熟的教程,所以App体积优化是我们更需要关注的点。

业务优化

  1. Multidex 在安装过程中耗费大量的时间。其实优化方案就是异步加载,那么异步加载,但是在一些业务调用时Dex未加载完成,导致ClassNotFound问题。那么怎么才能解决这个问题呢,我记得之前美团提出一种方案是比较好的,文章现在找不到了,将Application、Service、Reciver、ContentPrivider以及主页面和主页面之前用到的Activity放到MainDex,然后在Application中异步加载其他Dex, 然后Hook系统的Instrumentation的创建Activity,发现如果Activity类找不到,就强行跳转到一个Loading界面,等待dex加载完成,这里或许大家有疑问, 为什么不在闪屏使用异步加载Dex,加载完后再跳转到首页,这里有个非常重要的知识点,App启动不一定走闪屏页,因为存在App被回收重建的时候会直接调转到回收前的界面,所以一些必要的数据初始化必须放在Application里面。然后就是maindex分包策略的文件生成,我这边一直没找到好的解决方案,都是手动维护,好在Multidex在5.0以后将会消失,应该也快了。

  2. 必要任务。必要任务分为同步和异步两块。在Application创建过程中,有些值必须同步调用,这块也没有好的解决办法,只能尽量减少,放到异步中。非必要任务。非不要任务可以延迟加载或者懒加载,减少启动时候的cpu、io、网络占用等问题。这块的理论很简单,但是实践非常难,历史包袱非常沉重,而且“牵一发动全身”,改动风险比较大。但是我想说,如果有合适的时机,我们依然需要勇敢去偿还这些“历史债务”。

  3. 高级特性dex重排和apk资源重排
    Linux 文件系统从磁盘读文件的时候,会以 block 为单位去磁盘读取,一般 block 大小是 4KB。也就是说一次磁盘读写大小至少是 4KB,然后会把 4KB 数据放到页缓存 Page Cache 中。如果下次读取文件数据已经在页缓存中,那就不会发生真实的磁盘 I/O,而是直接从页缓存中读取,大大提升了读的速度。所以上面的例子,我们虽然读了 1000 次,但事实上只会发生一次磁盘 I/O,其他的数据都会在页缓存中得到。Dex 文件用的到的类和安装包 APK 里面各种资源文件一般都比较小,但是读取非常频繁。我们可以利用系统这个机制将它们按照读取顺序重新排列,减少真实的磁盘 I/O 次数。
    Dex优化FaceBook出了一个开源的Redex,apk的资源重排支付宝和微信都出了相关的文章,但是没有开源工具。

  4. 减少App冷启动。

  5. 减少进程调用;减少大数据分配从而减少GC。

3.3优化实践

下面大概讲下自己在优化实践中的一些案例

  1. 通过上述的工具统计耗时方法后,整理出整体的耗时方法流程图。 其实我们发现只有账号体系是需要在主线程同步从数据库获取,其他部分都可以异步加载,所以将其他任务更改为异步加载。
  2. 初始化任务拆分。初始化任务拆分的好处是,每个任务独立, 就可以方便的只初始化必要任务。 其实这里分为两块, 一块是多进程减少非必要的初始化任务,一个是懒加载初始化任务和需要时才初始化。这里我们把每个任务独立一个plugin,按顺序初始化任务。我们App的进程拆分主要参考微信,主进程和tools进程,tools进程主要放webview和本地选图模块,所以tool进程都是懒加载,而且大部分组件在tools进程都没必要初始化。
  3. dex优化。 我们使用redex工具,对dex进行优化, 优化后,在老款手机还是比较明显,启动速度可以提升10%-20%, 新款机型提升较少5%左右。 使用dex优化后,后续我们遇到一个问题,就是和tinker热修复的打包有冲突,需要改tinker插件,所以后续我们放弃这个方案。

4 保护成果

其实很多时候项目的开始,大家都想写好代码,写好性能,在开发的过程多人协同开发,在迭代的过程中就慢慢出现各种问题,所以保护成果尤为重要。我们做了一下几点。

  1. 输出文档,入职员工必须阅读文档并写文档总结。
  2. Application层面的改代码必须codeview。
  3. 定时跑App统计耗时的脚本,记录每个版本最终的数据,方便后面版本对比。

5 总结

其实理论上很简单,其中的过程非法复杂,其中任务拆分,多进程拆分,整理出必要任务,任务懒加载,用时加载等都耗费大量的时间。但结果始终是好的,经过优化,启动整体提升了20%左右。