笔者是面霸,面试200+场 当过考官:面过别人300+场 去过500强,也呆过初创公司。
关注我就能达到大师级水平,这话我终于敢说了, 年薪60万不是梦!
斩获腾讯、华为、oppo,VIVO,安卓岗offer!我有一套速通大厂技巧分享给你!
为什么要优化启动速度?
举一个具体的场景:启动自己的APP。
结果发现:小米加载自己的广告,然后加载我们的广告,再进入首页。
刚好我们的app启动慢,结果先加载小米广告,然后到桌面上。然后再启动我们的App。体检极差
1.1计算启动的时间的几种方式
1.2.Actiivity的启动流程
1.3.冷启动和热启动原理
1.4.解决冷启动慢的方法(懒加载和背景替换)(重点分析)
1.5 高难度的启动优化方案
1.6.如果我们需要监控每一个控件的加载耗时,该怎么实现呢?(重点分析AOP)
1.7 启动分析工具
1.8 实战优化案例
第一步:计算启动的时间:
adb 命令统计shell指令
adb命令: adb shell am start -S -W 包名/启动类的全限定名 , -S 表示重启当前应用
打印日志如下:
C:\Android\Demo>adb shell am start -S -W com.example.moneyqian.demo/com.example.moneyqian.demo.MainActivity
Stopping: com.example.moneyqian.demo
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.moneyqian.demo/.MainActivity }
Status: ok
Activity: com.example.moneyqian.demo/.MainActivity
ThisTime: 2247
TotalTime: 2247
WaitTime: 2278
Complete
解析:ThisTime : 最后一个 Activity 的启动耗时(例如从 LaunchActivity - >MainActivity「adb命令输入的Activity」 , 只统计 MainActivity 的启动耗时)
TotalTime : 启动一连串的 Activity 总耗时.(有几个Activity 就统计几个)
WaitTime : 应用进程的创建过程 + TotalTime .
在第①个时间段内,AMS 创建 ActivityRecord 记录块和选择合理的 Task、将当前Resume 的 Activity 进行 pause.
在第②个时间段内,启动进程、调用无界面 Activity 的 onCreate() 等、 pause/finish 无界面的 Activity.
在第③个时间段内,调用有界面 Activity 的 onCreate、onResume.
//ActivityRecord
private void reportLaunchTimeLocked(final long curTime) {
``````
final long thisTime = curTime - displayStartTime;
final long totalTime = stack.mLaunchStartTime != 0 ? (curTime - stack.mLaunchStartTime) : thisTime;
}
最后总结一下 : 如果需要统计从点击桌面图标到 Activity 启动完毕,可以用WaitTime作为标准,但是系统的启动时间优化不了,所以优化冷启动我们只要在意 ThisTime。
启动时间计算第二种方式:系统日志统计
根据系统日志来统计启动耗时,在Android Studio中查找已用时间,必须在logcat视图中禁用过滤器(No Filters)。
过滤displayed输出的启动日志.
启动时间计算第三种方式AOP(Aspect Oriented Programming) 打点
特点:
1、线下使用方便,不能带到线上。
2、非严谨、精确时间。
我们如何去衡量界面的打开速度呢?
Lancet是一个轻量级的Android AOP框架,它具有如下优势:
1、编译速度快,支持增量编译。
2、API简单,没有任何多余代码插入apk。(这一点对应包体积优化时至关重要的)
获取界面布局耗时:使用AOP的方式去获取界面加载的耗时、利用LayoutInflaterCompat.setFactory2去监控每一个控件加载的耗时。
上面我们使用了AOP的方式监控了Activity的布局加载耗时,那么,如果我们需要监控每一个控件的加载耗时,该怎么实现呢?
这样我们就实现了利用LayoutInflaterCompat.Factory2全局监控Activity界面每一个控件加载耗时的处理,后续我们可以将这些数据上传到我们自己的APM服务端,作为监控数据可以分析出哪些控件加载比较耗时。当然,这里我们也可以做全局的自定义控件替换处理,比如在上述代码中,我们可以将TextView全局替换为自定义的TextView。
AOP:实战DEMO地址:
1.2 启动流程:
在安卓系统上,应用在没有进程的情况下,应用的启动都是这样一个流程:当点击app 的启动图标时。安卓系统会从Zygote 进程中fork 创建出一个新的进程分配给该应用。之后会依次创建和初始化Application 类、创建MainActivity 类、WindowManagerService 会对当前 Activity 的token和主题进行判断。
二、应用的启动过程
冷启动启动流程:当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的
windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上,所以直到这里,
应用的第一次启动才算完成,这时候我们看到的界面也就是所说的第一帧。所以,总结一下,应用的启动流程如下:
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。
1.3.冷启动和热启动原理
一、应用的启动方式
通常来说,启动方式分为两种:冷启动和热启动。
1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
2、热启动:当启动应用时,后台已有该应用的进程(例:按back键、在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。
特点
1、冷启动:冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
2、热启动:热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application
1.4.解决冷启动慢的方法优化方案:
1.Application 优化(懒加载,延时加载)
2.UI效果,背景图
3.fragment的懒加载
冷启动时间是指当用户点击你的app那一刻到系统调用Activity.onCreate()之间的时间段。在这个时间段内,WindowManager会先加载app主题样式中的windowBackground做为app的预览元素,然后再真正去加载activity的layout布局
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
图片制作好之后,我们就可以用它做为app冷启动阶段的预览元素,如下设置:
为启动的Activity自定义一个Theme
1
2
3
将新的Theme应用到设置到AndroidManifest.xml中
1
2
3
4
5
6
7
8
9
android:name=".MainActivity" android:theme="@style/AppTheme.Launcher">
黑白屏产生原因:当我们在启动一个应用时,系统会去检查是否已经存在这样一个进程,如果不存在,系统的服务会先检查startActivity 中的intent 的信息,然后在去创建进程,最后启动Acitivy,即冷启动。
而启动出现白黑屏的问题,就是在这段时间内产生的。系统在绘制页面加载布局之前,首先会初始化窗口(Window),而在进行这一步操作时,系统会根据我们设
置的Theme 来指定它的Theme 主题颜色,我们在Style 中的设置就决定了显示的是白屏还是黑屏。
4.延时加载
目前可以想到的场景:
1.Activity启动优化:onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
2.想要一个View绘制完成之后添加其他依赖于这个View的View,当然这个View#post()也能实现,区别就是前者会在消息队列空闲时执行。
3.发生一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作。
4.一些第三方库中使用,比如LeakCanary,Glide中使用到,具体可以自行去查看。
有效的结束分析3种方案:1.IdleHandler2.onWindowFocusChanged()3.View.post(Runnable runnable)
闲时调用:IdleHandler使用案例
IdleHandler:当Handler 空闲的时候才会被调用,如果返回true, 则会一直执行,如果返回false,执行完一次后就会被移除消息队列。比
如,我们可以将从服务器获取推送Token 的任务放在延迟。IdleHandler 中执行,或者把一些不重要的View 的加载放到,IdleHandler 中执行
public class DelayInitDispatcher {
private QueuemDelayTasks =new LinkedList<>();
private MessageQueue.IdleHandlermIdleHandler =new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
if(mDelayTasks.size()>0){
Log.d("SNDD","queueIdle");
Task task =mDelayTasks.poll();
new DispatchRunnable(task).run();
}
return !mDelayTasks.isEmpty();
}
};
public DelayInitDispatcheraddTask(Task task){
mDelayTasks.add(task);
return this;
}
public void start(){
Log.d("SNDD","DelayInitDispatcher start");
Looper.myQueue().addIdleHandler(mIdleHandler);
}
}
https://github.com/pengcaihua123456/shennandadao/tree/master/IdleHandler2
冷启动优化,总结为12个字“减法为主,异步为辅,延迟为补”
1.5 高难度的启动优化方案
1).Multidex预加载优化
https://juejin.im/post/5d95f4a4f265da5b8f10714b
我们都知道,安装或者升级后首次 MultiDex 花费的时间过于漫长,我们需要进行Multidex的预加载优化。
5.0以上默认使用ART,在安装时已将Class.dex转换为oat文件了,无需优化,所以应判断只有在主进程及SDK 5.0以下才进行Multidex的预加载
1、优化步骤
1、启动时单独开一个进程去异步进行Multidex的第一次加载,即Dex提取和Dexopt操作。
2、此时,主进程Application进入while循环,不断检测Multidex操作是否完成。
3、执行到Multidex时,则已经发现提取并优化好了Dex,直接执行。MultiDex执行完之后主进程Application继续执行ContentProvider初始化和Application的onCreate方法。
https://github.com/lanshifu/MultiDexTest
2).抖音团队:低版本5.0以下davilk虚拟机。BoostMultiDex 方案相比官方MultiDex 方案
https://github.com/bytedance/BoostMultiDex
androidopt是什么意思?
一句话总结原理:无需等待ODEX 优化的直接DEX 加载方案已经完全打
耗时原因:调MultiDex.install,它会解开APK 包,对第二个以后的DEX 文件做ODEX 优化并加载
MultiDex.install 生成ODEX 文件的过程,调用的方法是DexFile.loadDex,它会启动一个dexopt 进程对输入的DEX 文件进行ODEX 转化。那么,这个ODEX 优化的时间是否可以避免呢?
我们的BoostMultiDex 方案,正是从这一点入手,从本质上优化install 的耗时。我们的做法是,在第一次启动的时候,直接加载没有经过OPT 优化的原始DEX,
先使得APP 能够正常启动。然后在后台启动一个单独进程,慢慢地做完DEX 的,OPT 工作,尽可能避免影响到前台APP 的正常使用。
第二次启动用优化好的odex进行启动。,第二次进来读取sp中保存的dex信息,直接返回file list
小结一下,绕过ODEX 直接加载DEX 的方案,主要有以下步骤:
1. 从APK 中解压获取原始Secondary DEX 文件的字节码
2. 通过dlsym 获取dvm_dalvik_system_DexFile 数组
3. 在数组中查询得到 Dalvik_dalvik_system_DexFile_openDexFile_bytearray 函数
4. 调用该函数,逐个传入之前从APK 获取的DEX 字节码,完成DEX 加载,得到合法的DexFile 对象
5. 把DexFile 对象都添加到APP 的PathClassLoader 的pathList 里
完成了上述几步操作,我们就可以正常访问到Secondary DEX 里面的类了
https://juejin.cn/post/6844904079206907911
3).通过安装包重排布局 支付宝团队和facebook
类重排Redex 初探与Interdex:Andorid 冷启动优化
redex 是 Facebook 开源的一款字节码优化工具,目前只支持 mac 和 linux。
Facebook 的工具链优化方案Redex,对于dex 的优化,从度量到回归测试,开源
出了一整套解决方案,对于zip 的重布局,希望未来能将此整套方案,
https://mp.weixin.qq.com/s?__biz=MzUyMDk2MzUzMQ==&mid=2247483804&idx=1&sn=026f386cc88d07044735cde5206c1de0&scene=21#wechat_redirect
1.7 启动分析工具三种
1.systrace 查找耗时代码
2.分析工具:Traceview
https://juejin.cn/post/6854573215474253838
3.bloackcaneary分析耗时
Systrace使用案例
首先,我们可以通过Systrace来观察CPU的运行状况,比如有没有跑满CPU;
然后,我们在启动优化中学习到的优雅异步以及优雅延迟初始化等等一些方案;
其次,针对于我们的界面布局,我们可以使用异步Inflate、X2C、其它的绘制优化措施等等;
最后,我们可以使用预加载的方式去提前获取页面的数据,以避免网络或磁盘IO速度的影响,或者也可以将获取数据的方法放到onCreate方法的第一行。
https://mp.weixin.qq.com/s/_6pulOeRmA-YVCcC2JfcLQ
1.8 实战优化案例:Android页面启动速度优化工具项目:and-load-aot https://juejin.cn/post/6844903604348780552
参考博客:
https://www.jianshu.com/p/7d836a3b9a2f