Android 启动优化(三)- AnchorTask 开源了
原理简介
AnchorTask,锚点任务,它的实现原理是构建一个有向无环图,拓扑排序之后,如果任务 B 依赖任务 A,那么 A 一定排在任务 B 之前。
了解原理之前,请必须先了解有向无环图和多线程的一些基本知识,不然,下文,你基本是看不懂的。
一个共识
-
前置任务:任务 3 依赖于任务 0,1,那么任务 3 的前置任务是任务 0, 1
-
子任务:任务 0 执行完之后,任务 3 才能执行,那么称呼任务 3 为 任务 0 的子任务
如何构建一个有向无环图
这里我们采用 BFS 方法实现,算法思想大概是这样的
-
建立入度表,入度为 0 的节点先入队
-
当队列不为空,进行循环判断
-
- 节点出队,添加到结果 list 当中
-
将该节点的邻居入度减 1
-
若邻居课程入度为 0,加入队列
-
若结果 list 与所有节点数量相等,则证明不存在环。否则,存在环
多线程中,任务执行是随机的,那如何保证任务被依赖的任务先于任务执行呢?
这里要解决的主要有三个问题
-
首先我们要解决一个问题,它有哪些前置任务,这个可以用 list 存储,代表它依赖的任务 list。当它所依赖的任务 list 没有执行完毕,当前任务需要等待。
-
当前任务执行完毕之后,所有依赖它的子任务需要感知到。我们可以用一个 map 来存储这种关系,key 是当前任务,value 是依赖于当前任务的集合(list)
-
多线程当中,等待和唤醒功能,有多种方式可以实现。wait、notify 机制,ReentrantLock Condition 机制,CountDownLatch 机制。这里我们选择 CountDownLatch 机制,因为 CountDownLatch 有点类似于计数器,特别适合这种场景。
具体实现
IAnchorTask
首先,我们定义一个 IAnchorTask 接口,主要有一个方法
-
isRunOnMainThread(): Boolean
表示是否在主线程运行,默认值是 false -
priority(): Int
方法 表示线程的优先级别,默认值是 Process.THREAD_PRIORITY_FOREGROUND -
needWait()
表示当我们调用AnchorTaskDispatcher await
时,是否需要等待,return true,表示需要等待改任务执行结束,AnchorTaskDispatcher await
方法才能继续往下执行。 -
fun getDependsTaskList(): List<class>?</class
方法返回前置任务依赖,默认值是返回 null. -
fun run()
方法,表示任务执行的时候
1interface IAnchorTask : IAnchorCallBack {
2
3 /**
4 * 是否在主线程执行
5 */
6 fun isRunOnMainThread(): Boolean
7
8 /**
9 * 任务优先级别
10 */
11 @IntRange(
12 from = Process.THREAD_PRIORITY_FOREGROUND.toLong(),
13 to = Process.THREAD_PRIORITY_LOWEST.toLong()
14 )
15 fun priority(): Int
16
17 /**
18 * 调用 await 方法,是否需要等待改任务执行完成
19 * true 不需要
20 * false 需要
21 */
22 fun needWait(): Boolean
23
24 /**
25 * 当前任务的前置任务,可以用来确定顶点的入度
26 */
27 fun getDependsTaskList(): List<Class>?
28
29 /**
30 * 任务被执行的时候回调
31 */
32 fun run()
33
34}
它有一个实现类 AnchorTask,增加了 await 和 countdown 方法
-
await 方法,调用它,当前任务会等待
-
countdown() 方法,如果当前计数器值 > 0,会减一,否则,什么也不操作
1abstract class AnchorTask : IAnchorTask {
2
3 private val countDownLatch: CountDownLatch = CountDownLatch(getListSize())
4 private fun getListSize() = getDependsTaskList()?.size ?: 0
5
6 companion object {
7 const val TAG = “AnchorTask”
8 }
9
10 /**
11 * self call,await
12 */
13 fun await() {
14 countDownLatch.await()
15 }
16
17 /**
18 * parent call, countDown
19 */
20 fun countdown() {
21 countDownLatch.countDown()
22 }
23}
排序实现
无环图的拓扑排序,这里采用的是 BFS 算法。具体的可以见 AnchorTaskUtils#getSortResult
方法,它有三个参数
-
list 存储所有的任务
-
taskMap: MutableMap<class, AnchorTask> = HashMap()</class
存储所有的任务,key 是 Class
,value 是 AnchorTask
taskChildMap: MutableMap<class, ArrayList<class>?> =<br /> HashMap()</class</class
,储存当前任务的子任务, key 是当前任务的 class,value 是 AnchorTask 的 list
算法思想
-
首先找出所有入度为 0 的队列,用 queue 变量存储
-
当队列不为空,进行循环判断。
-
从队列 pop 出,添加到结果队列
-
遍历当前任务的子任务,通知他们的入度减一(其实是遍历 taskChildMap),如果入度为 0,添加到队列 queue 里面
-
当结果队列和 list size 不相等试,证明有环
1 @JvmStatic
2 fun getSortResult(
3 list: MutableList, taskMap: MutableMap<Class, AnchorTask>,
4 taskChildMap: MutableMap<Class, ArrayList<Class>?>
5 ): MutableList {
6 val result = ArrayList()
7 // 入度为 0 的队列
8 val queue = ArrayDeque()
9 val taskIntegerHashMap = HashMap<Class, Int>()
10
11 // 建立每个 task 的入度关系
12 list.forEach { anchorTask: AnchorTask ->
13 val clz = anchorTask.javaClass
14 if (taskIntegerHashMap.containsKey(clz)) {
15 throw AnchorTaskException(“anchorTask is repeat, anchorTask is a n c h o r T a s k , l i s t i s anchorTask, list is anchorTask, list is list”)
16 }
17
18 val size = anchorTask.getDependsTaskList()?.size ?: 0
19 taskIntegerHashMap[clz] = size
20 taskMap[clz] = anchorTask
21 if (size == 0) {
22 queue.offer(anchorTask)
23 }
24 }
25
26 // 建立每个 task 的 children 关系
27 list.forEach { anchorTask: AnchorTask ->
28 anchorTask.getDependsTaskList()?.forEach { clz: Class ->
29 var list = taskChildMap[clz]
30 if (list == null) {
31 list = ArrayList<Class>()
32 }
33 list.add(anchorTask.javaClass)
34 taskChildMap[clz] = list
35 }
36 }
37
38 // 使用 BFS 方法获得有向无环图的拓扑排序
39 while (!queue.isEmpty()) {
40 val anchorTask = queue.pop()
41 result.add(anchorTask)
42 val clz = anchorTask.javaClass
43 taskChildMap[clz]?.forEach { // 遍历所有依赖这个顶点的顶点,移除该顶点之后,如果入度为 0,加入到改队列当中
44 var result = taskIntegerHashMap[it] ?: 0
45 result–
46 if (result == 0) {
47 queue.offer(taskMap[it])
48 }
49 taskIntegerHashMap[it] = result
50 }
51 }
52
53 // size 不相等,证明有环
54 if (list.size != result.size) {
55 throw AnchorTaskException(“Ring appeared,Please check.list is l i s t , r e s u l t i s list, result is list, result is result”)
56 }
57
58 return result
59
60 }
AnchorTaskDispatcher
AnchorTaskDispatcher 这个类很重要,有向无环图的拓扑排序和多线程的依赖唤醒,都是借助这个核心类完成的。
它主要有几个成员变量
1// 存储所有的任务
2 private val list: MutableList = ArrayList()
3
4 // 存储所有的任务,key 是 Class,value 是 AnchorTask
5 private val taskMap: MutableMap<Class, AnchorTask> = HashMap()
6
7 // 储存当前任务的子任务, key 是当前任务的 class,value 是 AnchorTask 的 list
8 private val taskChildMap: MutableMap<Class, ArrayList<Class>?> =
9 HashMap()
10
11 // 拓扑排序之后的主线程任务
12 private val mainList: MutableList = ArrayList()
13
14 // 拓扑排序之后的子线程任务
15 private val threadList: MutableList = ArrayList()
16
17 //需要等待的任务总数,用于阻塞
18 private lateinit var countDownLatch: CountDownLatch
19
20 //需要等待的任务总数,用于CountDownLatch
21 private val needWaitCount: AtomicInteger = AtomicInteger()
它有一个比较重要的方法 setNotifyChildren(anchorTask: AnchorTask)
,有一个方法参数 AnchorTask,它的作用是通知该任务的子任务,当前任务执行完毕,入度数减一。
1 /**
2 * 通知 child countdown,当前的阻塞任务书也需要 countdown
3 */
4 fun setNotifyChildren(anchorTask: AnchorTask) {
5 taskChildMap[anchorTask::class.java]?.forEach {
6 taskMap[it]?.countdown()
7 }
8 if (anchorTask.needWait()) {
9 countDownLatch.countDown()
10 }
11 }
先自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以扫码领取!!!!
![](https://img-blog.csdnimg.cn/img_convert/fce9984b66991521828fa2c3a5ca2e70.jpeg)
【延伸Android必备知识点】
【Android部分高级架构视频学习资源】
**Android精讲视频学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!
**任何市场都是优胜略汰适者生存,只要你技术过硬,到哪里都不存在饱和不饱和的问题,所以重要的还是提升自己。懂得多是自己的加分项 而不是必须项。门槛高了只能证明这个市场在不断成熟化!**另外一千个读者就有一千个哈姆雷特,所以以上只是自己的关键,不喜勿喷!
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。欢迎关注会持续更新和分享的。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可免费领取!
【延伸Android必备知识点】*
[外链图片转存中…(img-rxf2Gwnb-1711267439086)]
【Android部分高级架构视频学习资源】
**Android精讲视频学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!
**任何市场都是优胜略汰适者生存,只要你技术过硬,到哪里都不存在饱和不饱和的问题,所以重要的还是提升自己。懂得多是自己的加分项 而不是必须项。门槛高了只能证明这个市场在不断成熟化!**另外一千个读者就有一千个哈姆雷特,所以以上只是自己的关键,不喜勿喷!
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。欢迎关注会持续更新和分享的。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可免费领取!