前言
在应用性能优化中,启动优化一直是一个热门的话题,我们都知道应用的启动速度直接影响我们的用户体验。Android应用开发者一直处于既要丰富强大的功能,又要应用秒开的效果之间矛盾中,中间一定需要跟启动耗时任务作斗争。启动中的耗时主要是耗时任务,如何高效的让必须在应用启动前就就绪的任务最快速的得到执行呢。有些聪明的开发者就想到了做一个初始化框架。本文的Alpha就是这样一个开源库,出自Alibaba。下面我们就来分析一下它的原理。
使用
直接参考Alpha库中Sample部分代码
//这里ConfigTest就是一个Demo类无需太关心
ConfigTest test = new ConfigTest(getApplicationContext());
test.start();
ConfigTest代码
//ConfigTest.java
public void start() {
//配置任务
config();
MyLog.e("==ALPHA==", "start -->" + System.currentTimeMillis());
//启动任务
AlphaManager.getInstance(mContext).start();
}
private void config() {
Project.Builder builder = new Project.Builder().withTaskCreator(new MyTaskCreator());
builder.add(TASK_A);
builder.add(TASK_B).after(TASK_A);
builder.add(TASK_C).after(TASK_A);
builder.add(TASK_D).after(TASK_B, TASK_C);
builder.setProjectName("innerGroup");
...
AlphaManager.getInstance(mContext).addProject(builder.create());
}
原理分析
这个库的代码其实还是相对简单的,而且每个类的代码大都是400行以内,读起来也比较方便。我们结合前面的例子,大致猜测下这个开源库的原理。注意我这个猜测其实就是结论,基于已经阅读的基础上。
config()方法中的代码作用就是编排任务。既然是初始化框架核心任务就是编排任务。既然是任务,最终还是要执行,后面调用lphaManager.getInstance(mContext).start();就是启动任务。
Alpha库的核心是编排任务,让库的使用者可以自由的编排自己的启动子任务。编排好任务之后下一个要做的就是执行。既然是编排好的任务,先执行谁,后执行谁,执行完成后要做什么等等。设想下如果你是这个库的作者,让你实现。你会如何实现?用到哪些集合?使用什么设计模式?用到哪些Java的功能?多个任务如A,B,C都执行完成后才能执行D该如何实现?
也许你能比作者实现的更好,也许你不知道该出哪里下手。让我们带着问题继续看代码。
从一个Task说起
作者首先抽象出了一个Task的类,
- 既然要编排任务首先要有任务类呀,Task就充当了这样的角色。
- 一个任务前面可能有多个任务,后面可能也有多个任务。该用什么数据结构或者集合来实现呢?
- 既然是任务就要有状态,该如何表示呢?
- 既然是任务就要可以执行下,大概率会有run,Start之类的方法区启动
好,上面我说的作者都有考虑,下面看代码,注释非常全面,大部分不需要我补充
//Task.jva
public abstract class Task {
/**
* {@code Task}执行状态,{@code Task}尚未执行
*/
public static final int STATE_IDLE = 0;
/**
* {@code Task}执行状态,{@code Task}正在执行中
*/
public static final int STATE_RUNNING = 1;
/**
* {@code Task}执行状态,{@code Task}已经执行完毕
*/
public static final int STATE_FINISHED = 2;
/**
* {@code Task}执行状态,{@code Task}等待执行
*/
public static final int STATE_WAIT = 3;
/**
* 默认的执行优先级
*/
public static final int DEFAULT_EXECUTE_PRIORITY = 0;
/**
* 执行优先级,由于线程池是有限的,对于同一时机执行的task,其执行也可能存在先后顺序。值越小,越先执行。
*/
private int mExecutePriority = DEFAULT_EXECUTE_PRIORITY;
/**
* 线程优先级,优先级高,则能分配到更多的cpu时间
*/
private int mThreadPriority;
//这里赋值一个线程执行器,通过AlphaConfig.getExecutor()获取,用来执行子线程任务
private static ExecutorService sExecutor = AlphaConfig.getExecutor();
//主线程的Handler,用来执行主线程的任务
private static Handler sHandler = new Handler(Looper.getMainLooper());
//当前状态
private volatile int mCurrentState = STATE_IDLE;
//这里果然有后面任务的集合,用List来存放
private List<Task> mSuccessorList = new ArrayList<Task>();
//这里还有前面任务的集合,用Set来存放,为什么后面任务用List,前面任务用Set呢?
protected Set<Task> mPredecessorSet = new HashSet<Task>();
}
下面看任务的启动
public synchronized void start() {
//一个任务不能被启动多次
if (mCurrentState != STATE_IDLE) {
throw new RuntimeException("You try to run task " + mName + " twice, is there a circular dependency?");
}
//切换到等待状态
switchState(STATE_WAIT);
if (mInternalRunnable == null) {
//新建一个Runnable
mInternalRunnable = new Runnable() {
@Override
public void run() {
//设置线程优先级
android.os.Process.setThreadPriority(mThreadPriority);
//记录启动时间
long startTime = System.currentTimeMillis();
//切换到运行态
switchState(STATE_RUNNING);
//执行run方法,在Task类中是一个抽象方法,由子类实现,就是子类任务要做的事情放里面
Task.this.run();
//run方法执行玩切换到完成状态
switchState(STATE_FINISHED);
long finishTime = System.currentTimeMillis();
//记录task耗时
recordTime((finishTime - startTime));
//通知完成
notifyFinished();
//回收资源
recycle();
}
};
}
if (mIsInUiThread) {
//主线程通过上面的主线程Handler实现
sHandler.post(mInternalRunnable);
} else {
//子线程通过线程执行器执行
sExecutor.execute(mInternalRunnable);
}
}
任务编排
想在再让我们代入一下作者的角度。你现在抽象出了任务,如何把多个任务组成PERT网路图呢?并且如何让任务按照PERT网路图来执行呢?
- 你需要准备一个起始节点,和一个终止节点
- 你需要构造出节点的先后关系,对于每一个节点的先后节点,都是一对多的关系
- 一个任务执行结束后,需要执行它后面的任务(后面的任务如何知道自己该执行了?观察者模式?还是已经编排好了,直接放在一个线程执行?)
作者抽象出了一个Project的概念,用于承载一个PERT网路图,而一个PERT网路图里面的子节点又可以是一个PERT网路图也就是一个Project,这个我们先不分析,先主要关注Project。Project的构造是建造者模式。
Project.Builder builder = new Project.Builder().withTaskCreator(new MyTaskCreator());
builder.add(TASK_A);
builder.add(TASK_B).after(TASK_A);
builder.add(TASK_C).after(TASK_A);
builder.add(TASK_D).after(TASK_B, TASK_C);
builder.setProjectName("innerGroup");
先看Project.Builder的构造方法,里面有一些初始化工作
public Builder() {
init();
}
private void init() {
//设置mCacheTask为null
mCacheTask = null;
//设置mIsSetPosition标志位为true
mIsSetPosition = true;
//构造Project,Project继承自Task,构造方法仅仅做了命名和设置优先级
mProject = new Project();
//构造一个完成的任务
mFinishTask = new AnchorTask(false, "==AlphaDefaultFinishTask==");
mFinishTask.setProjectLifecycleCallbacks(mProject);
//构造一个初始任务
mStartTask = new AnchorTask(true, "==AlphaDefaultStartTask==");
mStartTask.setProjectLifecycleCallbacks(mProject);
//设置启动和初始任务
mProject.setStartTask(mStartTask);
mProject.setFinishTask(mFinishTask);
//构造一个执行监听器
mMonitor = new ExecuteMonitor();
//给Project设置项目执行监听器
mProject.setProjectExecuteMonitor(mMonitor);
}
看代码中builder.add(TASK_A);TASK_A是一个字符串,如何转化成Task的呢?
public Builder add(String taskName) {
...
Task task = mTaskFactory.getTask(taskName);
...
}
public synchronized Task getTask(String taskName) {
//mTasks是缓存好的,如果没有找到,继续往下走
Task task = mTasks.get(taskName);
if (task != null) {
return task;
}
//通过mTaskCreator创建Task,mTaskCreator就是最开始withTaskCreator方法传入的
task = mTaskCreator.createTask(taskName);
//为空抛异常
if (task == null) {
throw new IllegalArgumentException("Create task fail, there is no task corresponding to the task name. Make sure you have create a task instance in TaskCreator.");
}
//放入缓存
mTasks.put(taskName, task);
return task;
}
MyTaskCreator实现ITaskCreator接口,实现createTask方法,看里面其实就是构造了这些Task,通过把构造的过程放在这里,在Project传递任务的时候更加的方便,只需一个TASK_A字符串即可。
public static class MyTaskCreator implements ITaskCreator {
@Override
public Task createTask(String taskName) {
Log.d("==ALPHA==", taskName);
switch (taskName) {
case TASK_A:
return new TaskA();
case TASK_B:
return new TaskB();
case TASK_C:
return new TaskC();
case TASK_D:
return new TaskD();
case TASK_E:
return new TaskE();
case TASK_F:
return new TaskF();
case TASK_G:
return new TaskG();
}
return null;
}
}
继续看Project.Builder的add方法
public Builder add(String taskName) {
if (mTaskFactory == null) {
throw new IllegalAccessError(
"You should set a ITaskCreator with withTaskCreator(), and then you can call add() and after() with task name.");
}
Task task = mTaskFactory.getTask(taskName);
add(task);
return Builder.this;
}
public Builder add(Task task) {
addToRootIfNeed();
//给mCacheTask赋值为传入的task
mCacheTask = task;
//设置执行监听器,init方法中有构造
mCacheTask.setExecuteMonitor(mMonitor);
//设置mIsSetPosition为false
mIsSetPosition = false;
//添加任务执行完成监听器
mCacheTask.addOnTaskFinishListener(new InnerOnTaskFinishListener(mProject));
//设置成功执行后的任务是mFinishTask
mCacheTask.addSuccessor(mFinishTask);
return Builder.this;
}
/**
* 这个方法的作用是在需要的时候将当前添加的任务放到mStartTask的后面
*/
private void addToRootIfNeed() {
//mIsSetPosition前面init方法已经设置为true,这里直接返回
if (!mIsSetPosition && mCacheTask != null) {
mStartTask.addSuccessor(mCacheTask);
}
}
假如我们就编排一个任务A,效果是怎么样的呢?
mStartTask->任务A->mFinishTask
总结一下:
- 一个Project默认会有一个mStartTask和一个mFinishTask
- 编排的时候会将添加的库在合适的时机添加到mStartTask的后面
继续看after方法
public Builder after(String taskName) {
if (mTaskFactory == null) {
throw new IllegalAccessError(
"You should set a ITaskCreator with withTaskCreator(), and then you can call add() and after() with task name.");
}
//获取到Task
Task task = mTaskFactory.getTask(taskName);
after(task);
return Builder.this;
}
public Builder after(Task task) {
//将mCacheTask放到传入的task的后面
task.addSuccessor(mCacheTask);
//mFinishTask的前置任务中移除task
mFinishTask.removePredecessor(task);
//设置mIsSetPosition为true,已经设置好位置
mIsSetPosition = true;
return Builder.this;
}
看完after我们看到一些问题,
问题一:我能不能不add任何任务,直接after这个任务A呢?
问题二:我先add某个任务B,然后能不能after一个没有add的任务A呢?
看代码时候不行的,只能先add任务,然后这个任务也必须after一个已经add的任务。
至此,就完成了任务的编排。
任务执行
首先去看Project的mStartTask
public void start() {
Project project = null;
//暂时忽略掉多进程相关的逻辑,后面感兴趣可以自行分析
...
if (project != null) {
addListeners(project);
//调用start()
project.start();
} else {
AlphaLog.e(AlphaLog.GLOBAL_TAG, "No startup project for current process.");
}
}
public void start() {
//启动mStartTask
mStartTask.start();
}
这里会去调用mStartTask的run方法,并且会调用Project的onProjectStart回调,记录开始时间。mStartTask也是Task,run方法执行完也会调用notifyFinished()
/**
* 通知所有紧后{@code Task}以及{@code OnTaskFinishListener}自己执行完成。
*/
/*package*/ void notifyFinished() {
if (!mSuccessorList.isEmpty()) {
AlphaUtils.sort(mSuccessorList);
for (Task task : mSuccessorList) {
//调用所有mSuccessorList中Task的onPredecessorFinished
task.onPredecessorFinished(this);
}
}
if (!mTaskFinishListeners.isEmpty()) {
for (OnTaskFinishListener listener : mTaskFinishListeners) {
listener.onTaskFinish(mName);
}
mTaskFinishListeners.clear();
}
}
synchronized void onPredecessorFinished(Task beforeTask) {
//如果前置任务集合为空,直接返回
if (mPredecessorSet.isEmpty()) {
return;
}
//清除当前这个依赖任务
mPredecessorSet.remove(beforeTask);
//如果前置依赖任务不为空什么也不做,继续等待,为空,表示该自己执行了
if (mPredecessorSet.isEmpty()) {
start();
}
}
最后看mFinishTask执行完,做一些打印时间等操作
private static class AnchorTask extends Task {
private boolean mIsStartTask = true;
...
@Override
public void run() {
if (mExecuteListener != null) {
if (mIsStartTask) {
mExecuteListener.onProjectStart();
} else {
//回调onProjectFinish
mExecuteListener.onProjectFinish();
}
}
}
}
总结
看Alpha的源码让我感受到一种思想,图从某种意义上说也是链表的拓展。
单链表一个节点前面和后面只会有一个节点,图就是每一个节点的前面和后面都有可能出现多个节点,所以对于图,就不能再用preNode,nextNode.而应该用preList,nextList这样的数据结构。
看完是不是感觉自己也能写一个初始化框架了,无非就行借助已有的数据结构,设计模式编排自己的任务。