背景

在咱们app开发中,总会遇到各种各样的需要初始化的使命,各个使命又会有各种前后依靠,那么咱们怎么办!手动添加依靠联系,肯定会导致后期依靠本钱过大,所以咱们肯定会引进各种各样的结构去处理这个问题。那么咱们今日就来动手完成这么一个结构!

方针

咱们要完成的方针是:

  1. 支撑Task主动依靠转化,即咱们要完成不管咱们使命如何界说,只需声明了使命的依靠联系,就能主动处理好使命之间的处理联系
  2. 支撑使命循环依靠检测,即咱们使命假如存在循环依靠,那么就要给出相关的提示

拓扑算法

这里咱们开端引进一个小算法了,便是拓扑排序算法(对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中一切极点排成一个线性序列,使得图中恣意一对极点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。一般,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简略的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。)好啦,咱们看了完全不知道啥意思的官方解说,咱们直接看下图吧!

启动时任务治理-利用拓扑排序

可以看到上图,简略来说,拓扑算法便是 把一幅图「拉平」,并且这个「拉平」的图里面,一切箭头方向都是共同的

拓扑排序有很多种完成的方法,比方BFS (广度优先遍历),DFS (深度优先遍历),这里咱们用BFS的方法完成

基于 BFS 的拓扑排序算法的过程如下:

  1. 计算图中每个节点的入度:遍历一切边,统计每个节点的入度(即指向该节点的边的数量)。
  2. 创立一个行列,将一切入度为 0 的节点参加行列。这些节点没有前驱节点,可以立即处理。
  3. 进入循环:当行列非空时进行如下操作:

a. 从行列中取出一个节点,将其添加到拓扑排序的成果列表中。

b. 遍历以此节点为开头的一切边,将边的结尾的入度减 1。假如边的结尾入度变为 0,则将其参加行列,表明可以处理此节点。

c. 删去此节点及其边。

  1. 假如成果列表中的节点数量与图中节点数量相同,则阐明一切节点都被处理,拓扑排序成功。假如不相同,阐明图中存在环,拓扑排序失利。

这里引荐一篇算法文章 环检测及拓扑排序算法

经过上述的算法,咱们就能完成方针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,代码十分简洁,只需两个类,咱们就能处理掉一个大问题!