app 启动优化做了蛮久了,简单做个记录分享一下~
数据统计
数据统计其实是一个比较重要的环节,启动优化到底做的怎么样,需要一个量化的数据。
首先是要确定启动流程的耗时,网上很多方案统计的启动时间都是读的系统给出的第一个 Activity Display 的时间。大致是这样一条命令
adb shell am start -W com.xxx.xxx/.MainActivity |
这个启动时间,大致等于 Application.attachBaseContext()
到 MainActivity.onWindowFocusChanged()
的时间,需要的时候可以用代码统计。
如果启动阶段有实时请求广告这种逻辑,系统判定启动完毕之后,用户看到的App其实并没有启动完成,这时候我们可以根据业务场景,自己决定启动流程到哪里结束。
之后可以在开发阶段、集成测试阶段使用自动化脚本测试启动时间(启动几十次取平均值),App 发布之后可以使用上面说的自己通过代码上报服务端分析
排查手段
Profiler
android studio 自带的分析工具,如果 app 有明显的卡顿行为,采用这个工具就可以了,可以很直观的看出主线程有哪些耗时行为。缺点是开启 profile 之后会影响 app 的运行速度
如果不想每次都点 profile ‘app’ 之后都要重新编译,可以 开启开发者选项-等待调试程序 -> 开启 ,选择调试应用,app 启动之后会等待断点,我们把 profile 准备好了之后再关闭调试可以了
BlockCanary & BlockCanaryEx
开源工具,网上很多教程,这里就不说了
轮询主线程
开启一个 handler ,定时往主线程发消息,如果消息没有被及时处理,说明主线程卡住了,这时可以 dump 主线程的堆栈,看下是哪里卡住。
StringBuilder sb = new StringBuilder(); |
这种方案适用于已经发布的 app 出现卡顿,本地又无法复现的场景
主动获取traces.txt
在 App 发生 ANR 的时候,系统会自动生成 trace 文件,方便分析 ANR 的原因。如果我们在调试时需要主动获取 release 包下的trace,可以配合上面的卡顿检测自己主动生成 trace 文件:
Process.sendSignal(pid,Process.SIGNAL_QUIT); |
也可以使用命令行工具:
adb shell kill -3 [pid] |
SysTrace
release包可以优先使用这个工具,不影响app运行速度,使用可以看 https://www.androidperformance.com/2019/05/28/Android-Systrace-About/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95
perfetto
perfetto是用于替代SysTrace的,速度快,UI好看,需要Android 9.0以上。网页直接操作在部分机型会有问题,命令行还是靠谱一点
自动化测试
启动优化做完之后需要保持成果,或者持续优化。可以按分支、版本等维度检测启动速度。很多时候发现启动速度变慢,排查问题‘人工二分法’可能更快。。。
优化方案
懒加载
这个是最容易想到的,很多 sdk 无脑放 application 阶段启动,挨个改成懒加载就可以了。最终目标是除了首页必须要用到的功能,其它都往后延
视图懒加载
在 Activity
、 Fragment
中,很多接口请求都是做了懒加载的,但是在首页这种一上来底部四五个Tab,第一个Tab中又有N个子Tab,除了正在展示的那个Tab,其它Tab的View 其实已经加载出来了,可能有些自定义View被改的很耗时,这个时候让其它Tab连View也不加载是很好的解决方案:
- 基于
PagerAdapter
改造,让Fragment
选中的时候再实例化,这样做可以懒加载更彻底,但是会有时候有bug,出问题需要清楚原理 - 让
Fragment
的根布局使用ViewStub
,选中的时候再inflate
,这样是用的Android自带的解决方案,稳定
另外,如果选中的 Tab 不是第一个,页面又是使用 ViewPager
实现的话,第一个 Tab 总是会加载,再跳到其它的Tab,现在想到的解决方案只能用反射设置 ViewPager
的 mCurItem
(后续版本升级调用失败了也没关系。。)。在 notifyDataSetChanged()
或者 setCurrentItem()
之前调用:
private void hookViewPagerIndex(ViewPager viewPager, int index) { |
不必要的动画、View、主题背景
线程优先级
如果启动的时候子线程很多,又必须有的时候,设置子线程的优先级(线程获得CPU执行的时间片)可以有很大的提升:
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); |
必须在子线程里面设置,别设置到主线程里面去了
线程池
使用线程池让网络请求、后台任务等排队执行,减少并发,减少CPU的峰值
接口合并
app启动的时候可能有很多配置信息需要拉取,可以推动服务端合并接口,也可以做一些策略减少接口的请求,按版本增量等
多进程
现在很多App都是多进程,最好是等app启动完成之后再唤醒其它进程,但是如果使用了 Replugin
这种多进程框架的,Replugin
会在启动阶段同步唤醒其它进程,这时候就需要让其它进程尽量减少启动项。当然,最好的方案还是去掉多进程框架
代码编写布局
理论上用代码编写布局,减去读xml文件和inflate 的时间,速度会更快,但是实际上没有效果
瘦身
瘦身也是会加快启动速度的,一些低端机上更加明显
ThinR
资源文件多了之后,R文件会很大,可以在编译时把R文件去掉
资源混淆
跟代码混淆类似,资源混淆会把路径、文件名等简化
插件化
现在大家好像都在去插件化,这个就不推荐了