目录
启动优化的第一步就是要获取启动的时间,整个启动逻辑的所需要的精确时长。根据各个方法的时长去定位哪些逻辑需要优化。首先我们如何获取代码的执行时间。如下
1.获取启动时间
1.1 启动分类
- 冷启动:系统里
没有对应的进程,需要新建进程和分配内存
来启动程序。 - 温启动:系统里已经
存在对应的进程
,进程在后台运行。但是界面的对象(activity)已经被杀死
。 - 热启动:系统里已经存在对应的进程,进程还
在后台运行,与用户可交互的界面
控件和数据都还在。
1.2 启动时间的测量
1.2.1 adb 命令
查看启动时间。适合线下模式
注意:使用命令前,adb需要配置环境变量
adb shell am start -W com.example.demo/com.example.demo.MainActivity
This time:最后一个activity启动耗时
Total time;所有Activity启动耗时,因为有时候程序启动涉及好几个界面。
WaitTime:AMS启动Actvity总耗时。
1.2.2 手动记录时间
手动打点: 手动记录启动时间和结束时间,线上可以使用。
启动时间:attchBaseContext()
结束时间:不要记录首帧时间(windowsFocusChanged)。要记录第一个数据展示的时间
可以线上跟踪
1.2.3 AOP 面向切面编程
全称:Aspect Oriented Programming,适用于批量监控,统计等使用。
简单使用方式:
1.依赖
project里的build.gradle文件添加
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
module里的build.gradle文件添加
apply plugin: 'android-aspectjx'
implementation 'org.aspectj:aspectjrt:1.8.+'
2.创建监听类
/**
* 添加Aspect注解,且创建方法,来去监听Activity每个方法执行的时间
* 方法名添加注解:
* @Around 在被监听的方法前后都执行逻辑。
* @before 在被监听的方法执行前执行
* @After 在被监听的方法后执行
* 语法解释:call 调用
* (* com..)* 返回值
* com..具体的类
* init** init开头的方法
* (..)任意参数
*
*/
@Aspect
public class AopDemo {
private static final String TAG = "AopDemo";
@Around("call (* com.example.aspectjdemo.MainActivity.init*(..))")
public void getTime(ProceedingJoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
String s = signature.toShortString();
long start= System.currentTimeMillis();
try {
Log.d(TAG, "getTime: yy");
//proceed调用 类似于执行监听方法,eg:initUmeng()
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.d(TAG, s+" getTime: "+(System.currentTimeMillis()-start));
}
}
3.被监听的类和方法
public class MainActivity extends AppCompatActivity {
private static final String TAG = "AopDemo";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUmeng();
initDialog();
}
private void initDialog() {
Log.d(TAG, "initDialog: ");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void initUmeng() {
Log.d(TAG, "initUmeng: ");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
无侵入 修改方便
1.3启动优化工具
1.3.1 traceView
图形展示执行时间,线程具体任务。适合线下检测状态。
好处:定点埋点,高效快捷。
通过执行代码,使用文件的形式收集代码执行逻辑的信息。
生成文件在SD卡:Android/data/packagename/files/xxx.trace
Debug.startMethodTracing("文件名")
Debug.stopMethodTracing()
图片:
wall time 代码执行时间
thread time cpu执行时间
缺点:运行时开销严重,整体会变慢。
1.3.2 systrace
获取内核数据,生成html报告。需要执行sdk里systrace命令。
api 18 以上 推荐TraceCompat
执行命令:
python /users/.../sdk/platform-tools/systrace/systrace.py -b 3200 -t 5 -a com.examle.demo -o hello.html sched gfx view wm am app
- -b 文件大小
- -t 时间
- -a 包名
- -o 输出文件名字
TraceCompat.beginSection("app");
TraceCompat.endSection();
轻量级,开销小,反映CPU利用率
cputime和walltime 区别
walltime代码执行时间
cputime代码消耗CPU时间
2.启动优化
异步优化就是去分担启动页面的其他任务,提高启动页的速度。
2.1 使用线程池加载各个子任务
把程序初始化的加载工作放到子线程,是一种直接高效的形式。我们可以创建线程池服务对象,执行N个线程调用每个初始化任务即可;当然这些初始化的任务也有特殊的情况存在。如:需要上下文环境Context,需要主线程的Looper对象,又或者此任务需要在主线程运行。也就需要特殊处理,如传入context,Looper,Handler对象。
缺点:代码优雅度不好,阅读性差。
2.2 子任务之间有依赖关系
有一种情况更为特殊,就是子任务之间存在依赖关系,这时候需要我们用到CountDownLatch。定时器概念,初始化计数值,当减到为0时,就可以执行下一个任务。
CountDownLatch count= new CountDownLatch(1);
//执行减一
count.countDown();
//如果为count==0,则开始执行下面逻辑
count.await();
2.3 启动器
自定义一套启动器,类似于线程池的原理,同时添加了任务之间的依赖关系处理,可以实现主线程执行和子线程执行,是否等待其他任务。
优点:充分利用CPU的多核,自动梳理执行流程
常规异步的缺点:代码不优雅,不好处理,如子任务有依赖关系。
1.抽取为task任务
2.所有关系生成一个有向无环图。
3.多线程的优先级依次执行
代码资源:
https://download.csdn.net/download/chentaishan/20033656
2.4 延迟方案idleHandler
对于一些不紧急的任务,我我们可以延迟加载。这里使用idleHandler
源码解析:
https://blog.csdn.net/chentaishan/article/details/104437160
2.5 其他
提前加载SharedPrefrences
multidex之前加载,利用此阶段CPU
启动阶段不启动子进程
子进程会共享CPU资源,导致主进程CPU紧张
启动顺序:app onCreate 之前是contentprovider
类加载优化:提前异步类加载
class.forName() 只加载类本身及静态变量的引用类
new 可以额外加载类成员变量的引用类
启动阶段抑制GC
cpu 锁频 CPU的线程全部打开
总结
如何进行启动优化的?
从过程进入主题,分析现状,确认问题。
针对性优化,针对问题 怎么优化问题
长期保持优化效果。 ci监控 启动器
异步问题?演进过程
详细介绍启动器
容易忽略的注意点;
cpu time wall time
注意延迟初始化优化
类加载
CPU拉高
版本迭代导致启动变慢的解决方式
结合CI
完善监控