⭐️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 [BaguTree Pro] 知识星球提问。
Android Jetpack 开发套件是 Google 推出的 Android 应用开发编程范式,为开发者提供了解决应用开发场景中通用的模式化问题的最佳实践,让开发者可将时间精力集中于真正重要的业务编码工作上。
这篇文章是 Android Jetpack 系列文章的第 9 篇文章,完整目录可以移步至文章末尾~
前言
大家好,我是小彭。
2020 年 10 月 28 日,JetPack | App Startup 1.0.0 终于迎来正式发布,正好最近在总结组件化架构专题,所以也专门学习下 App Startup 的工作原理。在这篇文章里,我将带你总结 App Startup 的使用方法 & 实现原理 & 源码分析。有用请点赞给 Star,给小彭一点创作的动力,谢谢。
记录:2022 年 9 月 4 日修订:优化文章结构
学习路线图:
- 认识 AppStartup
1.1 App Startup 解决了什么问题?
App Startup 是 Google 提供的 Android 轻量级初始化框架:
- 优点:使用 App Startup 框架,可以简化启动序列并显式设置初始化依赖顺序,在简单、高效这方面,App Startup 基本满足需求。
- 不足:App Startup 框架的不足也是因为它太简单了,提供的特性太过简单,往往并不能完美契合商业化需求。例如以下特性 App Startup 就无法满足:
- 缺乏异步等待: 同步等待指的是在当前线程先初始化所依赖的组件,再初始化当前组件,App Startup 是支持的,但是异步等待就不支持了。举个例子,所依赖的组件需要执行一个耗时的异步任务才能完成初始化,那么 App Startup 就无法等待异步任务返回;
- 缺乏依赖回调: 当前组件所依赖的组件初始化完成后,未发出回调。
1.2 App Startup 如何实现自动初始化?
App Startup 利用了 ContentProvider 在应用启动的时候初始化的特性,提供了一个自定义 ContentProvider 来实现自动初始化。很多库都利用了 ContentProvider 的启动机制,来实现无侵入初始化,例如 LeakCanary 等
使用 AppStartup 还能够合并所有用于初始化的 ContentProvider ,减少创建 ContentProvider,并提供全局管理。
App Startup 示意图
详细的源码分析下文内容。
- App Startup 使用方法
这一节,我们来总结 App Startup 的使用步骤。
2.1 基本用法
- 1、添加依赖
在模块级 build.gradle
添加依赖:
模块级 build.gradle
1 | groovy复制代码implementation "androidx.startup:startup-runtime:1.0.0" |
- 2、实现 Initializer 接口
Initializer
接口是 App Startup 定义组件接口,用于指定组件的初始化逻辑和初始化顺序(也就是依赖关系),接口定义如下:
- 1、create(…) 初始化操作: 返回的初始化结果将被缓存,其中
context
参数就是当前进程的Application
对象; - 2、dependencies() 依赖关系: 返回值是一个依赖组件的列表,如果不需要依赖于其它组件,返回一个空列表。App Startup 在初始化当前组件时,会保证所依赖的组件已经完成初始化。
Initializer.java
1 | java复制代码public interface Initializer<T> { |
示例程序
1 | kotlin复制代码// LeakCanary 2.9.1 |
- 3、配置
在 Manifest 文件中将 Initializer 实现类配置到 androidx.startup.InitializationProvider
的 <meta-data>
中。
示例程序
1 | xml复制代码<!-- LeakCanary 2.9.1 --> |
要点如下:
- 1、组件名必须是
androidx.startup.InitializationProvider
; - 2、需要声明
android:exported="false"
,以限制其他应用访问此组件; - 3、要求
android:authorities
要求在设备中全局唯一,通常使用${applicationId}
作为前缀; - 4、需要声明
tools:node="merge"
,确保 manifest merger tool 能够正确解析冲突的节点; - 5、meta-data
android:name
为组件的 Initializer 实现类的全限定类名,android:value
固定为androidx.startup
。
提示: 为什么要将
androidx.startup
设置为 value,而不是 name?因为在键值对中,name 是唯一的,而 value 是允许重复的,将androidx.startup
放到 value 的话才能允许同时配置多个相同语义的<meta-data>
。
至此,App Startup 基本的使用与配置完成,在应用启动时,App Startup 会自动收集各个模块配置的 Initializer
实现类,并按照依赖顺序依次执行。
2.2 进阶用法
- 1、手动初始化
当你的组件需要进行手动初始化,而不是自动初始化时(例如存在耗时任务),可以进行手动初始化,而且手动初始化是可以在子线程调用的,而自动初始化均是在主线程执行的。
- App Startup 中会缓存初始化后的结果,重复调用
initializeComponent()
也不会导致重复初始化; - 要手动初始化的 Initializer 实现类不能在声明到 AndroidManifest 中,也不能被其它组件依赖,否则它依然会自动初始化。
调用以下方即可进行手动初始化:
示例程序
1 | kotlin复制代码AppInitializer.getInstance(context).initializeComponent(ExampleLoggerInitializer::class.java) |
- 2、取消自动初始化
假如有些库已经配置了自动初始化,而我们又希望进行懒加载时,就需要利用 manifest merger tool
的合并规则来移除这个库对应的 Initializer。具体如下:
示例程序
1 | ini复制代码<provider |
- 3、禁用 App Startup
假如需要完全禁用 App Startup 自动初始化,同样也可以利用到 manifest merger tool
的合并规则:
示例程序
1 | xml复制代码<provider |
- App Startup 原理分析
3.1 App Startup 如何实现自动初始化?
App Startup 利用了 ContentProvider 的启动机制实现自动初始化。ContentProvider 通常的用法是为当前进程 / 远程进程提供内容服务,它们会在应用启动的时候初始化。利用这个特性,App Startup 的方案就是自定义一个 ContentProvider 的实现类 InitializationProvider
,在 onCreate(…) 方法中执行初始化逻辑。
InitializationProvider.java
1 | java复制代码已简化 |
由于 ContentProvider 的其他方法是没有意义的,所以都抛出了 IllegalStateException
。
3.2 说一下 App Startup 的初始化过程
从上一节可以看到,App Startup 在 InitializationProvider
中调用了AppInitializer#discoverAndInitialize()
执行自动初始化。AppInitializer
是 App StartUp 框架的核心类,整个 App Startup 框架的代码其实非常少,其中很大部分核心代码都在 AppInitializer 类中。
我将整个自动初始化过程概括为 3 个阶段:
- 步骤 1 - 获取 数据: 扫描 Manifest 中定义在 InitializationProvider 里面的 数据,从中筛选出 Initializer 的配置信息;
- 步骤 2 - 递归执行初始化器: 通过 Initializer#create() 执行每个初始化器的逻辑,并且会通过 Initializer#dependencies() 优先保证依赖项已经初始化;
- 步骤 3 - 缓存初始化结果: 将初始化后的结果缓存到映射表中,避免重复初始化。
源码摘要如下:
AppInitializer.java
1 | java复制代码private static final Object sLock = new Object(); // 后面会提到 |
3.3 手动初始化的执行过程
前面我们提到使用 initializeComponent()
方法可以手动初始化,我们来看手动初始化(懒加载)的源码:
AppInitializer.java
1 | java复制代码public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) { |
其实非常简单,就是调用上一节的 doInitialize(...)
执行初始化。需要注意的是,这个方法是允许在子线程调用的,换句话说,自动初始化与手动初始化是存在线程同步问题的,那么 App Startup 是如何解决的呢?还记得我们前面有一个 sLock
没有说吗?其实它就是用来保证线程同步的锁:
AppInitializer.java
1 | java复制代码<T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) { |
- 总结
到这里,App Startup 的内容就讲完了。可以看到 App Startup 只是一个轻量级的初始化框架,能做的事情有限。市面上有开发者开源了基于 DAU 有向无环图的初始化框架,这个我们下次再说。关注我,带你了解更多。
参考资料
- App Startup —— Android Developers
- 合并多个清单文件 —— Android Developers
- AndroidX: App Startup —— Husayn Hakeem 著
- Jetpack新成员,App Startup 一篇就懂 —— 郭霖 著
- 我为何弃用 Jetpack 的 App Startup? —— 午后一小憩 著
- 更快!这才是我想要的 Android Startup 库! —— idisfkj 著
- 组件化:代码隔离也难不倒组件的按序初始化 —— leobert-lan 著
- 从源码看 Jetpack(5)Startup 源码详解 —— 叶志陈 著
推荐阅读
Android Jetpack 系列文章目录如下(2023/07/08 更新):
- #1 Lifecycle:生命周期感知型组件的基础
- #2 为什么 LiveData 会重放数据,怎么解决?
- #3 为什么 Activity 都重建了 ViewModel 还存在?
- #4 有小伙伴说看不懂 LiveData、Flow、Channel,跟我走
- #5 Android UI 架构演进:从 MVC 到 MVP、MVVM、MVI
- #6 ViewBinding 与 Kotlin 委托双剑合璧
- #7 AndroidX Fragment 核心原理分析
- #8 OnBackPressedDispatcher:Jetpack 处理回退事件的新姿势
- #9 食之无味!App Startup 可能比你想象中要简单
- #10 从 Dagger2 到 Hilt 玩转依赖注入(一)
⭐️ 永远相信美好的事情即将发生,欢迎加入小彭的 Android 交流社群~
本文转载自: 掘金