1.为什么要用启动器
为什么要做启动器?直接写它不香吗?来先回顾下恶心的代码结构
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 一堆耗时方法,严重影响启动
initBugly();
initBaiduMap();
initJPushInterface();
initShareSDK();
}
}
面对这些比较恶心的启动方法,为了加快启动,我们一般会采用线程池的方式启动,一线大厂资深APP性能优化系列-异步优化与拓扑排序(二),
但是如果有的方法自己需要依赖的方法执行完毕才能执行,比如 initJPushInterface() 可能需要先执行完毕 GetDeviceID() 执行完毕才能进行再执行,那么把它们都放入线程池里面并行执行就会产生问题,另外有的方法比如initBugly(); 必须先执行完它之后,主线程才能执行完毕,再跳转页面。那么因为这些问题,如果只是用线程池来并行,就会导致代码写起来过于复杂。
这也就是为什么要推出启动器的原因,当然阿里做的还是不错的,但是狗东用阿里做的启动器感觉怪怪的,所以跟着作者一起从零搭建一个启动器吧。
1.定义task接口
首先,我们要定义自己的一些task, 就是用来执行耗时方法的。先定义个接口吧。
/**
* @author: lybj
* @date: 2020/5/14
* @Description:
*/
public interface ITask {
void run();
/**
* Task执行所在的线程池,可指定,一般默认
*/
Executor runOnExecutor();
/**
* 存放需要先执行的task任务集合(也就是添加需要先执行的依赖)
*/
List<Class<? extends ITask>> dependentArr();
/**
* 开始锁
* */
void startLock();
/**
* 解锁
* */
void unlock();
/**
* 异步线程执行的Task是否需要在被调用await的时候等待,默认不需要
*
* @return
*/
boolean needWait();
/**
* 是否在主线程执行
*/
boolean runOnMainThread();
/**
* Task主任务执行完成之后需要执行的任务
*/
Runnable getTailRunnable();
}
好了,这些基本够用了。
2.实现task接口
public abstract class Task implements ITask {
// 当前Task依赖的Task数量(需要等待被依赖的Task执行完毕才能执行自己),默认没有依赖
private CountDownLatch taskCountDownLatch = new CountDownLatch(dependentArr() == null ? 0 : dependentArr().size());
/**
* 当前Task等待,让依赖的Task先执行
*/
@Override
public void startLock() {
try {
taskCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 依赖的Task执行完一个
*/
@Override
public void unlock() {
taskCountDownLatch.countDown();
}
/**
* 是否需要尽快执行,解决特殊场景的问题:一个Task耗时非常多但是优先级却一般,很有可能开始的时间较晚,
* 导致最后只是在等它,这种可以早开始。
*/
public boolean needRunAsSoon() {
return false;
}
/**
* Task的优先级,运行在主线程则不要去改优先级
*/
@Override
public int priority() {
return Process.THREAD_PRIORITY_BACKGROUND;
}
/**
* Task执行在哪个线程池,默认在IO的线程池;
*/
@Override
public ExecutorService runOnExecutor() {
return DispatcherExecutor.getIOExecutor();
}
/**
* 异步线程执行的Task是否需要在被调用await的时候等待,默认不需要
*/
@Override
public boolean needWait() {
return false;
}
/**
* 当前Task依赖的Task集合(需要等待被依赖的Task执行完毕才能执行自己),默认没有依赖
*/
@Override
public List<Class<? extends ITask>> dependentArr() {
return null;
}
@Override
public boolean runOnMainThread() {
return false;
}
@Override
public Runnable getTailRunnable() {
return null;
}
}
很简单,主要做的是:
1.dependentArr() 定义一个栅栏
很好理解,传入的task(我们的耗时任务),因为需要依赖,比如TaskA,必须得等TaskB,TaskC加载完毕才能加载TaskA,dependentArr()返回的就是TaskB,TaskC,也就是在TaskA中加了几个同步锁(锁的数量就是TaskA所需要依赖的Task数量),unlock()就减少一把锁。
3.实现启动器
外部调用
TaskManager manager = TaskManager.getInstance(this);
manager.add(new InitBuglyTask()) // 默认添加,并发处理
.add(new InitBaiduMapTask()) // 在这里需要先处理了另外一个耗时任务initShareSDK,才能再处理它
.add(new InitJPushTask()) // 等待主线程处理完毕,再进行执行
.add(new InitShareTask())
.start();
manager.startLock();
构建启动器
public class TaskManager {
private static TaskManager sInstance;
private Context mContext;
// 维持task和它的依赖Task的依赖关系,这里是仿照EventBus的存放事件的机制设计
private HashMap<Class<? extends ITask>, ArrayList<ITask>> dependOfTaskArray = new HashMap<>();
// 存放已经执行完毕的Task队列
private volatile List<Class<? extends ITask>> taskFinishedArray = new ArrayList<>();
// 存放所有的task
private List<Task> taskAll = new ArrayList<>();
private List<Class<? extends Task>> taskAllClazz = new ArrayList<>();
// 需要在主线程中执行的Task队列
private volatile List<Task> mainThreadTaskArray = new ArrayList<>();
// 主线程需要等待先执行的task数量
private AtomicInteger mainNeedWaitCount = new AtomicInteger();
private CountDownLatch mCountDownLatch;
private static final int WAITTIME_TIME = 996 * 31;
private List<Future> futureArray = new ArrayList<>();
private TaskManager(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context is null.");
}
mContext = context;
}
/**
* 使用单例模式创建对象
*/
public static TaskManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new TaskManager(context);
}
return sInstance;
}
/**
* 添加任务
*/
public TaskManager add(Task task) {
if (task == null) {
throw new IllegalArgumentException("task is null !");
}
// ->> 1
setDependentOfTask(task);
// ->> 2
taskAll.add(task);
taskAllClazz.add(task.getClass());
// ->> 3
// 非主线程且需要wait的
if (ifNeedWait(task)) {
// 主线程的锁加一把
mainNeedWaitCount.getAndIncrement();
}
return this;
}
/**
* 获取依赖的集合,主要做的为两件事
*
* 1.是以依赖类为Key,对应的依赖者的集合为value添加进map里面
* 2.在从完成的任务集合里面查询,该task所依赖的类是否已经完成,完成的话进行解锁
* */
private void setDependentOfTask(Task task) {
if (task.dependentArr() != null && task.dependentArr().size() > 0) {
for (Class<? extends ITask> dependTaskClazz : task.dependentArr()) {
if (dependOfTaskArray.get(dependTaskClazz) == null) {
dependOfTaskArray.put(dependTaskClazz, new ArrayList<ITask>());
}
// 如果该task所依赖的依赖任务已经加载过了,就解锁其中已经完成的
dependOfTaskArray.get(dependTaskClazz).add(task);
if (taskFinishedArray.contains(dependTaskClazz)) {
task.unlock();
}
}
}
}
private boolean ifNeedWait(Task task) {
return !task.runOnMainThread() && task.needWait();
}
@UiThread
public void start() {
if (Looper.getMainLooper() != Looper.myLooper()) {
throw new RuntimeException("小子,启动器必须要在主线程启动");
}
if (taskAll.size() > 0) {
// 4.->> 效率排序
taskAll = TaskSortUtil.getSortResult(taskAll, taskAllClazz);
// 5.->> 构建同步锁
mCountDownLatch = new CountDownLatch(mainNeedWaitCount.get());
// 6.->> 分发任务
dispatchTasks();
runOnMainThread();
}
}
/**
* task分发,根据设定的不同规则,分发到不同的线程
*/
private void dispatchTasks() {
for (final Task task : taskAll) {
// 如果是需要在主线程中运行的,加入到主线程队列中
if (task.runOnMainThread()) {
mainThreadTaskArray.add(task);
} else {
// 异步线程中执行,是否执行取决于具体线程池
Future future = task.runOnExecutor().submit(new TaskRunnable(task, this));
futureArray.add(future);
}
}
}
private void runOnMainThread() {
for (Task task : mainThreadTaskArray) {
new TaskRunnable(task,this).run();
}
}
@UiThread
public void startLock() {
try {
if (mainNeedWaitCount.get() > 0) {
mCountDownLatch.await(WAITTIME_TIME, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException e) {
}
}
/**
* 取消
* */
public void cancel() {
for (Future future : futureArray) {
future.cancel(true);
}
}
/**
* 当完成一个任务之后,通知所有依赖它的任务,并解锁他们
*/
public void unLockForChildren(Task task) {
ArrayList<ITask> arrayList = dependOfTaskArray.get(task.getClass());
if (arrayList != null && arrayList.size() > 0) {
for (ITask subTask : arrayList) {
subTask.unlock();
}
}
}
// 7 ->>
public void finish(Task task) {
if (ifNeedWait(task)) {
taskFinishedArray.add(task.getClass());
mCountDownLatch.countDown();
mainNeedWaitCount.getAndDecrement();
}
}
}
首先是通过getInstance()构造了一个实例对象,然后通过addTask() 添加我们的Task, 如果它不为空的话
根据上面的角标,逐一介绍
- 调用setDependentOfTask(),遍历该task所依赖的全部task,并且以它所依赖的task为Key, 对应的依赖者的集合为value添加进map里面,然后检查,该task中的依赖是否已经执行过了,如果已经执行过了,调用该task的unlock()方法减该task的一把锁。
- 然后将这个task和它的class文件添加到2个集合中,方便后面使用。
- 如果该Task需要主线程等其完成再执行的话,等待队列计数器+1
- 拓扑排序,经典的算法,用于描述依赖关系的排序,在上一章节有过介绍也给出过源码,这里就不再赘述
- 这里实际上就是构建一把锁,这个锁注意并不在Task里面,Task里面的锁,注意是为了先执行依赖的Task,执行完毕,再执行自己,而这里的锁是在启动器上,其作用是让主线程等待,优先执行那些必须要先执行完毕才能让主线程继续执行完毕,再跳转页面的task
- 根据需要分发不同的线程去执行,如果是需要在主线程中执行,那就先存储起来,如果是需要在一异步中执行,那就直接调用task.runOnExecutor()方法来异步执行耗时task,runOnExecutor()可复写,不写为默认线程池
- 如果该线程需要在主线程中执行,等待队列-1,添加进结束队列,如果该task需要主线程等待的话,主线程的同步锁-1
4.TaskRunnable的实现
好了如何执行任务尼?
/**
* 任务真正执行的地方
*/
public class TaskRunnable implements Runnable {
private Task task;
private TaskManager taskManager;
public TaskRunnable(Task task) {
this.task = task;
}
public TaskRunnable(Task task, TaskManager taskManager) {
this.task = task;
this.taskManager = taskManager;
}
@Override
public void run() {
TraceCompat.beginSection(task.getClass().getSimpleName());
Process.setThreadPriority(task.priority());
task.startLock();
task.run();
// 执行Task的尾部任务
Runnable tailRunnable = task.getTailRunnable();
if (tailRunnable != null) {
tailRunnable.run();
}
if (!task.runOnMainThread()) {
if(taskManager != null){
taskManager.unLockForChildren(task);
taskManager.finish(task);
}
}
TraceCompat.endSection();
}
}
好了,是不是很简单? 优先执行需要依赖的Task, 然后再执行自己,等都执行完毕后,调用 taskManager.unLockForChildren(mTask); 将该task从等待队列中移除,添加进结束队列,如果该task需要主线程等待的话,主线程的同步锁-1,等待队列数-1
再看下我们自己的task
public class InitJPushTask extends Task {
@Override
public boolean needWait() {
return true;
}
@Override
public List<Class<? extends ITask>> dependentArr() {
List<Class<? extends ITask>> tasks = new ArrayList<>();
tasks.add(InitShareTask.class);
return tasks;
}
@Override
public void run() {
try {
Thread.sleep(1500);
System.out.println("InitJPushTask运行完毕,它所在的线程是:"+Thread.currentThread().getName());
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
我们自己的这个Task就写完看,这是一个需要先执行完毕GetDeviceIdTask, 然后需要执行完毕自己,才能允许Application去加载页面的任务,看是不是非常简单,看下Application的改造
TaskManager manager = TaskManager.getInstance(this);
manager.add(new InitBuglyTask()) // 默认添加,并发处理
.add(new InitBaiduMapTask()) // 在这里需要先处理了另外一个耗时任务initShareSDK,才能再处理它
.add(new InitJPushTask()) // 等待主线程处理完毕,再进行执行
.add(new InitShareTask())
.start();
manager.startLock();
5.总结

6.END
这个启动器目前已经在某厂的一个比较成熟的项目中使用了,目测还是蛮好用的,确实比之前启动速度提升了很多,大约能提升到3秒,还有其余的大概18-19章节,包含很多核心的优化及大型APP的容灾方案,前几天有人联系我,想让我把接下来的内容写书,犹豫了很久,不知道你们是爱看书,还是喜欢看博客,欢迎在底下留言
