topologicalSort.add(taskNode);
if (taskNode.nextList.size() != 0) {
for (TaskNode nextNode : taskNode.nextList) {
// 当前节点指向下一节点,将下一节点的入度 减1
nextNode.inDegree--;
// 如果下一节点的入度是0,将入度为 0 的节点入栈,用于下一次遍历
if (nextNode.inDegree == 0) {
nodeStack.push(nextNode);
}
}
}
}
// 抛出异常中断程序异常信息中提示 存在环的相关 Task
if (taskCount != topologicalSort.size()) {
taskNodes.removeAll(topologicalSort);
StringBuilder builder = new StringBuilder();
builder.append(" [");
for (TaskNode taskNode : taskNodes) {
builder.append(taskNode.getClass().getSimpleName());
builder.append(",");
}
builder.append(" ]");
throw new RuntimeException("there is a ring among" + builder.toString());
}
}

上图是一个有向无环图,输出的拖布排序序列为/[1,2,4,3,5/],如果 3,5 是循环依赖关系,则排序只会输出/[1,2,4/]就结束了。图中的元素无法全部遍历完成。
### [](
)2.3 多线程遍历图
因为牵扯子线程初始化任务,必须确保在跳转第一个业务页面时,所有的Task都初始化完成了。也就是说从遍历开始到结束,主线程是不可以跳转到闪屏页面的,而且部分初始化会在主线程进行。阻塞主线程就成了必需要做的事。
多线程遍历
runTask(root); // 开始遍历
waitMain();
private void runTask(final TaskNode taskNode) {
// 只有入度为0的节点才能开始运行
if (taskNode.backupInDegree.get() == 0) {
// 当前Task运行完成回掉
taskNode.setOnTaskResult(new OnTaskResult() {
@Override
public void OnTaskEnd(HashSet<TaskNode> nextList) {
// 遍历结束条件,尾节点遍历完成
if (taskNode instanceof TaskTail) {
return;
}
// 寻找下一节点,尝试运行。
for (TaskNode nextNode : taskNode.nextList) {
// 递减入度,直到为0的时候,该Task 才可以执行
nextNode.backupInDegree.decrementAndGet();
runTask(nextNode);
}
}
});
if (taskNode.isMainThread()) {
// 主线程任务放入消费队列,由主线程消费
try {
// 阻塞队列,会阻塞主线程
// blockingQueueMain = new ArrayBlockingQueue<TaskNode>();
blockingQueueMain.put(taskNode);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 子线程任务直接由线程池运行
executorService.execute(taskNode);
}
}
}
主线程阻塞代码
/**
-
遍历开始时,主线程阻塞,直到尾节点遍历结束。
*/
private void waitMain() {
long startTime = SystemClock.uptimeMillis();
// 超时逻辑,防止主线程阻塞超时
while (SystemClock.uptimeMillis() - startTime < timeOut) {
try {
TaskNode taskNode = blockingQueueMain.poll(timeOut, TimeUnit.MILLISECONDS);
taskNode.run();
// 到达尾节点直接跳出循环,放开主线程
if (taskNode instanceof TaskTail) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
遍历完成,整个初始化结束。
[](
)3.时间对比
--------------------------------------------------------------------
1. 不使用图组织关系,串行执行时。使用上文提到的A,B,C,D,E, 每个Task模拟耗时2s,依赖关系保持不变。
class Application{
...
onCreate(){
new TaskA().run();
new TaskC().run();
new TaskB().run();
new TaskD().run();
new TaskE().run();
....
}
...
}
2. 使用图组织依赖关系,开启两个子线程进行遍历。
TasksManager.getInstance(this).addTask(new TaskA())
.addTask(new TaskB())
.addTask(new TaskC())
.addTask(new TaskC())
.addTask(new TaskE()).start();
**时间对比**
| 机型//遍历方式 | 不使用图(主线程,时间ms) | 使用图(2个线程,时间ms) | 优化比例 |
| --- | --- | --- | --- |
| 小米Mix2(10.0系统) | 10000左右 | 6020~6040 | 39.6% 左右 |
| 魅族mx6(7.0系统) | 10000左右 | 6020~6050 | 39.5%左右 |
初始化时间在实际项目中也会因为依赖关系不同造成图的关系的不同。最差情况下,所有的Task会形成一个链表。最好的情况下所有的Task之间没有依赖关系。所以优化的百分比时间还要根据具体的业务场景来进行比对总结。
> 后续接入公司项目后,项目中有大概30+ sdk数量,初始化速度提升大概在30%-40%之间
[](
)4.总结
------------------------------------------------------------------
### 总结
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套**腾讯、字节跳动、阿里、百度2019-2021面试真题解析**,我把技术点整理成了**视频和PDF**(实际上比预期多花了不少精力),包**知识脉络 + 诸多细节**。
还有?**高级架构技术进阶脑图、Android开发面试专题资料**?帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。



**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)**
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
> 2021年虽然路途坎坷,都在说Android要没落,但是,不要慌,做自己的计划,学自己的习,竞争无处不在,每个行业都是如此。相信自己,没有做不到的,只有想不到的。祝大家2021年万事大吉。
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/149968.html