android 框架_推荐一个更贴近 android 场景的启动框架 | Anchors
本文作者
作者:yummyLau
链接:
https://juejin.im/post/5f168dd9f265da22ce394a7a
本文由作者授权发布。
1背景
随着公司项目需求迭代,项目依赖库越来越多,Application#onCreate() 承载的初始化逻辑变得越来越复杂。
以上一年线上项目的初始化逻辑例子。
@Overridepublic void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) return; if (YXFDebuggable.strictMode()) { StrictModeUtil.startStrictMode(); } AppDirs.init(app, PACKAGE_NAME); // dirs YXFStorage.init(app); // storage YXFLogs.init(app, YXFDebuggable.log(), YXFBuildInfo.getBuildStamp()); // logs YXFBuildInfo.init(app); YXFDeviceInfo.init(app); // device & build info YXFReceivers.init(app); GLLogManager.init(app); CrashHandler.getInstance().init(app); //crash YXCrashManager.init(app, new Bugrpt163Capture()); // skin ServiceManager.register(app, SkinGuideImpl.class); ServiceManager.register(app, SkinServiceImpl.class); //rxjavaplugins GodlikeRxPlugins.init(); //省略 200 行逻辑 ...}
项目的初始化代码真的又臭又长...
在第一次项目重构的时候,尝试拆分初始化逻辑, 把原来的所有初始化逻辑划分为同步初始化和异步初始化,相对聚合的逻辑合成一个 Task 任务,Task 任务之间可相互依赖。
在代码逻辑上划分三个方法来承载 Task 初始化。
onCreateBlock(), 确保所有的 Task 在主线程上执行且在 Application#onCreate() 前完成
onCreateSync(),确保主线程上执行完成,使用 handle#post() 发送 Task 到主线程消息队列排队等待执行
onCreateAsync(),在子线程上完成所有 Task 执行
代码逻辑大致如下。
@Overridepublic void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { return; } onCreateBlock(this); onCreateSync(this); onCreateAsync(this);}private void onCreateBlock(Application app) { //task running}private void onCreateSync(Application app) { //post task to looper}private void onCreateAsync(final Application app) { new Thread(new Runnable() { @Override public void run() { //task running }).start();}
onCreateBlock() 内的任务为串行初始化,很大程度会影响到应用首帧显示的时机。
如果 onCreateBlock() 耗费的时间太长,那就要好好考虑下这些代码存在的必要性及合理性,重新设计了。
onCreateSync() 对首帧显示的时机无影响,onCreateAsync() 则不一定。
倘若不进行任务分类初始化,则首帧显示前需要完成所有任务的初始化
分类之后,对原来 Task 进行依赖排列如下
降低初始化阻塞时间收益如下
然而在实际场景中,拆分出来的三个方法中的任务可能存在依赖关系导致情况变得复杂:
onCreateBlock() 只能依赖 onCreateAsync() 内任务m依赖 onCreateSync() 会导致死锁;
onCreateSync() 可依赖其他两个方法内的任务;
onCreateAsync() 可依赖其他两个方法内的任务,同时可尽可能支持多条子线程任务来加快缩短所有异步任务的完成时间,这取决于当前设备的 CPU 状态
如果按照上述的逻辑来重新梳理启动任务的初始化,则需要实现一下逻辑:
封装 Task 支持上述三种场景的运行
提供线程池以运行异步任务,Handler#Post 运行同步非阻塞任务
以 Task 为图节点,构建一张应用依赖启动图并从头部开始初始化
Task 运行状态支持拦截提供外部业务逻辑获取状态,打断初始化等
多条异步子链运行时尽可能保持同时并发
...
希望完成上述功能,优先考虑现有轮子。于是在 github找到了 alpha 。
https://github.com/alibaba/alpha
alpha 是一个阿里巴巴开源的,基于PERT图构建的Android异步启动框架,协助应用启动时正确执行依赖任务。
集成了之后发现满足不了项目的应用场景,当时并没有很好的解决方法,迫于项目需求当晚就 clone 下了源码研究了实现,略感失落,但也找到了优化的方向。
2alpha 的缺陷
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 库。
3anchors 更适合 Android 启动场景
anchors 是一个基于图结构,支持同异步依赖任务初始化 Android 启动框架。其锚点特性提供 "勾住" 依赖的功能,能灵活解决初始化过程中复杂的同步问题。
https://github.com/YummyLau/Anchors
参考 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 codegetInstance() .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 | 线程信息 : Anchors Thread #10 | 开始时刻 : 1595319503047 ms | 等待运行耗时 : 1 ms | 运行任务耗时 : 3653 ms | 结束时刻 : 1595319506701 ==============================================D/Anchors: TASK_10_waiter -- onRelease --
anchors 框架自由度非常高,同异步混合链及 anchor 功能的结合使用可以灵活处理很多复杂初始化场景,但是要充分理解使用功能时的线程场景哦。
如果你还在梳理凌乱无序的启动任务而烦恼, 又或者为任务的同异步任务控制而心烦, anchors 绝对适合你哦 ? ? ?
https://github.com/YummyLau/Anchors
如果对启动场景有任何疑惑或者框架设计的意见与建议,欢迎评论留言~
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
直面底层:经常用的ViewTreeObserver 背后的原理“新技术” 又又又又又来了?大佬们,一波RxJava 3.0来袭,请做好准备~
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!
android 框架_推荐一个更贴近 android 场景的启动框架 | Anchors相关推荐
- java轻量分布式框架_推荐5个强大的Java分布式缓存框架
在开发中大型Java软件项目时,很多Java架构师都会遇到数据库读写瓶颈,如果你在系统架构时并没有将缓存策略考虑进去,或者并没有选择更优的缓存策略,那么到时候重构起来将会是一个噩梦.动宝儿为了帮助大家 ...
- java gui框架_推荐!程序员整理的Java资源大全
构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...
- down mark 打钩_推荐一个Markdown数学公式编辑器——Haroopad Mathjax
要在Markdown里插入数学公式,如果没有好用的的引擎or编辑器,那么只能插入图片了,十分麻烦.这里推荐一个十分强大的数学公式引擎--Mathjax. 配置 有道云笔记目前不支持浏览MathJax公 ...
- flutter 日历_Flutter:一个更贴近真实项目的练习
最近 Flutter 火的一塌糊涂,所以今天给大家推荐一个 Flutter 的练习项目.非常的适合入门级学习 Flutter 的开发者. 为什么这么说呢?因为它包含了完整 UI 设计图,更贴近真实项目 ...
- Carson带你学Android:这是一个与众不同的Android微信公众号
前言 今天,我想向广大Android开发者介绍一个与众不同.有特色的Android微信公众号,希望你们会喜欢. 注:我是本Android微信公众号的运营者carson_ho,简单介绍如下: 附:各大技 ...
- python 格式化工具_推荐一个小而美的 Python 格式化工具
原标题:推荐一个小而美的 Python 格式化工具 代码可读性是评判代码质量的标准之一,有一个衡量代码质量的标准是 Martin 提出的 "WFT" 定律,即每分钟爆出 " ...
- 知云文献翻译打不开_推荐一个很好用的阅读英文文献工具(研究僧看过来啦!!!)...
刚刚研一的小伙伴是不是看到整篇的英文文献十分苦恼... 科研小白表示完全看不懂英文论文啊,就算看完了再回忆回忆脑袋怎么是一片空白..金鱼的记忆. 我这六级的水平(更可能是四级的水平)怎么才能看懂这动不 ...
- 推荐一个开源快速开发erp管理系统的框架
前言 现在的程序开发人员大部分都不会自己去从零开发一个系统了,基本都是在使用一些免费的框架或破解一些有用的功能来自己二次开发组合使用,一般要达到要求是几个框架的功能组合才能实现. 程序开发人员都想找到 ...
- angular 图片引入_推荐一个 angular 图像加载插件
推荐一个简单的 Angular 图片加载插件:vgSrc,插件根据图片资源的不同加载状态,显示不同图片,亲测兼容IE-8. 使用 推荐使用 bower 加载: bower install vgSrc ...
最新文章
- RabbitMQ安装与初始配置【转载】
- 网盘们打打打起来了(doge)
- python—函数实例一
- 向不支持输入法的软件输入中文
- 第二阶段 工作总结 05
- 如何优雅地实现 C 编译期静态反射
- (fofa信息收集骚操作)windows查看文件的md5值
- Flutter视频播放、Flutter VideoPlayer 视频播放组件精要
- 【BZOJ4562】食物链,拓扑DP
- 美国自动驾驶研发国家队NASA入局,还提供了航天跑道做测试
- 简单介绍,基于ldirectord的高可用lvs-dr调度器
- 产品的国内版和国际版背后的思考
- 母亲节微信公众号走心文章就用来逗编辑器排版
- 25 张图详解交换机:秒懂二层交换机的 16 个问题
- win7系统移动磁盘合并卷
- 通过自媒体赚钱的13种方式,来看看你适合哪种
- php完全匹配,PHP完全匹配验证
- dnf超时空漩涡副本路线流程图_dnf怎么打超时空漩涡 dnf超时空漩涡打法攻略
- 视频、图像原理 设配选择 图像出入门概念理解
- RuntimeError: mat1 and mat2 shapes cannot be multiplied