Android任务执行框架

379 阅读5分钟

前言

前段时间把项目拆成了组件化(ARouter),发现每个组件的初始化存在个问题,如果要初始化ModuleA,app必须显式依赖ModuleA(implemention(project(ModuleA))),然后使用调用ModuleA.init(),虽然代码不多,但是有没有更简单的方式?


  • 2023-01-29 新增

应用市场要求,在隐私协议同意之前,不能调用数据存储、读取IMEI等操作,这种情况下各组件靠监听应用启动时初始化的行为有可能行不通了,比如推送组件需要读取IMEI,但是APP第一次启动时还未同意隐私协议,此时推送组件去初始化并读取IMEI是不合规的。也就是说需要分批进行初始化,启动时初始化一批组件,同意隐私之后初始化一批,还有可能登录之后初始化一批等等。


Module想要避免在App壳项目显式调用初始化,就要知道APP启动时机,然后Module自己初始化,简而言之就是Module如何监听APP启动?

利用ContentProvider的特性来完成,APP启动后,Application创建时,会实例化APP里面所有的ContentProvider。这样ModuleA里面可以放一个ContentProvider用来监听APP启动,然后执行初始化。

使用ContentProvider带来的问题

  1. 多进程问题,一个ContentProvider默认配置下,只会在主进程初始化,如果是多进程都要初始化,就需要再配置一个ContentProvider,指定process,很麻烦。
  2. 多Module问题,如果只有一两个Module,并且只在主进程初始化,使用这种方式还可以接受,但是一旦Module过多,每一个Module都有一个或多个ContentProvider,势必会影响启动速度。

有没有一种方式,可以将任务收集起来,然后使用一个ContentProvider来感知应用启动,然后依次执行初始化?

Google Jetpack里面Startup就是这么实现的,参考# Android | App Startup 可能比你想象中要简单

Jetpack Startup有啥优缺点么

优点:

  1. 集成简单
  2. 无侵入

缺点:

  1. 不支持异步,所有的都在主线程初始化
  2. 任务依赖过于复杂,需要依赖其他组件才能做到任务执行顺序的依赖关系,对组件化不友好

改进方案,需要实现的目标

  1. 集成简单
  2. 尽可能降低对启动速度的影响
  3. 组件化友好,无需组件之间相互依赖便可指定依赖关系
  4. 支持多进程
  5. 支持异步
  6. 支持优先级
  7. 解除各个任务的耦合

设计思路

  1. 定义注解,标记各个需要执行的任务。
  2. 使用apt收集各个任务。
  3. 使用KotlinPoet生成一个新的类TaskCollector,将收集到的任务放到该类里面的List保存起来。
  4. 使用AutoRegister将所有Module里面的TaskCollector注入到一个管理类。
  5. 实现一个任务启动框架,依次执行收集到的任务。

整体设计学习的听说你还不懂依赖任务启动框架?带你撸一个(感谢~),扩展了任务组和空闲执行功能,后续实现可参考该文章,流程和demo都有。

注解的设计

@Target(AnnotationTarget.CLASS) 
@Retention(AnnotationRetention.RUNTIME) 
annotation class InitTask( 
    /** * 任务名称,需唯一 */ 
    val name: String, 
    /** * 任务组 */ 
    val group: String, 
    /** * 是否在后台线程执行 */ 
    val background: Boolean = false,
    /** * 是否空闲时执行 */ 
    val whenIdle: Boolean = false,
    /** * 优先级,越小优先级越高 */ 
    val priority: Int = PRIORITY_NORM, 
    /** * 任务执行进程,支持主进程、非主进程、所有进程、:xxx、特定进程名 */ 
    val process: Array<String> = [PROCESS_ALL], 
    /** * 依赖的任务 */ 
    val depends: Array<String> = [] 
)

group:用于分组,该任务系统不仅仅是面向启动任务管理,还有一些特殊场景,比如用户登录后需要执行获取权限、特定配置等等一系列操作。使用group可将启动时所要执行的任务和登录后需要执行的任务区分开。

whenIdle:使用android IdleHander机制,主要应用于启动时,将一些非必须启动相关的初始化放到APP空闲的时候初始化,相当于一个启动速度的优化。

遇到的问题

  1. 创建annotion Module问题 需要使用new Module->Java or Kotlin Library

  2. 使用auto-service发现不好使 看看annotion是用kotlin定义的,引入auto-service使用的implication,需要改成kapt

  3. 使用kotlinpoet生成新类文件,报错javax.annotation.processing.FilerException: Attempt to reopen a file for path process方法会进入多次,导致会重复生成该类,异常了,解决方案是在process方法里面加上if(annotations.isEmpty()) { return false }

  4. 为什么要用AutoRegister,可以直接用kotlinpoet将所有任务收集到一个新类里面,App可以通过反射获取那个新类然后执行。 第一个原因就是需要用反射,使用AutoRegister修改字节码注入,无需反射,效率高。第二则是组件化问题,每一个组件使用kotlinpoet都会生成一个新的类,如何将所有生成的新类里面的任务列表统一收集起来,这就是AutoRegister的功能。

  5. 使用AutoRegister报错Module requires ASM6 AutoRegister问题,使用github.com/ooftf/AutoR…

参考

听说你还不懂依赖任务启动框架?带你撸一个

KotlinPoet

AutoRegister