**微信公众号:程序员徐公(stormjun94)**,本文首发我的微信公众号,有兴趣的可以点此扫码关注
前言
上周六,吃错了东西,突然头晕,发烧,半夜突然呕吐,拉肚子,整个人被折腾得够呛的。到医院检查,说是 xx 肠炎,吃错东西导致的,整整躺在家休息四天。
今天,终于缓过来了。更新一下 Android 启动优化有向无环图系列的最后一篇文章。最近一段时间,暂时不会更新这方面的文章了。系列文章汇总如下:
Android 启动优化(一) - 有向无环图
Android 启动优化(二) - 拓扑排序的原理以及解题思路
Android 启动优化(三) - AnchorTask 使用说明
Android 启动优化(四)- 手把手教你实现 AnchorTask
更新说明
- 之前的 0.1.0 版本 配置前置依赖任务,是通过
AnchorTask getDependsTaskList
的方式,他是通过 className
找到 AnchorTask
,并且内聚在当前的 AnchorTask 中,从全局的角度看 ,这种方式不太直观,1.0.0 放弃了这种方式,参考阿里 Alpha
的方式,通过 addTask(TASK_NAME_THREE).afterTask(TASK_NAME_ZERO, TASK_NAME_ONE)
- 1.0.0 版本新增了 Project 类,并增加
OnProjectExecuteListener
监听
- 1.0.0 版本新增
OnGetMonitorRecordCallback
监听,方便统计各个任务的耗时
说明
Android 启动优化,大家第一时间可能会想到异步加载。将耗时任务放到子线程加载,等到所有加载任务加载完成之后,再进入首页。
多线程异步加载方案确实是 ok 的。但如果遇到前后依赖的关系呢。比如任务2 依赖于任务 1,这时候要怎么解决呢。
假设我们有这样的任务依赖
我们要怎么使用它呢
1 2 3 4 5 6 7 8 9 10 11
| val project = AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG) .setAnchorTaskCreator(ApplicationAnchorTaskCreator()) .addTask(TASK_NAME_ZERO) .addTask(TASK_NAME_ONE) .addTask(TASK_NAME_TWO) .addTask(TASK_NAME_THREE).afterTask(TASK_NAME_ZERO, TASK_NAME_ONE) .addTask(TASK_NAME_FOUR).afterTask(TASK_NAME_ONE, TASK_NAME_TWO) .addTask(TASK_NAME_FIVE).afterTask(TASK_NAME_THREE, TASK_NAME_FOUR) .build() project.start().await()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class ApplicationAnchorTaskCreator : IAnchorTaskCreator { override fun createTask(taskName: String): AnchorTask? { when (taskName) { TASK_NAME_ZERO -> { return AnchorTaskZero() }
TASK_NAME_ONE -> { return AnchorTaskOne() } TASK_NAME_TWO -> { return AnchorTaskTwo() } TASK_NAME_THREE -> { return AnchorTaskThree() } TASK_NAME_FOUR -> { return AnchorTaskFour() } TASK_NAME_FIVE -> { return AnchorTaskFive() } } return null }
}
|
Demo 跑起来,可以看到预期的效果。
基本使用
第一步:在 moulde build.gradle 配置远程依赖
1
| implementation 'com.xj.android:anchortask:1.0.0'
|
最新的版本号可以看这里 lastedt version
第二步:自定义 AnchorTaskZero
,继承 AnchorTask
,并指定 taskName
,注意 taskName
必须是唯一的,因为我们会根据 taskName
找到相应的 AnchorTask
重写相应的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class AnchorTaskZero() : AnchorTask(TASK_NAME_ZERO) { override fun isRunOnMainThread(): Boolean { return false }
override fun run() { val start = System.currentTimeMillis() try { Thread.sleep(300) } catch (e: Exception) { } LogUtils.i( TAG, "AnchorTaskOne: " + (System.currentTimeMillis() - start) ) } }
|
如果任务 三 依赖任务 二,任务 一,可以这样写
1
| addTask(TASK_NAME_THREE).afterTask(TASK_NAME_ZERO, TASK_NAME_ONE)
|
最后,通过 project.start()
方法启动, 如果需要阻塞等待,调用 await() 方法
1 2 3 4 5 6 7 8 9 10
| AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG) .setAnchorTaskCreator(ApplicationAnchorTaskCreator()) .addTask(TASK_NAME_ZERO) .addTask(TASK_NAME_ONE) .addTask(TASK_NAME_TWO) .addTask(TASK_NAME_THREE).afterTask(TASK_NAME_ZERO, TASK_NAME_ONE) .addTask(TASK_NAME_FOUR).afterTask(TASK_NAME_ONE, TASK_NAME_TWO) .addTask(TASK_NAME_FIVE).afterTask(TASK_NAME_THREE, TASK_NAME_FOUR) .build() project.start().await()
|
监听任务回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| project.addListener(object : OnProjectExecuteListener { // project 开始执行的时候 override fun onProjectStart() { com.xj.anchortask.LogUtils.i(MyApplication.TAG, "onProjectStart ") }
// project 执行一个 task 完成的时候 override fun onTaskFinish(taskName: String) { com.xj.anchortask.LogUtils.i( MyApplication.TAG, "onTaskFinish, taskName is $taskName" ) }
// project 执行完成的时候 override fun onProjectFinish() { com.xj.anchortask.LogUtils.i(MyApplication.TAG, "onProjectFinish ") }
})
|
添加每个任务执行耗时回调
1 2 3 4 5 6 7 8 9 10 11 12 13
| project.onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback { // 所有 task 执行完毕会调用这个方法,Map 存储了 task 的执行时间, key 是 taskName,value 是时间,单位毫秒 override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) { onGetMonitorRecordCallback?.onGetTaskExecuteRecord(result) } // 所有 task 执行完毕会调用这个方法,costTime 执行时间 override fun onGetProjectExecuteTime(costTime: Long) { onGetMonitorRecordCallback?.onGetProjectExecuteTime(costTime) }
}
|
AnchorProject 介绍
AnchorTaskDispatcher start
方法必须在主线程调用,子线程调用会抛出异常。
await
阻塞当前线程,等待所有任务执行完毕之后,会自动往下走,await 方法携带一个参数,timeOutMillion 表示超时等待的时间
await()
方法必须在 start 方法之后调用
- 添加任务是通过
AnchorProject.Builder().addTask
添加的,典型的构造模式
- 设置执行的线程池,可以通过
AnchorProject.Builder().setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor)
AnchorTask 介绍
AnchorTask 实现了 IAnchorTask 接口,主要有几个方法
isRunOnMainThread(): Boolean
表示是否在主线程运行,默认值是 false
priority(): Int
方法 表示线程的优先级别,默认值是 Process.THREAD_PRIORITY_FOREGROUND
needWait()
表示当我们调用 AnchorTaskDispatcher await
时,是否需要等待,return true,表示需要等待改任务执行结束,AnchorTaskDispatcher await
方法才能继续往下执行。
fun run()
方法,表示任务执行的时候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| interface IAnchorTask : IAnchorCallBack {
/** * 是否在主线程执行 */ fun isRunOnMainThread(): Boolean
/** * 任务优先级别 */ @IntRange( from = Process.THREAD_PRIORITY_FOREGROUND.toLong(), to = Process.THREAD_PRIORITY_LOWEST.toLong() ) fun priority(): Int
/** * 调用 await 方法,是否需要等待改任务执行完成 * true 不需要 * false 需要 */ fun needWait(): Boolean
/** * 任务被执行的时候回调 */ fun run()
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class AnchorTaskOne : AnchorTask() { override fun isRunOnMainThread(): Boolean { return false }
override fun run() { val start = System.currentTimeMillis() try { Thread.sleep(300) } catch (e: Exception) { } LogUtils.i( TAG, "AnchorTaskOne: " + (System.currentTimeMillis() - start) ) }
}
|
监听任务的回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| val anchorTask = AnchorTaskTwo() anchorTask.addCallback(object : IAnchorCallBack { override fun onAdd() { com.xj.anchortask.LogUtils.i(TAG, "onAdd: $anchorTask") }
override fun onStart() { com.xj.anchortask.LogUtils.i(TAG, "onStart:$anchorTask ") }
override fun onFinish() { com.xj.anchortask.LogUtils.i(TAG, "onFinish:$anchorTask ") }
})
|
总结
AnchorTask 的原理不复杂,本质是有向无环图与多线程知识的结合。
- 根据 BFS 构建出有向无环图,并得到它的拓扑排序
- 在多线程执行过程中,我们是通过任务的子任务关系和 CounDownLatch 确保先后执行关系的
- 前置任务没有执行完毕的话,等待,执行完毕的话,往下走
- 执行任务
- 通知子任务,当前任务执行完毕了,相应的计数器(入度数)要减一。
AnchorTask
想看 1.0.0 版本的具体实现,可以看这篇文章。 AnchorTask 1.0.0 原理说明。
如果你觉得对你有所帮助,可以关注我的微信公众号 程序员徐公