范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

写个App启动任务框架,有多难?

  作者:王晨彦
  我们在开发应用的时候,一般都会引入 SDK,而大部分 SDK 都要求我们在 Application 中初始化,当我们引入的 SDK 越来越多,就会出现 Application 越来越长,如果 SDK 的初始化任务相互依赖,还要处理很多条件判断,这时,如果再来个异步初始化,相信大家都会崩溃。
  有人可能会说,我都在主线程按顺序初始化不就行了,当然行,只要老板不来找你麻烦。
  「小王啊,咱们的 APP 启动时间怎么这么久?」
  开个玩笑,可见,一个优秀的启动框架对于 APP 启动性能而言,是多么的重要!  一、为什么不用 Google 的 StartUp?
  说到启动框架,就不得不提 StartUp,毕竟是 Google 官方出品,现有的启动框架,或多或少都有参考 StartUp,这里不再详细介绍,如果对 StartUp 还不了解,可以参考这篇文章  Jetpack系列之App Startup从入门到出家 。
  https://juejin.cn/post/7023643365048582174
  StartUp 提供了简便的依赖任务初始化功能,但是对于一个复杂项目来说,StartUp 有以下不足:
  1. 不支持异步任务
  如果通过 ContentProvider 启动,所有任务都在主线程执行,如果通过接口启动,所有任务都在同一个线程执行。
  2. 不支持组件化
  通过 Class 指定依赖任务,需要引用依赖的模块。
  3. 不支持多进程
  无法单独配置任务需要执行的进程。
  4. 不支持启动优先级
  虽然可以通过指定依赖来设置优先级,但是过于复杂。  二、一个合格的启动框架是怎么样的?
  1. 支持异步任务
  减少启动时间的有效手段。
  2. 支持组件化
  其实就是解耦,一方面是解耦任务依赖,另一方面是解耦 app 和 module 的依赖。
  3. 支持任务依赖
  可以简化我们的任务调度。
  4. 支持优先级
  在没有依赖的情况下,允许任务优先执行。
  5. 支持多进程
  只在需要的进程中执行初始化任务,可以减轻系统负载,侧面提升 APP 启动速度。  三、收集任务
  如果要做到完全解耦,我们可以使用 APT 收集任务。
  首先定义注解,即任务的一些属性。  @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class InitTask(     /**      * 任务名称,需唯一      */     val name: String,     /**      * 是否在后台线程执行      */     val background: Boolean = false,     /**      * 优先级,越小优先级越高      */     val priority: Int = PRIORITY_NORM,     /**      * 任务执行进程,支持主进程、非主进程、所有进程、:xxx、特定进程名      */     val process: Array = [PROCESS_ALL],     /**      * 依赖的任务      */     val depends: Array = [] )
  name  作为任务唯一标识,类型为 String 主要是解耦任务依赖。
  background  即是否后台执行。
  priority  是在主线程、无依赖场景下的执行顺序。
  process  指定了任务执行的进程,支持主进程、非主进程、所有进程、:xxx、特定进程名。
  depends  指定依赖的任务。
  任务的属性定义好,还需要一个执行任务的接口:  interface IInitTask {     fun execute(application: Application) }
  任务需要收集的信息已经定义好了,那么看一下一个真正的任务长什么样。  @InitTask(     name = "main",     process = [InitTask.PROCESS_MAIN],     depends = ["lib"] ) class MainTask : IInitTask {     override fun execute(application: Application) {         SystemClock.sleep(1000)         Log.e("WCY", "main1 execute")     } }
  还是比较简洁清晰的。
  接下来需要通过 Annotation Processor 收集任务,然后通过 kotlin poet 写入文件。  class TaskProcessor : AbstractProcessor() {      override fun process(annotations: MutableSet?, roundEnv: RoundEnvironment): Boolean {         val taskElements = roundEnv.getElementsAnnotatedWith(InitTask::class.java)         val taskType = elementUtil.getTypeElement("me.wcy.init.api.IInitTask")          /**          * Param type: MutableList          *          * There"s no such type as MutableList at runtime so the library only sees the runtime type.          * If you need MutableList then you"ll need to use a ClassName to create it.          * [https://github.com/square/kotlinpoet/issues/482]          */         val inputMapTypeName =             ClassName("kotlin.collections", "MutableList").parameterizedBy(TaskInfo::class.asTypeName())          /**          * Param name: taskList: MutableList          */         val groupParamSpec = ParameterSpec.builder(ProcessorUtils.PARAM_NAME, inputMapTypeName).build()          /**          * Method: override fun register(taskList: MutableList)          */         val loadTaskMethodBuilder = FunSpec.builder(ProcessorUtils.METHOD_NAME)             .addModifiers(KModifier.OVERRIDE)             .addParameter(groupParamSpec)          for (element in taskElements) {             val typeMirror = element.asType()             val task = element.getAnnotation(InitTask::class.java)             if (typeUtil.isSubtype(typeMirror, taskType.asType())) {                 val taskCn = (element as TypeElement).asClassName()                  /**                  * Statement: taskList.add(TaskInfo(name, background, priority, process, depends, task));                  */                 loadTaskMethodBuilder.addStatement(                     "%N.add(%T(%S, %L, %L, %L, %L, %T()))",                     ProcessorUtils.PARAM_NAME,                     TaskInfo::class.java,                     task.name,                     task.background,                     task.priority,                     ProcessorUtils.formatArray(task.process),                     ProcessorUtils.formatArray(task.depends),                     taskCn                 )             }         }          /**          * Write to file          */         FileSpec.builder(ProcessorUtils.PACKAGE_NAME, "TaskRegister$moduleName")             .addType(                 TypeSpec.classBuilder("TaskRegister$moduleName")                     .addKdoc(ProcessorUtils.JAVADOC)                     .addSuperinterface(ModuleTaskRegister::class.java)                     .addFunction(loadTaskMethodBuilder.build())                     .build()             )             .build()             .writeTo(filer)          return true     } }
  看一下生成的文件长什么样。  public class TaskRegister$sample : ModuleTaskRegister {   public override fun register(taskList: MutableList): Unit {     taskList.add(TaskInfo("main2", true, 0, arrayOf("PROCESS_ALL"), arrayOf("main1","lib1"),MainTask2()))     taskList.add(TaskInfo("main3", false, -1000, arrayOf("PROCESS_ALL"), arrayOf(), MainTask3()))     taskList.add(TaskInfo("main1", false, 0, arrayOf("PROCESS_MAIN"), arrayOf("lib1"), MainTask()))   } }
  sample 模块收集到了3个任务,TaskInfo 对任务信息做了聚合。
  我们知道 APT 可以生成代码,但是无法修改字节码,也就是说我们在运行时想到拿到注入的任务,还需要将收集的任务注入到源码中。
  这里可以借助  AutoRegister  帮我们完成注入。
  https://github.com/luckybilly/AutoRegister
  注入前:  internal class FinalTaskRegister {     val taskList: MutableList = mutableListOf()      init {         init()     }      private fun init() {}      fun register(register: ModuleTaskRegister) {         register.register(taskList)     } }
  将收集到的任务注入到   init    方法中,注入后的字节码:/* compiled from: FinalTaskRegister.kt */ public final class FinalTaskRegister {     private final List taskList = new ArrayList();      public FinalTaskRegister() {         init();     }      public final List getTaskList() {         return this.taskList;     }      private final void init() {         register(new TaskRegister$sample_lib());         register(new TaskRegister$sample());     }      public final void register(ModuleTaskRegister register) {         Intrinsics.checkNotNullParameter(register, "register");         register.register(this.taskList);     } }
  我们通过 APT 生成的类已经成功的注入到代码中。
  小结
  至此,我们已经完成了任务的收集,通过 APT 和字节码修改是常见的类收集方案,相比反射,字节码修改没有任何性能的损失。
  后来发现 Google 已经推出了新的注解处理框架 ksp,处理速度更快,于是果断尝试了一把,所以有两种注解处理可以选择,GitHub 上有详细介绍。  四、任务调度
  任务调度是启动框架的核心,大家可能听到过。
  处理依赖任务首先要构建一个「有向无环图」。
  什么是有向无环图,看下维基百科的介绍:
  在图论中,如果一个有向图从任意顶点出发无法经过若干条边回到该点,则这个图是一个有向无环图(DAG, Directed Acyclic Graph)。
  听起来好像很简单,那么具体怎么实现呢,今天我们抛开高级概念不谈,用代码带大家实现任务的调度。
  首先,需要把任务分为两类,有依赖的任务和无依赖的任务。
  有依赖的首先检查是否有环,如果有循环依赖,直接 throw,这个可以套用公式 —— 如何判断链表是否有环。
  如果没有循环依赖,则收集每个任务的被依赖任务,我们称之为子任务,用于当前任务执行完成后,继续执行子任务。
  无依赖的最简单,直接按照优先级执行即可。
  不知道大家是否有疑问:有依赖的任务什么时候启动?
  有依赖的任务,依赖链的叶子端点一定是一个无依赖的任务,因此无依赖的任务执行完成后,就可以开始执行有依赖的任务。
  下面用一个小例子来介绍:  A 依赖 B、C  B 依赖 C  C 无依赖
  树形结构:
  1、分组并梳理子任务。  有依赖:
  A: 无子任务
  B: 子任务: [A]  无依赖:
  C: 子任务: [A, B]
  2、执行无依赖的任务C。
  3、更新已完成的任务: [C]。
  4、检查 C 的子任务是否可以执行。
  A: 依赖 [B, C],已完成任务中不包含 B,无法启动
  B: 依赖 [C],已完成任务中包含 C,可以执行
  5、执行任务 B。
  6、重复步骤 3,直到所有任务执行完成。
  下面我们就用代码来实现:
  使用递归检查循环依赖:  private fun checkCircularDependency(     chain: List,     depends: Set,     taskMap: Map ) {     depends.forEach { depend ->         check(chain.contains(depend).not()) {             "Found circular dependency chain: $chain -> $depend"         }         taskMap[depend]?.let { task ->             checkCircularDependency(chain + depend, task.depends, taskMap)         }     } }
  梳理子任务:  task.depends.forEach {     val depend = taskMap[it]     checkNotNull(depend) {         "Can not find task [$it] which depend by task [${task.name}]"     }     depend.children.add(task) }
  执行任务:  private fun execute(task: TaskInfo) {     if (isMatchProgress(task)) {         val cost = measureTimeMillis {             kotlin.runCatching {                 (task.task as IInitTask).execute(app)             }.onFailure {                 Log.e(TAG, "executing task [${task.name}] error", it)             }         }         Log.d(             TAG, "Execute task [${task.name}] complete in process [$processName] " +                     "thread [${Thread.currentThread().name}], cost: ${cost}ms"         )     } else {         Log.w( TAG, "Skip task [${task.name}] cause the process [$processName] not match")     }     afterExecute(task.name, task.children) }
  如果进程不匹配直接跳过。
  继续执行下一个任务:  private fun afterExecute(name: String, children: Set) {     val allowTasks = synchronized(completedTasks) {         completedTasks.add(name)         children.filter { completedTasks.containsAll(it.depends) }     }     if (ThreadUtils.isInMainThread()) {         // 如果是主线程,先将异步任务放入队列,再执行同步任务         allowTasks.filter { it.background }.forEach {             launch(Dispatchers.Default) { execute(it) }         }         allowTasks.filter { it.background.not() }.forEach { execute(it) }     } else {         allowTasks.forEach {             val dispatcher = if (it.background) Dispatchers.Default else Dispatchers.Main             launch(dispatcher) { execute(it) }         }     } }
  如果子任务的依赖任务都已经执行完毕,就可以执行了。
  最后还需要提供一个启动任务的接口,为了支持多进程,这里不能使用 ContentProvider。
  小结
  通过层层拆解,将复杂的依赖梳理清楚,用通俗易懂的方法,实现任务调度。
  源码
  https://github.com/wangchenyan/init
  另外,我也在 JitPack 上发布了 alpha 版本,欢迎大家尝试:  kapt "com.github.wangchenyan.init:init-compiler:1-alpha.1" implementation "com.github.wangchenyan.init:init-api:1-alpha.1"
  详细使用请移步  GitHub 。
  https://github.com/wangchenyan/init  最后
  本文以 StartUp 作为引子,阐述依赖任务启动框架还需要具备哪些能力,通过 APT + 字节码注入进行解耦,支持模块化,通过一个简单的模型来表述任务调度具体的实现方式。
  希望本文能够让大家了解依赖任务启动框架的核心思想,如果你有好的建议,欢迎评论交流探讨。
  在这里就还分享一份由大佬亲自收录整理的学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
  这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来取得一份不错的答卷。
  当然,你也可以拿去查漏补缺,提升自身的竞争力。
  真心希望可以帮助到大家,Android路漫漫,共勉!
  如果你有需要的话,只需私信我【进阶】即可获取

锤子手机回来了?罗永浩将携手机巨头再出发,网友那热闹了前些日子,罗永浩在晒出一篇苹果公众号宣传iOS15的文章,并吐槽没文化,真是没文化唉,强得很。能公开diss,这也不怪罗永浩,苹果这些文案还真像东北老哥的画风,确实有点敷衍了。除此iPhone13ProMax使用总结当开箱打开手机的那一刻,大屏给人的体验真是非常的爽!我自问也是见过世面的人了。从20年前摩托罗拉BB机开始,用过爱立信,诺基亚,以及刷过无数次机的HTC,到后来的魅族,小米,华为,苹果手机比国产手机贵那么多,为什么还有人买?首先,这个问法需要纠正。只能说苹果手机比多数国产手机贵,并非比所有国产手机贵,比如华为等不少旗舰机的价格比苹果还贵。其次,苹果手机的系统是封闭的,除专用APP外,其余各种游戏广告非谁拍的花朵更有创意,更好看?欢迎拿出来分享交流(请备注拍摄设备)?我用手机p30拍几多月季花请大家欣赏!佳能相机十微距镜头我就是用中华有为手机拍照,拍出相机的水平,不信你可以自己来看看。用的是p30por。我愛种花,日复一日,年复一年,年年种,喜联想这些年对国人做了什么呢?老陈今天给大家看看两张图片,让大家对联想再加深一下认识。第一个图是联想与华为的对比图联想与华为对比先看看两家公司研发费投入对比,联想92亿,华为1300亿,是联想的约15倍2020坚持自主研发菱电电控让中国大脑驱动国产汽车李羊摄证券时报记者邢云人体最精密最复杂的器官是大脑。同样,汽车的大脑发动机管理系统(EMS)是汽车电子领域最核心最关键的部分。不过,汽车产业链中,如此重要的EMS领域却由国外巨头掌论胡锡进评司马南质疑联想中提到的原罪环球时报总编,头条大胡总就日前热点司马南质疑柳杨发了一篇评论,其中提到很多民营企业都有原罪胡总原文我认为,倒过来追究联想是否导致了国有资产流失,需要非常非常谨慎。因为有不少民营企业诗意感受是人工智能体验不到的来源华西都市报在专访中,徐英瑾对哲学和人工智能进行了精彩阐述,他认为,让哲学家去对社会上各种现象随时发表评论,就好比让昆曲演员去跳disco。但如果要对通用人工智能的发展进行战略性360自由旋转的iPadPro磁吸支架,媲美苹果官方工艺,秒变iMac买前生产力,买后爱奇艺?为了打破这个刻板印象连库克都得变装特工亲自上阵为iPadPro拧螺丝了但是只能躺着的iPadPro,办公追剧实在太不Pro了,尤其是现在孩子们上网课还常常要存储芯片拐点已至四季度内存价格下跌预计明年下半年走向平衡21世纪经济报道记者倪雨晴深圳报道今年下半年以来,存储芯片市场的景气度一度面临分歧。8月,不少机构就对内存DRAM价格走势做出下跌预警。从三季度看,内存价格仍呈上升趋势,但是第四季联想问题,你怎么看?这个问题我可以这么回答你。你是中国人还是美国人联想的主要毛病是发生在下半程,脱壳改制当时都受政策鼓励的,我们不能切割历史来看,关键是发展起来后,一味图利而忘记做为中国企业,特別是受
2021年华为HDC开发者大会一票难求,全新HarmonyOS3。0有望登场今年的华为开发者大会(HDC。Together)将在10月22日24日举行,举办地在东莞松山湖的地标性建筑东莞篮球中心,大家期待的HarmonyOS(鸿蒙)3。0有可能在届时亮相也华为智慧屏V75SuperVS索尼A80JOLED,论实力谁是强者?近些年随着电视行业的不断发展,传统的电视已经逐渐在人们的视野中淡化,作为行业的新宠MiniLED电视已经在崭露头角,作为全新一代的电视机华为智慧屏V75Super和索尼A80JOL华为P50Pro手机移动影像的经典之作不久前,华为P50Pro(麒麟9000)在华为商城各大授权电商华为授权体验店授权零售商等渠道全线开售。华为P50Pro,在系统上搭载全新的鸿蒙HarmonyOS2,还首发跨越物理极Q2手机芯片成绩单出炉联发科出货量占比43,继续扮演领跑者近日,随着2021年第二季度手机芯片成绩单的出炉,联发科再次成为科技圈焦点,备受瞩目。据全球据调研机构CounterpointResearch发布的报告显示,全球智能手机APSoC宝能汽车作为汽车圈的新势力已完成混动与纯电领域的初步布局为实现全国碳达峰与碳中和目标,政策持续催化产业发展进程,全力助推光伏风电新型动力电池重大终端应用等发展,驱动新能源汽车产业全速奔跑。当下,国内新能源汽车高速发展的背后是纯电车型接受华为HDC开发者大会早鸟票抢先发售,你种草了么?华为作为科技领域的巨头,每一步的创新都备受瞩目,华为将要在10月22日24日举办一年一度的开发者大会(HDC),本次开发者大会的早鸟票在9月16日上午9时抢先开售,大家可以提前购票姚振华带领宝能集团与中科为深入交流多领域展开合作29年耕耘奋进,29载砥砺前行。宝能集团在姚振华的带领下不断发展壮大,如今已发展成为涵盖高端制造国际物流综合开发民生服务四大核心业务板块的大型现代化企业集团,业务遍布全国30多个省恒洁HLG65系列淋浴房人性化设计理念,给予更牢靠的安全感淋浴房是一种将洗浴空间管理和干湿分离等功能高度集成的家居浴具,其性能及时尚精美的外观深受现代年轻消费者的喜爱,越来越多家庭开始选择淋浴房来替代传统洗浴方式。但作为卫生间非标产品,如恒洁卫浴带你享受智美生活,守护家庭成员卫浴健康卫生间是每个家庭使用频率最高的空间之一,家庭可以偶尔不用厨房,但没办法不上卫生间。所以,对卫生间产品的挑选不可大意,一定要选择品质好,安全可靠的,恒洁是做卫浴产品的专业国内知名品牌姚振华打造产城融合典范宝能集团助力燕子矶全面升级当前,房地产行业已进入产城融合的新赛道,姚振华带领宝能城发依托宝能集团高端制造国际物流综合开发民生服务四大产业资源优势,持续提升自身资源整合能力和城市运营能力,倾力打造产城融合城市如果商用本也有奥运会金牌选手会是谁?东京奥运会已经缓缓落幕,这半个月的时间带给我们欢笑泪水,也带我们重新认识了很多优秀运动员和他们坚守的梦想。当我们回到家,习惯性打开电视机找到奥运频道,却突然想起奥运会已经结束的时候