启动优化
,其实就是优化从点击icon到主页面展示这个过程的速度,让主界面尽量快的展现在用户面前。 所以我们要做的就是找到那些 耗时操作
,并将其优化。
耗时操作怎么找到?一般分成两个场景:
1、线下(debug)场景 在应用的开发阶段,我们一般通过 AOP
进行函数的耗时统计,通过 aspectj
库可以很方便的将代码插入到函数内部,从而统计到每个方法的 耗时时间
。 或者直接通过Android Studio 自带的 Profiler CPU
工具,查看每个方法的时间,CPU信息。
2、线上场景 当应用已经发布到线上,统计就变得不是那么容易了。所以我们一般就通过 函数插桩
的方式,自己写一个 统计耗时
的工具类,部署到需要统计的地方,比如Application和Activity的生命周期,数据库的初始化,第三方库的初始化,然后最后 上传数据到服务器
即可。
找到耗时的地方后该怎么优化解决呢?
一般就是通过分析这些要执行的任务,进行 异步,懒加载,预加载
等操作。
其中 「 异步任务 」 是很重要的一环,这里就涉及到我们今天要讲的内容了, 启动器
。顾名思义就是帮我们优化启动的一个工具,可以 高效合理
的帮我们安排启动过程中的一些任务处理。
接下来就带大家从源码开始分析,一起看看阿里的 启动器
—— Alpha
。
作为一个启动器,该有什么功能
有人可能要问了,不就是异步任务吗,我整几个线程,把任务往里面一丢不就行了。
事情可没那么简单,比如现在有 6个任务
需要在Application里面执行,其中 「 Task1,Task4,Tas6 」 需要在主线程执行, 「 Task2,Task3 」 需要在 「 Task1 」 执行完才能执行, 「 Task4,Task5 」 需要 「 Task2和Task3 」 执行完才能执行, 「 Task6 」 需要 「 Task4和Task5 」 执行完才能执行, 「 Task4 」 的耗时要大于 「 Task5 」 。
这是个啥啊?我晕了。这么多关系,我该怎么处理? 既然文字看着太麻烦,就画个图吧,这里涉及到一个用于 时间管理的图形——Pert图
。 Pert 图是一个有向图,能清晰地描述子任务之间的 「 依赖关系 」 。比如我们这个项目的情况,画成Pert 图如下:

Pert图.jpg
通过 Pert图
还是可以很直观的看到每个Task的关系,其中当执行完Task2和Task3之后,我们有两个选择,先执行Task4或者先执行Task5,由于Task4的耗时要大于Task5,所以我们就选择先执行Task4了。
其实 「 制定任务执行 」 的计划在我们生活中也随处可见,比如我们早起后也有很多事情要处理,比如烧水(5分钟),刷牙(3分钟),洗脸(2分钟),上厕所(8分钟)。怎么选一条 「 最优路线 」 能让我们最快完成这些事情呢?肯定是能一起并行的事情就安排到一起,然后并行的同时让耗时久的事情先发生。比如先烧水,然后上厕所的同时刷牙洗脸?扯远了扯远了,哈哈哈,收!
好了,看看我们如果用Alpha框架实现该怎么写呢?
//构造方法,true为主线程执行 Task1=new Task("task1",true); Task2=new Task("task2",false); Task3=new Task("task3",false); Task4=new Task("task4",true); Task5=new Task("task5",false); Task6=new Task("task6",true); //设置优先级,耗时操作优先级较高 Task4.setExecutePriority(1); Task5.setExecutePriority(2); Project.Builder builder = new Project.Builder().withTaskCreator(new MyTaskCreator()); builder.add(Task1); builder.add(Task2).after(Task1); builder.add(Task3).after(Task1); builder.add(Task4).after(Task2,Task3); builder.add(Task5).after(Task2,Task3); builder.add(Task6).after(Task4,Task5); builder.setProjectName("innerGroup"); AlphaManager.getInstance(mContext).addProject(builder.create()); AlphaManager.getInstance(mContext).start(); 复制代码
搞定!还不错吧。那就来一起分析下它吧!
首先,我们自己如果好好想想,如果让我们来做一个 异步启动框架
,需要考虑哪些问题?
就让我们带着这些问题去看看Alpha的内部源码。
Alpha源码解析
从上面的代码可以看到,任务的开启是由 AlphaManager.getInstance(mContext).start()
方法开始调用,所以我们就从这个 start
方法开始研究:
public void start() { Project project = null; do { //1.是否有为当前进程单独配置的Project,此为最高优先级 if (mProjectForCurrentProcess != null) { project = (Project) mProjectForCurrentProcess; break; } //2.如果当前是主进程,是否有配置主进程Project if (AlphaUtils.isInMainProcess(mContext) && mProjectArray.indexOfKey(MAIN_PROCESS_MODE) >= 0) { project = (Project) mProjectArray.get(MAIN_PROCESS_MODE); break; } //3.如果是非主进程,是否有配置非主进程的Project if (!AlphaUtils.isInMainProcess(mContext) && mProjectArray.indexOfKey(SECONDARY_PROCESS_MODE) >= 0) { project = (Project) mProjectArray.get(SECONDARY_PROCESS_MODE); break; } //4.是否有配置适用所有进程的Project if (mProjectArray.indexOfKey(ALL_PROCESS_MODE) >= 0) { project = (Project) mProjectArray.get(ALL_PROCESS_MODE); break; } } while (false); if (project != null) { addListeners(project); project.start(); } else { AlphaLog.e(AlphaLog.GLOBAL_TAG, "No startup project for current process."); } } 复制代码
哇,一开始就把我们多进程的疑惑给解决了。start方法首先就判断了当前的进程以及是否能匹配到相关进程的任务。可以看到一共有三种进程配置变量:
-
MAIN_PROCESS_MODE
: 主进程任务 -
SECONDARY_PROCESS_MODE
:非主进程任务 -
ALL_PROCESS_MODE
:适用于所有进程的任务
那么在哪里配置这些进程选项呢? addProject
方法
public void addProject(Task project, int mode) { if (project == null) { throw new IllegalArgumentException("project is null"); } if (mode < MAIN_PROCESS_MODE || mode > ALL_PROCESS_MODE) { throw new IllegalArgumentException("No such mode: " + mode); } if (AlphaUtils.isMatchMode(mContext, mode)) { mProjectArray.put(mode, project); } } 复制代码
ok,够简单吧。继续往下看start方法。 跳转到 project的start
方法:
@Override public void start() { mStartTask.start(); } 复制代码
这么简单吗,就开启了一个 mStartTask
?这个 mStartTask
是之前设置的那些任务中第一个任务吗?接着看:
//Project.java
private void init() {
...
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);
...
}
private static class AnchorTask extends Task {
private boolean mIsStartTask = true;
private OnProjectExecuteListener mExecuteListener;
public AnchorTask(boolean isStartTask, String name) {
super(name);
mIsStartTask = isStartTask;
}
public void setProjectLifecycleCallbacks(OnProjectExecuteListener callbacks) {
mExecuteListener = callbacks;
}
@Override
public void run() {
if (mExecuteListener != null) {
if (mIsStartTask) {
mExecuteListener.onProjectStart();
} else {
mExecuteListener.onProjectFinish();
}
}
}
}
复制代码
可以看到,在 Project类
的初始化方法中,定义了一个 开始任务
和一个 结束任务
。这是因为从执行角度看,一个任务序列必须有一个开始节点和一个结束节点。但是实际情况中,可能会有多个任务可以同时开始,而且有多个任务可以同时作为结束点。所以就设置了这两个节点 方便控制整个流程
,标记流程的开始和结束,也方便了 任务的监听
。
说回上面,开始任务的 start
方法走到哪里去了呢?自然是 AnchorTask的父类Task
,看看源码:
public synchronized void start() {
...
switchState(STATE_WAIT);
if (mInternalRunnable == null) {
mInternalRunnable = new Runnable() {
@Override
public void run() {
android.os.Process.setThreadPriority(mThreadPriority);
long startTime = System.currentTimeMillis();
switchState(STATE_RUNNING);
Task.this.run();
switchState(STATE_FINISHED);
long finishTime = System.currentTimeMillis();
recordTime((finishTime - startTime));
notifyFinished();
recycle();
}
};
}
if (mIsInUiThread) {
sHandler.post(mInternalRunnable);
} else {
sExecutor.execute(mInternalRunnable);
}
}
复制代码
源码还是挺简单的哈,定义了一个 Runnable
,然后判断是否主线程,并执行这个 Runnable
。其中还穿插了一些状态的改变,在 Runnable
内部主要是执行了 Task.this.run()
,也就是执行了任务本身。其中 setThreadPriority
方法主要是设置了线程的优先级,比如 THREAD_PRIORITY_DEFAULT
等,这里的优先级是较线程而言的,主要是针对CPU资源的竞争,跟我们需要的Task之间的优先级关系不大。 如果是需要在主线程执行的任务,就会通过 Handler(sHandler)
将事件传递给主线程执行。 如果是需要在非主线程执行的任务,就会通过 线程池(sExecutor)
去执行线程任务。
诶,好像没了?开始任务执行了就没了吗?再回头看看,还有一个 notifyFinished
方法。 按这个名字应该就是通知任务结束的一个方法,看看源码:
void notifyFinished() {
if (!mSuccessorList.isEmpty()) {
AlphaUtils.sort(mSuccessorList);
for (Task task : mSuccessorList) {
task.onPredecessorFinished(this);
}
}
if (!mTaskFinishListeners.isEmpty()) {
for (OnTaskFinishListener listener : mTaskFinishListeners) {
listener.onTaskFinish(mName);
}
mTaskFinishListeners.clear();
}
}
复制代码
这个方法主要做了三件事:
-
mSuccessorList
排序 -
监听回调
onTaskFinish
方法
mSuccessorList
是什么呢?我们叫它 「 紧后任务列表 」 ,也就是接下来要执行的任务列表。所以流程就是先把当前任务之后的任务列表进行一个排序,根据优先级排序。然后按顺序执行 onPredecessorFinished
方法。
如果紧后任务列表为空,也就代表没有后续任务了,那么就会走 onTaskFinish
回调方法,告知当前Project已经执行完毕。
接下来就看看紧后任务是怎么加进来的呢?又该怎么排序? onPredecessorFinished
方法又执行了些什么东西?
//1、紧后任务添加
public Builder after(Task task) {
task.addSuccessor(mCacheTask);
mFinishTask.removePredecessor(task);
mIsSetPosition = true;
return Builder.this;
}
void addSuccessor(Task task) {
task.addPredecessor(this);
mSuccessorList.add(task);
}
//2、紧后任务列表排序
public static void sort(List<Task> tasks) {
if (tasks.size() <= 1) {
return;
}
Collections.sort(tasks, sTaskComparator);
}
private static Comparator<Task> sTaskComparator = new Comparator<Task>() {
@Override
public int compare(Task lhs, Task rhs) {
return lhs.getExecutePriority() - rhs.getExecutePriority();
}
};
//3、紧后任务执行
synchronized void onPredecessorFinished(Task beforeTask) {
if (mPredecessorSet.isEmpty()) {
return;
}
mPredecessorSet.remove(beforeTask);
if (mPredecessorSet.isEmpty()) {
start();
}
}
复制代码
ok,源码写的很清楚了,这里逐步分析下:
由源码得知,紧后任务列表主要是通过 after方法
,还记得之前配置任务的时候吗? builder.add(Task2).after(Task1)
,所以这个after就代表Task2要在Task1后面执行,也就是Task2成了Task1的紧后任务。同理,Task1也就成了Task2的紧前任务。也就是代码中的 addPredecessor
方法,在添加紧后任务的同时也添加了紧前任务。
可能有人会问了,紧前任务添加了有什么用呢?难不成还倒退回去执行? 试想一下,如果有多个任务的紧后任务都是一个呢?比如这种情况: builder.add(Task4).after(Task2,Task3)
。Task4是Task2和Task3的紧后任务,所以在Task2执行完之后,还要判断Task3是否执行成功,然后才能执行Task4,这就是紧前任务列表的作用。这也就对应到上述代码中 onPredecessorFinished
方法的逻辑了。
然后这个紧后任务列表的排序是怎么排的呢?其实就是通过 getExecutePriority
方法获取task的执行优先级数字,按照正序排列,越小的任务执行时机越早。还记得之前配置的时候我设置了 setExecutePriority
方法吗,就是这里设置了优先级的。
至此主要逻辑就差不多了。好像还挺简单的是不是。还有一些细节我也简单的提下:
-
各种回调:包括一些task的回调,project的回调。
-
日志记录:比如耗时时间的记录,刚才执行任务时候的
recordTime方法
,就是记录了每个task的耗时。 -
多种Task配置方法:除了上面用Java代码配置,还可以通过
xml文件
来配置Project和里面的Task,这个就主要是
XmlPullParser
类来解析xml数据,然后生成Prject。 -
各种设计模式:比如构建Project的建造者模式,还有通过传入task名称就可以创建Task的工厂模式。
诸如此类的一些细节感兴趣朋友的可以自己下源码看看。
最后用一张流程图总结下吧:

Alpha流程图.jpg
分析下来,这个异步启动框架应该算比较简单的,但是能解决问题啊!其实我们平时工作中也可以做一些积累,然后写成工具或者框架,如果能开源出来大家一起使用还真是一件不错的事情呢!
「 你的一个:+1:,就是我分享的动力:heart:。 」
原文
https://juejin.im/post/5ef018e2f265da02d96ac81b
本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 更快!更高效!异步启动框架Alpha完全解析