一个更贴近 android 场景的启动框架 | Anchors

作者:yummyLau

背景

一年前多,想找一个适合项目的 Android 启动任务框架时,找到了 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() 中要求严格的代码执行同步时,如

则后置代码块会优先被执行。当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 的优势

  1. 支持配置 anchors 等待任务链,常用于 application#onCreate 前保证某些初始化任务完成之后再进入 activity 生命周期回调。
  2. 支持主动请求阻塞等待任务,常用于任务链上的某些初始化任务需要用户逻辑确认。
  3. 支持同异步任务链。

如果一个任务要确保在 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 任务执行信息

  • ANCHOR_DETAIL 锚点任务信息

  • LOCK_DETAIL 阻塞等待信息

  • 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,则从始端到该锚点的优先级将被提升。从上图可以看到执行该依赖链的时间缩短了。

anchors 项目同时提供了核心的 Sample 场景进行演示。

  1. 多进程初始化
  2. 某初始化链中间节点需要等待响应
  3. 某初始化链完成之后可能会再启动另一条新链

如 某初始化链中间节点需要等待响应 例子中,业务逻辑可决定是否终止链继续初始化等,如下点击 继续执行 。

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 
    | 线程信息 : Anchors Thread #10 
    | 开始时刻 : 1595319503047 ms
    | 等待运行耗时 : 1 ms
    | 运行任务耗时 : 3653 ms
    | 结束时刻 : 1595319506701 
    ==============================================
D/Anchors: TASK_10_waiter -- onRelease -- 

anchors 框架自由度非常高,同异步混合链及 anchor 功能的结合使用可以灵活处理很多复杂初始化场景,但是要充分理解使用功能时的线程场景哦。

如果你还在梳理凌乱无序的启动任务而烦恼, 又或者为任务的同异步任务控制而心烦, anchors 绝对适合你哦

如果对启动场景有任何疑惑或者框架设计的意见与建议,欢迎评论留言~

最后

福利福利福利!

分享一份大佬收录整理的**Android学习PDF+架构视频+源码笔记**,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料

这些都是我现在闲暇还会反复翻阅的精品资料。 相信可以有效的帮助大家掌握知识、理解原理。

你也可以拿去查漏补缺,提升自身的竞争力。

如果你有需要的话,可以私信我【提升】获取

互联网行业是一个知识迭代非常快的行业,如果你不养成学习的习惯,其他人不会停下来等你,这样的话你就等于一直在退步!

如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧

共勉!

猜你喜欢

转载自blog.csdn.net/ajsliu1233/article/details/107524947