前言
本章继续上一章的启动优化讲解,主要基于手淘全链路性能优化分析 Android StartUp 启动框架;
有向无环图(DAG)算法
我们首先去 LeetCode 上看一道算法题:课程表
看题目,可能好多算法薄弱的人有点懵逼,转换一下思路,例如:如果你想学习 OKHttp,那么你需要一些其他的知识来辅助你学习 OKhttp
那么你需要学习 java 只是,学习 Socket、Https 和设计模式,然后学习了这些知识之后,来辅助你学习 OKHttp,这样的一个依赖关系;
转换到我们进行启动优化上,我们在 Application 中初始化三方 SDK 的时候,就要考虑相互之间的依赖关系,而不是一股脑的所有 SDK 都放到子线程或者延迟初始化;
这种有依赖,有方向,没有形成回环的结构就是『有向无环图』简称 DAG;
在上面那张依赖图中,剪头我们通常叫做『边』,Socket 有一个箭头指向它,那么它的入度就是 1,OKHttp 有两个箭头指向它,那么它的入度就是 2,倒过来讲就是出度,Java 的出度是 2, OKHttp 的出度是 0;
那么,我们接下来如何通过代码来实现这个『有向无环图』我们可以借助 BFS 或者 DFS,对这两个感兴趣的可以看下这个链接:图文详解 BFS DFS
拓扑排序
拓扑排序是对一个有向图构造拓扑序列的过程;图的拓扑排序不是唯一的;实现拓扑排序的整体思路是:
- 找出图中 0 入度的顶点;
- 依次在图中删除这些顶点,删除后再找出图中 0 入度的顶点;
- 然后在删除…再找出…;
- 直至删除所有顶点,即完成拓扑排序;
Android StartUp 具体实现
首先我们来定一个 StartUp 接口,这个接口用来创建当前任务,获取当前任务依赖了哪些任务,以及当前任务依赖了多少个任务
任务的排序
public interface Startup<T> {
/**
* 创建当前任务
* @param context context
* @return T 任务执行结果,返回值类型
*/
T create(Context context);
/**
* 当前任务依赖了哪些任务,也就是入度数
*
* @return List<Class<? extends StartUp<?>>>
*/
List<Class<? extends Startup<?>>> dependencies();
/**
* 当前任务依赖了多少个任务
* @return int
*/
int getDependenciesCount();
}
然后定义一个抽象类 AndroidStartup 实现 Startup 接口
public abstract class AndroidStartup<T> implements Startup<T> {
/**
* 获取依赖的任务
* @return 依赖的任务
*/
@Override
public List<Class<? extends Startup<?>>> dependencies() {
return null;
}
/**
* 获取依赖的任务数
* @return int
*/
@Override
public int getDependenciesCount() {
List<Class<? extends Startup<?>>> dependencies = dependencies();
return dependencies == null ? 0 : dependencies.size();
}
}
这个抽象类中只实现了 dependencies 和 getDependenciesCount 接口;
然后定义了 5 个 Task;
CommonParamsTaskStartup 依赖 PushTaskStartup;
CuidTaskStartup 依赖 PushTaskStartup;
DeviceIdTaskStartup 依赖 CommonParamsTaskStartup
ImTaskStartup 依赖 DeviceIdTaskStartup、CuidTaskStartup
PushTaskStartup 没有任何依赖;
所以最终的有向无环图如下:
那么,具体是怎么实现这个拓扑排序的呢?我们来看下排序规则:
/**
* 拓扑排序
* @param startupList startupList
* @return StartupSortStore
*/
public static StartupSortStore sort(List<? extends Startup<?>> startupList) {
// 将所有的任务存入到一个map中
Map<Class<? extends Startup>, Startup<?>> startupMap = new HashMap<>();
// 入读表,记录每个任务的依赖数
Map<Class<? extends Startup>, Integer> inDegreeMap = new HashMap<>();
// 记录每个任务的依赖任务
Map<Class<? extends Startup>, List<Class<? extends Startup>>> startupChildrenMap = new HashMap<>();
// 记录入度为 0 的任务
Deque<Class<? extends Startup>> zeroDegree = new ArrayDeque<>();
// 遍历所有的任务
for (Startup<?> startup : startupList) {
startupMap.put(startup.getClass(), startup);
// 获取每个任务的依赖数
int dependenciesCount = startup.getDependenciesCount();
// 存入入度表中
inDegreeMap.put(startup.getClass(), dependenciesCount);
if (dependenciesCount == 0) {