2.2 判断图中是否有环
2.2.1拓补排序的特性
如果图中有环,Task之间存在循环依赖,会造成遍历无法结束,尾节点无法添加。
在图论中,拓扑排序是一个有向无环图(DAG)的所有顶点的线性序列。且该序列必须满足下面两个条件:
- 每个顶点出现且只出现一次。
- 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。
那也就意味着如果一个图的拓补排序无法输出所有顶点,那么这个图中必定存在环,或者循环依赖。
2.2.2拓补排序的算法实现
-
从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出,同时把该节点的后继节点都减1,然后查找后继节点中入度为0的节点,找到后加入临时栈中(临时栈中都是入度为0的节点)。上图4中只有一个入度0的节点,就是Root节点
-
从临时栈中拿到入度为0的节点弹出元素加入拓补排序集合中,然后重复步骤1。直到临时栈中元素为空。拓补排序结束
代码如下
/**
-
判断图中是否有环
*/
private void isThereARing() {
// 临时栈,用于存放入度为0的节点
Stack nodeStack = new Stack<>();
nodeStack.push(root);
// 存放拓补排序排序的集合
ArrayList topologicalSort = new ArrayList<>();
while (!nodeStack.isEmpty()) {
TaskNode taskNode = nodeStack.pop();
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 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();
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();
}
}
}
遍历完成,整个初始化结束。
- 不使用图组织关系,串行执行时。使用上文提到的A,B,C,D,E, 每个Task模拟耗时2s,依赖关系保持不变。
class Application{
…
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
学习分享
在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021最新上万页的大厂面试真题
七大模块学习资料:如NDK模块开发、Android框架体系架构…
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示
g-TEAYb4pW-1711865865249)]
七大模块学习资料:如NDK模块开发、Android框架体系架构…
[外链图片转存中…(img-zKWnJrGu-1711865865250)]
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示