应用的启动速度对一个APP来说至关重要,会直接影响到用户体验,如果启动速度过慢会导致用户的流失,本文就启动速度优化分析,为优化启动速度提供一些思路。
一、获取启动时间
1、adb命令获取
启动方式分两种:冷启动和热启动
冷启动:应用启动时后台无应用进程,需新创建进程分配给应用,Application会重新创建并执行生命周期;
热启动:应用启动时后台已存在应用进程,一般Home键退出,Back键退出,任务列表中存在进程任务,Application不会重新创建;
获取启动时间adb命令
adb shell am start -W [packageName]/[packageName.xxActivity]

ThisTime:最后一个Activity启动耗时;
TotalTime:启动时经历的所有Activity启动耗时;
WaiteTime:AMS启动所有Activity的总时间(包括启动目标应用前的当前Activity的onPause方法耗时和TotalTime)
可以看出冷启动热启动耗时差距很大;
谈到的启动耗时一般都是冷启动耗时,所以我们一般着重优化冷启动耗时;
该方式只能统计到应用启动耗时时间,无法精确定位是哪个方法耗时;
2、时间戳打点
public class LaunchTimer {
private static long sTime;
public static void startRecord() {
sTime = System.currentTimeMillis();
}
public static void endRecord() {
endRecord("");
}
public static void endRecord(String msg) {
long cost = System.currentTimeMillis() - sTime;
LogUtils.i(msg + "cost " + cost);
}
该方式一般为Application初始化attachBaseContext方法打启动开始时间戳,应用用户可操作界面完全展示可操作后打结束时间戳,两时间差即为启动耗时;
二、启动优化工具
1、Traceview
traceview 是Android 自带性能工具,可图形化展示方法调用时间,调用栈,还可以查看所有线程信息,对分析方法耗时,调用链是非常好的工具。
1.1 使用方法
代码埋点:
开始收集,传入自定义文件名
Debug.startMethodTracing("app_trace");
结束收集
Debug.stopMethodTracing();默认文件生成在/sdcard/Android/data/com.xxx.xx/files/app_trace.trace
使用Android Studio打开该文件

区域1用于选择时间范围,滑动可以选择查看时间范围内线程运行
区域2可以查看到有多少线程和执行时间,点击每个线程区域3可显示每个线程对应的执行方法;
区域3可以查看堆栈信息
Call Chart:查看方法时间段和时间消耗,从上往下依次为调用顺序,系统api一般为橙色,应用自身方法为绿色,第三方api一般是蓝色;
Flame Chart:Call Chart的倒序调用;

Top Down:该区域可以看每个方法内部调用了哪些方法,对查看调用堆栈十分滴方便,此处注意几个概念;
Total:方法执行总耗时;Self:方法内部除了调用的子方法总耗时;Children:调用的子方法总耗时;Total = Self + Children
Wall Clock Time:该方法在线程内部执行总耗时;
Thread Time:该方法CPU消耗执总耗时;
Bottom Up:显示一个方法的被调用列表,和Top Down相反;
2、Systrace
Systrace 是平台提供的一款工具,用于记录短期内的设备活动。该工具会生成一份报告,其中汇总了 Android 内核中的数据,例如 CPU 调度程序、磁盘活动和应用线程。这份报告可帮助我们了解如何以最佳方式改善应用性能;
官方文档:developer.android.google.cn/studio/prof…
2.1 使用方法
收集开始,传入自定义tag
TraceCompat.beginSection("AppOnCreate");
收集结束
TraceCompat.endSection();
使用python命令生成报告
cd到Android/sdk/platform-tools/systrace
执行python命令,python环境没有配置好的话可使用Home Brew install一下
python systrace.py -b 32768 -t 10 -a 包名 -o browser.html sched gfx view wm am app具体参数可参考:developer.android.google.cn/studio/prof…
执行命令后,启动应用,待生成报告后打开报告

A键左移,D键右移,S键缩小,W键扩大
区域1为CPU区域,可查看系统分配CPU情况,我们优化方向就是尽可能不让CPU空闲率过高;
区域2为应用进程所有线程的方法消耗时间分配,可以方便的查询到耗时过长的方法;
3、AOP
如果想获取启动过程中每个方法的耗时,可以用埋点的方式进行获取,但是侵入性高,埋点麻烦,需要在每个方法前后进行打点,AOP方式可以解决这个问题。
3.1 使用方法
project build.gradle 中添加插件
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'app build.gradle中添加依赖
apply plugin: 'android-aspectjx'
implementation 'org.aspectj:aspectjrt:1.8.+'
首先介绍几个概念
Join Points:程序运行时的执行点,可以作为切面的地方,比如方法调用执行、获取设置变量、类初始化
PointCut:带条件的Join Points
Advice:一种Hook,需要插入代码的位置,Before(PointCut之前),After(PointCut之后),Around(PointCut前后都要执行)
3.2 语法介绍
/*第一种*/
@Pointcut("execution(* android.app.Activity.on**(..))")
public void executionActivityOn(){
}
@Before("executionActivityOn()")
public void executionActivityOnBefore(JoinPoint joinPoint){
Log.d(TAG, "executionActivityOnBefore: ");
}
/*第二种*/
@Before("execution(* android.app.Activity.on**(..))")
public void executionActivityOnBefore(JoinPoint joinPoint){
Log.d(TAG, "executionActivityOnBefore: ");
3.3 获取方法耗时
@Around("call(* com.test.performance.PerformanceApp.init**(..))")
public void getTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.toShortString();
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
LogUtils.i("app method : "+name + " cost " + (System.currentTimeMillis() - time));
}
PerformanceApp.java
@Override
public void onCreate() {
super.onCreate();
initAMap();
initBugly();
initFresco();
initJPush();
initStetho();
initUmeng();
initWeex();
}
输出结果

三、启动异步优化
1、启动Theme优化
设置首屏主题背景
<style name="Theme.Splash" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowBackground">@drawable/lanucher</item>
<item name="windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="windowNoTitle">true</item>
</style>
@drawable/lanucher
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
<!-- The background color, preferably the same as your normal theme -->
<item android:drawable="@android:color/white"/>
<!-- Your product logo - 144dp color version of your app icon -->
<item>
<bitmap
android:src="@mipmap/splash"
android:gravity="fill"/>
</item>
</layer-list>
首屏OnCreate切换主题
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);2、启动多线程优化
核心思想:将各任务放置单独线程中并行执行;
创建线程池,根据CPU核数创建核心线程数
// 线程等待锁
private CountDownLatch mCountDownLatch = new CountDownLatch(3);
// CPU核数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// 核心线程数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
service.submit(new Runnable() {
@Override public void run() {
initAMap();
initBugly();
initFresco();
mCountDownLatch.countDown();
}
});
service.submit(new Runnable() {
@Override public void run() {
initJPush();
initStetho();
initUmeng();
mCountDownLatch.countDown();
}
});
service.submit(new Runnable() {
@Override public void run() {
initWeex();
mCountDownLatch.countDown();
}
}CountDownLatch
使一个线程等待其他线程各自执行完毕后再执行后续方法,可保证所有线程执行完毕后进入主页面;
3、使用自建启动器
使用线程池异步加载代码不太优雅,通用性不高,维护性较高;
3.1 启动器原理
- 充分利用CPU多核能力,自动梳理并顺序执行任务;
- 代码Task化,将启动任务抽象成各个task;
- 根据所有任务依赖关系排序生成一个有向无环图;
- 多线程按照线程优先级顺序执行;
3.2 流程图

本节介绍下启动器原理,后期单独针对启动器分析一下;
4、使用IdleHandler
IdleHandler 可以用来提升性能,主要用在能够在当前线程消息队列空闲时做些事情(譬如 UI 线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。具体用法如下。
//getMainLooper().myQueue()或者Looper.myQueue()
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
//你要处理的事情
return false;
}
});
5、其它优化方案
5.1 提前加载SharedPreferences
在multidex之前CPU是空闲的,加载系统类是可行的,所以可充分利用这段时间加载SharedPreferences;
5.2 启动阶段不启动子进程
初始化子进程会消耗CPU资源,在启动阶段会导致主进程CPU资源紧张,导致启动阶段资源紧张初始化过慢;
5.3 启动阶段不启动Service ContentProvider
在Application 生命周期方法attachBaseContext方法和onCreate方法中间还会执行ContentProvider生命周期方法,如果在application初始化过程中启动了service或者contentprovider会执行ContentProvider生命周期方法,非常耗时。
5.4 提前异步类加载
对启动阶段用到的类进行提前异步加载,加载方法有两种:
- Class.forName() 只加载类本身及其静态变量的引用类
- new 类实例 可以额外加载类成员变量的引用类
5.5 启动阶段抑制GC
5.6 CPU锁频
后续笔记
《Android深度性能优化--APP内存优化》
《Android深度性能优化--APP布局优化》
《Android深度性能优化--APP卡顿优化》
《Android深度性能优化--APP线程优化》
《Android深度性能优化--APP网络优化》
《Android深度性能优化--APP电量优化》
《Android深度性能优化--APP Crash优化》
持续关注添加微信公众号:xyx的笔记
