作者:lu人皆知
在APP做启动优化时,Application会做一些初始化的工作,但不要在Application中做耗时操作,然而有些初始化工作可能是很耗时的,那怎么办?初始化操作可以开启子线程来完成。
常规方案就是在执行前埋点标记开始时间,在执行后埋点标记结束时间,然后计算开始时间和结束时间的差值,时间差值就是耗时时间。
具体的耗时计算实现如下代码所示,在 Application 中 onCreate 方法中调用了很多初始化的方法,我们通过手动埋点标记的方式在每一个调用的方法内部都去计算其方法的耗时时间。
//Application.java
@Override
public void onCreate() {
initSharedPreference();
initImageLoader();
initSQLite();
//.....
}
private void initSharedPreference() {
long startTime = System.currentTimeMillis();
//init SharedPreference
Log.d(TAG, "initSharedPreference cost :" + (System.currentTimeMillis() - startTime));
}
private void initImageLoader() {
long startTime = System.currentTimeMillis();
Fresco.initialize(this);
Log.d(TAG, "initImageLoader cost :" + (System.currentTimeMillis() - startTime));
}
private void initSQLite() {
long startTime = System.currentTimeMillis();
//init bugly
Log.d(TAG, "initSQLite cost :" + (System.currentTimeMillis() - startTime));
}
上面的计算方式,是容易想到的实现方式,但缺点也很明显:
AOP 就是我们常说的
面向切面编程
,它可以针对同一类问题
进行统一处理
。
下面我们就来通过 AOP 的方式来优雅的获取Application每一个方法的执行时间。
在 Android 中通过 AspectJ 这个库来使用 AOP ,接下来来引入这个库:
@Aspect
public class PerformanceAop {
private static final String TAG = PerformanceAop.class.getSimpleName();
@Around("execution(* com.lu.aop.MyApplication.**(..))")
public void getTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
//得到方法的名字,例如:MyApplication.attachBaseContext(..)
String name = signature.toShortString();
//记录方法执行前的时间戳
long startTime = System.currentTimeMillis();
try {
//执行该方法
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//记录执行该方法的时间
long costTime = System.currentTimeMillis() - startTime;
Log.e(TAG, "method " + name + " cost:" + costTime);
}
}
运行结果
2019-08-18 17:09:12.946 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.attachBaseContext(..) cost:1
2019-08-18 17:09:12.979 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.initSQLite() cost:11
2019-08-18 17:09:13.002 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.initImageLoader() cost:17
2019-08-18 17:09:13.002 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.onCreate() cost:28
AOP 这种方式是一个比较优雅的方式实现的,它对已有代码是零侵入性的,修改也方便。
在 Application 去执行这些第三方库的初始化,是会拖慢整个应用的启动过程的,因此用子线程与主线程并行的方式来分担主线程的工作,从而减少主线程的执行时间。
我们可能会容易想到以下这两种方式:
public void onCreate(){
new Thread() {
public run() {
//执行任务1
//执行任务2
//执行任务3
}
}.start();
}
public void onCreate(){
new Thread() {
public run() {
//执行任务1
}
}.start();
new Thread() {
public run() {
//执行任务2
}
}.start();
new Thread() {
public run() {
//执行任务3
}
}.start();
}
方式二更加充分地利用 CPU资源,但是直接创建线程还是不够优雅,所以使用线程池来管理这些线程会更好一些。
获取到对应的线程池,但是这个线程个数不能随意填,我们要能充分利用到 CPU 资源
,因此我们可以参考 AsyncTask
它是如何去设置核心线程数
的。
Executors service = Executors.newFixedThreadPool(核心线程个数);
看到AsyncTask源码中了解核心线程数设置
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//CORE_POOL_SIZE 就是核心线程数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
这样我们就可以设置核心线程数了
//参考AsyncTask来设置线程的个数。
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
在MyApplication中实现异步执行任务:
@Override
public void onCreate() {
super.onCreate();
//参考AsyncTask来设置线程的个数。
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
service.submit(new Runnable() {
@Override
public void run() {
initSQLite();
}
});
service.submit(new Runnable() {
@Override
public void run() {
initImageLoader();
}
});
}
异步加载的代码执行结果
2019-08-18 19:09:38.022 13948-13948/com.lu.aop E/PerformanceAop: method MyApplication.attachBaseContext(..) cost:1
2019-08-18 19:09:38.062 13948-13948/com.lu.aop E/PerformanceAop: method MyApplication.onCreate() cost:4
2019-08-18 19:09:38.078 13948-13967/com.lu.aop E/PerformanceAop: method MyApplication.initSQLite() cost:9
2019-08-18 19:09:38.094 13948-13968/com.lu.aop E/PerformanceAop: method MyApplication.initImageLoader() cost:15
从Log日志数据对比,可以看出主线程执行的 onCreate 方法的执行时间从原来的 28ms 减到到了 4ms 。所以用子线程执行耗时任务还是相当好的。但是到这里又遇到一个问题,就是有一些方法是必须在 Application onCreate 执行完成之前完成初始化的,因为在 MainActivity 中就需要使用到,那我们上面的异步就会有问题了,那如何解决这个问题呢?
以initImageLoader()为例,不知道Application中的子线程什么时候才完成初始化任务,但是这个时候已经进入了MainActivity了,用到ImageLoader,ImageLoader在Application中还没有完成初始化就用不了,所以得控制ImageLoader在 Application onCreate 执行完成之前完成初始化。这时就需要使用到 CountDownLatch 了。
CountDownLatch是一种java.util.concurrent包下一个同步工具类,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。
//Application
private CountDownLatch countDownLatch = new CountDownLatch(1);
private void initImageLoader() {
Fresco.initialize(this);
//try {
//模拟耗时
//Thread.sleep(3000);
//} catch (Exception e) {
// e.printStackTrace();
//}
//Log.e(TAG, "初始化initImageLoader完毕");
//数量减一
countDownLatch.countDown();
}
在 onCreate 方法结束点等待,如果在此处之前之前调用了countDownLatch.countDown(),那么就直接跳过,否则就在此等待。
public void onCreate() {
super.onCreate();
//参考AsyncTask来设置线程的个数。
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
service.submit(new Runnable() {
@Override
public void run() {
initSQLite();
}
});
service.submit(new Runnable() {
@Override
public void run() {
initImageLoader();
}
});
//在 onCreate 方法中等待,如果在此处之前之前调用了countDownLatch.countDown(),那么就直接跳过,否则就在此等待。
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "Application onCreate 执行完毕");
}
这样,我们的 Application onCreate 方法就会等待异步任务 initImageLoader 执行完毕之后才会结束 onCreate 这个方法的生命周期。
为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):https://qr18.cn/FVlo89
https://qr18.cn/FVlo89
启动优化
内存优化
UI优化
网络优化
Bitmap优化与图片压缩优化:https://qr18.cn/FVlo89
多线程并发优化与数据传输效率优化
体积包优化
https://qr18.cn/FVlo89
https://qr18.cn/AQpN4J