背景
在咱们app开发中,总会遇到各种各样的需要初始化的使命,各个使命又会有各种前后依靠,那么咱们怎么办!手动添加依靠联系,肯定会导致后期依靠本钱过大,所以咱们肯定会引进各种各样的结构去处理这个问题。那么咱们今日就来动手完成这么一个结构!
方针
咱们要完成的方针是:
- 支撑Task主动依靠转化,即咱们要完成不管咱们使命如何界说,只需声明了使命的依靠联系,就能主动处理好使命之间的处理联系
- 支撑使命循环依靠检测,即咱们使命假如存在循环依靠,那么就要给出相关的提示
拓扑算法
这里咱们开端引进一个小算法了,便是拓扑排序算法(对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中一切极点排成一个线性序列,使得图中恣意一对极点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。一般,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简略的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。)好啦,咱们看了完全不知道啥意思的官方解说,咱们直接看下图吧!
可以看到上图,简略来说,拓扑算法便是 把一幅图「拉平」,并且这个「拉平」的图里面,一切箭头方向都是共同的
拓扑排序有很多种完成的方法,比方BFS (广度优先遍历),DFS (深度优先遍历),这里咱们用BFS的方法完成
基于 BFS 的拓扑排序算法的过程如下:
- 计算图中每个节点的入度:遍历一切边,统计每个节点的入度(即指向该节点的边的数量)。
- 创立一个行列,将一切入度为 0 的节点参加行列。这些节点没有前驱节点,可以立即处理。
- 进入循环:当行列非空时进行如下操作:
a. 从行列中取出一个节点,将其添加到拓扑排序的成果列表中。
b. 遍历以此节点为开头的一切边,将边的结尾的入度减 1。假如边的结尾入度变为 0,则将其参加行列,表明可以处理此节点。
c. 删去此节点及其边。
- 假如成果列表中的节点数量与图中节点数量相同,则阐明一切节点都被处理,拓扑排序成功。假如不相同,阐明图中存在环,拓扑排序失利。
这里引荐一篇算法文章 环检测及拓扑排序算法
经过上述的算法,咱们就能完成方针1的内容了,接着,咱们还有方针2,怎么去完成检测到环并且给出详细的节点呢?咱们可以利用过程b的时分,削减入度的这个条件,咱们就可以知道,当产生环的时分,肯定会存在入度不为0的节点,而这个节点,便是咱们需要的环节点!
直接看代码
bard.google.com/ 咱们用GoogleIO大会刚刚推出的基于PaLM 2模型的bard机器人帮咱们写!
val indegree = hashMapOf<ITask, Int>()
val queue = LinkedList<ITask>()
val cycleNodes = mutableListOf<ITask>()
计算一切节点的入度
taskGraph.keys.forEach { it ->
taskGraph[it]?.forEach { itask ->
indegree[itask] = indegree.getOrElse(itask) { 0 } + 1
}
}
taskGraph 是一个图的邻接表完成
taskGraph.keys.forEach {
if (indegree.getOrElse(it) { 0 } == 0) {
queue.offer(it)
}
}
var count = 0
val result = mutableListOf<ITask>()
进入BFS
while (queue.isNotEmpty()) {
val node = queue.poll() ?: break
result.add(node)
count++
taskGraph[node]?.forEach {
indegree[it] = indegree.getOrElse(it) { 1 } - 1
if (indegree[it] == 0) {
queue.offer(it)
}
}
}
环检测
if (count != allTaskSet.size) {
for (entry in indegree) {
if (entry.value > 0) {
cycleNodes.add(entry.key)
}
}
throw java.lang.RuntimeException("find cycle dependents $cycleNodes")
}
return result
(ps:这个模型还不太好用,需要练习一下,才干写出正确的代码,以上代码经过我部分批改)
其实ITask,是咱们界说的一个接口
interface ITask {
fun handleTask(context:Context)
}
咱们经过上面方法,就能得到一个经过拓扑算法排序好的List,这个时分咱们只需要遍历一遍,调用每个ITask的方法即可
val list = detectCycleNodes()
list.forEach {
it.handleTask(context)
}
总结
咱们经过了解一个小算法,就能处理咱们的启动时的使命混乱管理的问题,最后上述代码我一致放在BFSLauncher,代码十分简洁,只需两个类,咱们就能处理掉一个大问题!