0%

App启动优化

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();
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
for (StackTraceElement s : stackTrace) {
sb.append(s.toString() + "\n");
}
String text = sb.toString();

这种方案适用于已经发布的 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 阶段启动,挨个改成懒加载就可以了。最终目标是除了首页必须要用到的功能,其它都往后延

视图懒加载

ActivityFragment 中,很多接口请求都是做了懒加载的,但是在首页这种一上来底部四五个Tab,第一个Tab中又有N个子Tab,除了正在展示的那个Tab,其它Tab的View 其实已经加载出来了,可能有些自定义View被改的很耗时,这个时候让其它Tab连View也不加载是很好的解决方案:

  1. 基于 PagerAdapter 改造,让 Fragment 选中的时候再实例化,这样做可以懒加载更彻底,但是会有时候有bug,出问题需要清楚原理
  2. Fragment 的根布局使用 ViewStub,选中的时候再 inflate,这样是用的Android自带的解决方案,稳定

另外,如果选中的 Tab 不是第一个,页面又是使用 ViewPager 实现的话,第一个 Tab 总是会加载,再跳到其它的Tab,现在想到的解决方案只能用反射设置 ViewPagermCurItem (后续版本升级调用失败了也没关系。。)。在 notifyDataSetChanged() 或者 setCurrentItem() 之前调用:

private void hookViewPagerIndex(ViewPager viewPager, int index) {

try {
Field mCurItem=Class.forName("android.support.v4.view.ViewPager").getDeclaredField("mCurItem");
if (!mCurItem.isAccessible()){
mCurItem.setAccessible(true);
}
mCurItem.set(viewPager,index);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}


}

不必要的动画、View、主题背景

线程优先级

如果启动的时候子线程很多,又必须有的时候,设置子线程的优先级(线程获得CPU执行的时间片)可以有很大的提升:

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

必须在子线程里面设置,别设置到主线程里面去了

线程池

使用线程池让网络请求、后台任务等排队执行,减少并发,减少CPU的峰值

接口合并

app启动的时候可能有很多配置信息需要拉取,可以推动服务端合并接口,也可以做一些策略减少接口的请求,按版本增量等

多进程

现在很多App都是多进程,最好是等app启动完成之后再唤醒其它进程,但是如果使用了 Replugin 这种多进程框架的,Replugin 会在启动阶段同步唤醒其它进程,这时候就需要让其它进程尽量减少启动项。当然,最好的方案还是去掉多进程框架

代码编写布局

理论上用代码编写布局,减去读xml文件和inflate 的时间,速度会更快,但是实际上没有效果

瘦身

瘦身也是会加快启动速度的,一些低端机上更加明显

ThinR

资源文件多了之后,R文件会很大,可以在编译时把R文件去掉

资源混淆

跟代码混淆类似,资源混淆会把路径、文件名等简化

插件化

现在大家好像都在去插件化,这个就不推荐了