分类之后,对原来 Task 进行依赖排列如下
降低初始化阻塞时间收益如下
然而在实际场景中,拆分出来的三个方法中的任务可能存在依赖关系导致情况变得复杂:
-
onCreateBlock() 只能依赖 onCreateAsync() 内任务m依赖 onCreateSync() 会导致死锁;
-
onCreateSync() 可依赖其他两个方法内的任务;
-
onCreateAsync() 可依赖其他两个方法内的任务,同时可尽可能支持多条子线程任务来加快缩短所有异步任务的完成时间,这取决于当前设备的 CPU 状态
如果按照上述的逻辑来重新梳理启动任务的初始化,则需要实现一下逻辑:
-
封装 Task 支持上述三种场景的运行
-
提供线程池以运行异步任务,Handler#Post 运行同步非阻塞任务
-
以 Task 为图节点,构建一张应用依赖启动图并从头部开始初始化
-
Task 运行状态支持拦截提供外部业务逻辑获取状态,打断初始化等
-
多条异步子链运行时尽可能保持同时并发
-
...
希望完成上述功能,优先考虑现有轮子。于是在 github找到了 alpha 。
alpha 是一个阿里巴巴开源的,基于PERT图构建的Android异步启动框架,协助应用启动时正确执行依赖任务。
集成了之后发现满足不了项目的应用场景,当时并没有很好的解决方法,迫于项目需求当晚就 clone 下了源码研究了实现,略感失落,但也找到了优化的方向。
alpha 的缺陷
1. 启动节点粒度不够细 alpha 封装了 Task 类用于表示一个异步任务, 衍生出Project 及 AnchorTask 用于处理多个 task 对象构成的图结构。
其外层业务只需要继承 Task 或者同个构建 Project 来编写业务依赖逻辑,理想的结构应该如下
但是如果你添加 Task 启动的时候,会收到 xxxTask cannot be cast to com.alibaba.android.alpha.Project。从源码层上看确实不能从一个 task 启动,缺乏灵活性。
2. 无法满足同异步初始化依赖并阻塞 Application#onCreate
alpha 定位为异步启动框架。在执行启动任务时判断其是否是主线程执行,如果是则通过 handler#post 发送出去排队处理, 否则交给线程池处理。
任务处理完成之后通知依赖该任务的任务进行依赖检查, 此时若依赖其的所有依赖都已完成,则启动该任务。
定义一个任务 T,其启动任务时刻为 tTStart,结束任务时刻为 tTEnd。
若存在以下依赖任务,
D(异步)-> C(异步) ->B (同步)->A(同步)
则 alpha 中恒满足 tAStart < tAEnd < tBStart < tBEnd < tCStart < tCEnd < tDStart < tDEnd。由于同步任务时通过队列排队处理,任务的执行并不是与代码块的上下文严格同步,当 Application#onCreate() 中要求严格的代码执行同步时,如
public void onCreate(){
startInitTask() //启动上述链
code //后置代码块
}
则后置代码块会优先被执行。当tCode 为代码块执行时刻时,恒满足 tCode < txStart (x = {A,B,C,D})
尽管 alpha 中提供 AlphaManager#waitUntilFinish 用于阻塞执行线程,但是存在很大缺陷:
假如在UI线程等待,则会造成死锁。
其原因在于当前执行代码处等待解锁,而只有等到所有在主线程执行的 task 执行完才可能解锁,而 task 被 post 到消息队列里面,只有当解锁之后才能执行到消息队列的 task。
3.缺乏任务执行同步支持,同异步混合链支持及调度优化
很多应用都会在 application#onCreate 前保证某些初始化任务完成之后再进入 activity 生命周期。
同时,如果在进入 activity 生命周期前这块宝贵的时候可结合设备的 cpu 资源来尽可能执行一些异步初始化任务。
遗憾的是,官方上一次更新时 2 年前了,并没有好好打算支持并维护好 alpha 库。
anchors 更适合 Android 启动场景
anchors 是一个基于图结构,支持同异步依赖任务初始化 Android 启动框架。其锚点特性提供 "勾住" 依赖的功能,能灵活解决初始化过程中复杂的同步问题。
参考 alpha 并改进其部分细节, 更贴合 Android 启动的场景, 同时支持优化依赖初始化流程, 选择较优的路径进行初始化。
目前已经稳定服务于我们线上项目一年多了,经过改造之后,相比 alpha 的优势
-
支持配置 anchors 等待任务链,常用于 application#onCreate 前保证某些初始化任务完成之后再进入 activity 生命周期回调。
-
支持主动请求阻塞等待任务,常用于任务链上的某些初始化任务需要用户逻辑确认;
-
支持同异步任务链,使你的依赖链不再局限于同步或者异步,框架灵活帮你切换调度。
如果一个任务要确保在 application#onCreate 前执行完毕,则该任务成为锚点任务,使用起来非常简单。
添加依赖
implementation 'com.effective.android:anchors:1.1.0'
在 Application 中启动依赖图,提供 java/kotlin 两套 api 方便使用。
//java 代码 AnchorsManager.getInstance().debuggable(true) .addAnchors(anchorYouNeed) //传递任务id设置锚点任务 .start(dependencyGraphHead); //传入依赖图头部
//kotlin code getInstance() .debuggable { true } .taskFactory { TestTaskFactory() } //可选,构建依赖图可以使用工厂, .anchors { arrayOf("TASK_9", "TASK_10") } //传递任务 id 设置锚点任务 .block("TASK_13") { //可选,如果需要用户在某个任务结束之后进行交互,则使用 block 功能 //According to business it.smash() or it.unlock() } .graphics { // 可选,当使用工程时支持 dsl 构建图,sons 接受子节点 UITHREAD_TASK_A.sons( TASK_10.sons( TASK_11.sons( TASK_12.sons( TASK_13))), TASK_20.sons( TASK_21.sons( TASK_22.sons(TASK_23))),
UITHREAD_TASK_B.alsoParents(TASK_22),
UITHREAD_TASK_C ) arrayOf(UITHREAD_TASK_A) } .startUp()
如果你打开了 debug 模式,则可以看到整个初始化过程的详细信息,可过渡不同 TAG 来获取对应信息。
TASK_DETAIL 任务执行信息
D/TASK_DETAIL: TASK_DETAIL ======================= task (UITHREAD_TASK_A ) ======================= | 依赖任务 : | 是否是锚点任务 : false | 线程信息 : main | 开始时刻 : 1552889985401 ms | 等待运行耗时 : 85 ms | 运行任务耗时 : 200 ms | 结束时刻 : 1552889985686
ANCHOR_DETAIL 锚点任务信息
W/ANCHOR_DETAIL: anchor "TASK_100" no found ! W/ANCHOR_DETAIL: anchor "TASK_E" no found ! D/ANCHOR_DETAIL: has some anchors!( "TASK_93" ) D/ANCHOR_DETAIL: TASK_DETAIL ======================= task (TASK_93 ) ======================= | 依赖任务 : TASK_92 | 是否是锚点任务 : true | 线程信息 : Anchors Thread #7 | 开始时刻 : 1552891353984 ms | 等待运行耗时 : 4 ms | 运行任务耗时 : 200 ms | 结束时刻 : 1552891354188
D/ANCHOR_DETAIL: All anchors were released!
LOCK_DETAIL 阻塞等待信息
D/LOCK_DETAIL: Anchors Thread #9- lock( TASK_10 ) D/LOCK_DETAIL: main- unlock( TASK_10 ) D/LOCK_DETAIL: Continue the task chain...
DEPENDENCE_DETAIL 依赖图信息
D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_9_start(1552890473721) --> TASK_90 --> TASK_91 --> PROJECT_9_end(1552890473721) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_9_start(1552890473721) --> TASK_90 --> TASK_92 --> TASK_93 --> PROJECT_9_end(1552890473721) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_8_start(1552890473721) --> TASK_80 --> TASK_81 --> PROJECT_8_end(1552890473721) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_8_start(1552890473721) --> TASK_80 --> TASK_82 --> TASK_83 --> PROJECT_8_end(1552890473721) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_7_start(1552890473720) --> TASK_70 --> TASK_71 --> PROJECT_7_end(1552890473720) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_7_start(1552890473720) --> TASK_70 --> TASK_72 --> TASK_73 --> PROJECT_7_end(1552890473720) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_6_start(1552890473720) --> TASK_60 --> TASK_61 --> PROJECT_6_end(1552890473720) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_6_start(1552890473720) --> TASK_60 --> TASK_62 --> TASK_63 --> PROJECT_6_end(1552890473720) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_5_start(1552890473720) --> TASK_50 --> TASK_51 --> PROJECT_5_end(1552890473720) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_5_start(1552890473720) --> TASK_50 --> TASK_52 --> TASK_53 --> PROJECT_5_end(1552890473720) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_4_start(1552890473720) --> TASK_40 --> TASK_41 --> TASK_42 --> TASK_43 --> PROJECT_4_end(1552890473720) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_3_start(1552890473720) --> TASK_30 --> TASK_31 --> TASK_32 --> TASK_33 --> PROJECT_3_end(1552890473720) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_2_start(1552890473719) --> TASK_20 --> TASK_21 --> TASK_22 --> TASK_23 --> PROJECT_2_end(1552890473719) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> PROJECT_1_start(1552890473719) --> TASK_10 --> TASK_11 --> TASK_12 --> TASK_13 --> PROJECT_1_end(1552890473719) D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> UITHREAD_TASK_B D/DEPENDENCE_DETAIL: UITHREAD_TASK_A --> UITHREAD_TASK_C
有使用锚点和使用锚点场景下, 针对每个依赖任务做 Trace 追踪, 可以通过 python systrace.py 来输出 trace.html 进行时间分析。
依赖图中有着一条 UITHREAD_TASK_A -> TASK_90 -> TASK_92 -> Task_93 依赖。
假设我们的这条依赖路径是后续业务的前置条件,则我们需要等待该业务完成后再进行自身的业务代码。如果不是则我们不关系他们的结束时机。
在使用锚点功能时,我们勾住 TASK_93,则从始端到该锚点的优先级将被提升,当在优先的 cpu 资源可用前提下,高优先级的链会优先抢占 cpu 资源进行使用。从上图可以看到执行该依赖链的时间缩短了。
anchors 项目同时提供了核心的 Sample 场景进行演示。
-
多进程初始化
-
某初始化链中间节点需要等待响应
-
某初始化链完成之后可能会再启动另一条新链
如 某初始化链中间节点需要等待响应 例子中,业务逻辑可决定是否终止链继续初始化等,如下点击 继续执行 。
Log 信息能看到阻塞解除了。
D/LOCK_DETAIL: main- unlock( TASK_10 ) D/LOCK_DETAIL: Continue the task chain... D/Anchors: TASK_10_waiter -- onFinish -- D/TASK_DETAIL: TASK_DETAIL
======================= task (TASK_10_waiter ) =======================
| 依赖任务 :
| 是否是锚点任务 : false
尾声
面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。
不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2017-2020字节跳动Android面试历年真题解析》
如果需要PDF版本可以自行领取!