Android 启动优化(四)- AnchorTask 是怎么实现的

Android 启动优化(三)- AnchorTask 开源了

原理简介


AnchorTask,锚点任务,它的实现原理是构建一个有向无环图,拓扑排序之后,如果任务 B 依赖任务 A,那么 A 一定排在任务 B 之前。

了解原理之前,请必须先了解有向无环图和多线程的一些基本知识,不然,下文,你基本是看不懂的

一个共识

  • 前置任务:任务 3 依赖于任务 0,1,那么任务 3 的前置任务是任务 0, 1

  • 子任务:任务 0 执行完之后,任务 3 才能执行,那么称呼任务 3 为 任务 0 的子任务

如何构建一个有向无环图

这里我们采用 BFS 方法实现,算法思想大概是这样的

  • 建立入度表,入度为 0 的节点先入队

  • 当队列不为空,进行循环判断

    • 节点出队,添加到结果 list 当中
  • 将该节点的邻居入度减 1

  • 若邻居课程入度为 0,加入队列

  • 若结果 list 与所有节点数量相等,则证明不存在环。否则,存在环

多线程中,任务执行是随机的,那如何保证任务被依赖的任务先于任务执行呢?

这里要解决的主要有三个问题

  1. 首先我们要解决一个问题,它有哪些前置任务,这个可以用 list 存储,代表它依赖的任务 list。当它所依赖的任务 list 没有执行完毕,当前任务需要等待。

  2. 当前任务执行完毕之后,所有依赖它的子任务需要感知到。我们可以用一个 map 来存储这种关系,key 是当前任务,value 是依赖于当前任务的集合(list)

  3. 多线程当中,等待和唤醒功能,有多种方式可以实现。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&gt;?</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&gt; = HashMap()</class存储所有的任务,key 是 Class

,value 是 AnchorTask

  • taskChildMap: MutableMap<class, ArrayList<class&gt;?&gt; =<br /> HashMap()</class</class,储存当前任务的子任务, key 是当前任务的 class,value 是 AnchorTask 的 list

算法思想

  1. 首先找出所有入度为 0 的队列,用 queue 变量存储

  2. 当队列不为空,进行循环判断。

  • 从队列 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移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以扫码领取!!!!

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

**任何市场都是优胜略汰适者生存,只要你技术过硬,到哪里都不存在饱和不饱和的问题,所以重要的还是提升自己。懂得多是自己的加分项 而不是必须项。门槛高了只能证明这个市场在不断成熟化!**另外一千个读者就有一千个哈姆雷特,所以以上只是自己的关键,不喜勿喷!

如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。欢迎关注会持续更新和分享的。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可免费领取!

【延伸Android必备知识点】*

[外链图片转存中…(img-rxf2Gwnb-1711267439086)]

【Android部分高级架构视频学习资源】

**Android精讲视频学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

**任何市场都是优胜略汰适者生存,只要你技术过硬,到哪里都不存在饱和不饱和的问题,所以重要的还是提升自己。懂得多是自己的加分项 而不是必须项。门槛高了只能证明这个市场在不断成熟化!**另外一千个读者就有一千个哈姆雷特,所以以上只是自己的关键,不喜勿喷!

如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。欢迎关注会持续更新和分享的。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可免费领取!

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值