Android App Initializer 实现方案
文章目录
- 1、背景
- 2、ContentProvider 方案进行初始化
- 2.1 概念
- 2.2 优缺点
- 2.3 使用步骤
- 3、Initializer 方案进行初始化
- 3.1 概念
- 3.2 优缺点
- 3.3 源代码
- 3.4 使用步骤
- 4、Jetpack App Startup 方案进行初始化
- 4.1 概念
- 4.2 优缺点
- 4.3 使用步骤:
- 4.3.1 dependencies 配置
- 4.3.2 实现 Initializer 接口
- 4.3.3 自动初始化
- 4.3.4 手动初始化
- 4.3.5 取消自动化
- 4.3.6 禁止所有的自动初始化
- 5、Android Startup 方案进行初始化
- 5.1 概念
- 5.2 优缺点
- 5.3 使用步骤:
- 5.3.1 dependencies 配置
- 5.3.2 实现 AndroidStartup 接口
- 5.3.3 自动初始化
- 5.3.4 手动初始化
- 5.3.5 更多请参考
- 6、上述四种方案对比
- 6.1 ContentProvider 方案进行初始化时间对照表
- 6.2 Initializer 方案进行初始化时间对照表
- 6.3 Jetpack App Startup 方案进行初始化时间对照表
- 6.4 Andorid Startup 方案进行初始化时间对照表
- 6.5 对比总结
- 7、参考资料
1、背景
- 想必大家都了解,我们在做
Android
开发的时候,我们都会在Application
中的attachBaseContext()
方法或者onCreate()
方法中去进行初始化操作,并顺便获取到Application
的上下文,这里的初始化包含我们项目本身的类初始化或第三方库初始化或第三方SDK
初始化。 - 但是上述这种常规操作,只能满足一些简单的业务需求,比较复杂的情况就不太优雅了,比如说组件化等场景。
- 那么有没有一种在应用启动时能够更加简单、高效的方式来初始化组件,且能适配各种复杂场景呢?
2、ContentProvider 方案进行初始化
2.1 概念
- 利用
ContentProvider
进行初始化,定义一个ContentProvider
,然后在onCreate()
方法中拿到上下文,然后在此进行初始化操作。很多第三方库就用到了这种办法,例如:LeakCanary 2.4
、Picasso 2.7
、AutoSize 1.1.2
等等。 ContentProvider
初始化流程图:
2.2 优缺点
- 优点:可以偷偷摸摸就进行初始化操作,而不是在
Application
中书写代码进行初始化操作。 - 缺点:如果
ContentProvider
过多,启动过多的ContentProvider
会增加应用的启动时间。
2.3 使用步骤
// TestInitializer.kt
class TestInitializer: ContentProvider() {override fun onCreate(): Boolean {val application = context !!.applicationContext as ApplicationSamplesLogger.i("ContentProvider Test onCreate: ")return true}override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {throw IllegalStateException("Not allowed.")}override fun getType(p0: Uri): String? {throw IllegalStateException("Not allowed.")}override fun insert(p0: Uri, p1: ContentValues?): Uri? {throw IllegalStateException("Not allowed.")}override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {throw IllegalStateException("Not allowed.")}override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {throw IllegalStateException("Not allowed.")}
}// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="xxx"><application...><providerandroid:name="xxx.TestInitializer"android:authorities="${applicationId}.TestInitializer"android:exported="false" /></application></manifest>
3、Initializer 方案进行初始化
3.1 概念
- 定义
Initializer
接口,然后新增一个类实现该接口,并在AndroidManifest.xml
文件内,添加其对应的meta-data
标签信息,最终在Application
中的attachBaseContext()
方法或者onCreate()
方法中进行init
操作。 Initializer
初始化流程图:
3.2 优缺点
- 优点:可以简化启动序列并显式设置初始化依赖顺序,且简单、高效,比较符合国内合规隐私要求。
- 缺点:还是需要依赖在壳
App
的Application
中进行AppInitializer.initialize(this)
操作。
3.3 源代码
// Initializer.kt
/*** 定义一个接口,用于在应用程序启动期间初始化库*/
interface Initializer {/*** 初始化操作*/fun initialize(context: Context)/*** 依赖关系,返回值是一个依赖组件的 List 集合*/fun dependencies(): List<Class<out Initializer>>}// AppInitializer.kt
/*** 用于初始化所有发现的组件化初始化器* <br/>* 发现机制是通过合并的 `AndroidManifest.xml` 中的 `<meta-data>` 标签条目*/
object AppInitializer {/** 记录当前已经初始化操作后的 Set 集合 */private val mInitialized = mutableSetOf<Class<out Initializer>>()/** 记录当前已经发现的 Set 集合 */private val mDiscovered = mutableSetOf<Class<out Initializer>>()/*** 初始化* @param context 上下文*/fun initialize(context: Context) {discoveryAndInitialize(context)}/*** 初始化* @param context 上下文* @param component 组件*/fun initializeComponent(context: Context, component: Class<out Initializer>) {doInitialize(context, component, mutableSetOf())}/*** 发现并准备初始化操作* @param context 上下文*/private fun discoveryAndInitialize(context: Context) {try {// 获取当前应用的 ApplicationInfo 信息val applicationInfo: ApplicationInfo = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)// 获取当前应用的 metaData 信息val metaData: Bundle = applicationInfo.metaData// 获取 meta-data 标签 com.open.initializeval initialize = context.getString(R.string.open_initialize)// 存储正在执行初始化操作的 Set 集合val initializing = mutableSetOf<Class<*>>()// 遍历 meta-data 标签集合metaData.keySet().forEach {key ->val value = metaData.get(key)if (initialize == value) {// 通过反射获取类val clazz = Class.forName(key)if (Initializer::class.java.isAssignableFrom(clazz)) {val component = clazz as Class<out Initializer>mDiscovered.add(component)InitializerLogger.i("Discovered $key")doInitialize(context, component, initializing)}}}} catch (e: PackageManager.NameNotFoundException) {e.printStackTrace()InitializerLogger.e("NameNotFoundException ${e.localizedMessage}")}}/*** 执行初始化操作* @param context 上下文* @param component 组件* @param initializing 正在执行初始化操作的 Set 集合*/@Synchronizedprivate fun doInitialize(context: Context, component: Class<out Initializer>, initializing: MutableSet<Class<*>>) {// 鲁棒性判断if (initializing.contains(component)) {InitializerLogger.e("Cannot initialize ${component.name}")return}if (! mInitialized.contains(component)) {initializing.add(component)try {// 获取声明的构造函数val instance = component.getDeclaredConstructor().newInstance()val initializer = instance as Initializer// 获取依赖关系的 List 集合val dependencies = initializer.dependencies()if (dependencies.isNotEmpty()) {// 如果存在依赖关系,优先初始化依赖关系dependencies.forEach {clazz ->if (! mInitialized.contains(clazz)) {doInitialize(context, clazz, initializing)}}}InitializerLogger.i("Initializing ${component.name}")// 开始执行初始化操作initializer.initialize(context)InitializerLogger.i("Initialized ${component.name}")// 初始化执行完成后,将其从正在进行初始化 Set 集合中移除initializing.remove(component)// 然后将其加入已经初始化完毕的 Set 集合中mInitialized.add(component)} catch (e: Throwable) {InitializerLogger.i("Initialize error -> ${e.localizedMessage}")}}}}// InitializerLogger.kt
/*** 初始化器的 Log 日志类*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
object InitializerLogger {/** 日志标签 */private const val TAG = "InitializerLogger"/*** To enable logging set this to true.*//** 要启用日志记录,请将其设置为 true */private const val DEBUG = true/*** Info 级别日志记录** @param message 日志消息*/fun i(message: String) {if (DEBUG) Log.i(TAG, message)}/*** Warning 级别日志记录** @param message 日志消息*/fun w(message: String) {if (DEBUG) Log.w(TAG, message)}/*** Error 级别日志记录** @param message 日志消息*/fun e(message: String) {if (DEBUG) Log.e(TAG, message)}}// values.xml
<?xml version="1.0" encoding="utf-8"?>
<resources><string name="open_initialize" translatable="false">com.open.initialize</string>
</resources>
3.4 使用步骤
// TestInitializer.kt
class TestInitializer: Initializer {override fun initialize(context: Context) {SamplesLogger.i("Initializer Test initialize: ")}override fun dependencies(): List<Class<out Initializer>> {SamplesLogger.i("Initializer Test dependencies: ")return emptyList()}}// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="xxx"><applicationandroid:name=".MyApplication"...><meta-dataandroid:name="xxx.TestInitializer"android:value="com.open.initialize" /></application></manifest>// MyApplication.kt
class MyApplication: Application() {override fun attachBaseContext(base: Context?) {super.attachBaseContext(base)}override fun onCreate() {super.onCreate()AppInitializer.initialize(this)}}
4、Jetpack App Startup 方案进行初始化
4.1 概念
Android Jetpack
提供的App Startup
方案有点像ContentProvider 中初始化
和Initializer 中初始化
方案的结合体,相对来说属于更优的实现(推荐)。Jetpack App Startup
初始化流程图:
4.2 优缺点
- 优点:使用
App StartUp
框架,可以简化启动序列并显式设置初始化依赖顺序,在简单、高效这方面,App Startup
基本满足需求。 - 缺点:
App Startup
框架的不足也是因为它太简单了,提供的特性太过简单,往往并不能完美契合商业化需求。例如以下特性App Startup
就无法满足:- (1)缺乏异步等待:同步等待指的是在当前线程先初始化所依赖的组件,再初始化当前组件,
App Startup
是支持的,但是异步等待就不支持了。举个例子,所依赖的组件需要执行一个耗时的异步任务才能完成初始化,那么App Startup
就无法等待异步任务返回。 - (2)缺乏依赖回调:当前组件所依赖的组件初始化完成后,未发出回调。
- (1)缺乏异步等待:同步等待指的是在当前线程先初始化所依赖的组件,再初始化当前组件,
4.3 使用步骤:
4.3.1 dependencies 配置
dependencies {implementation "androidx.startup:startup-runtime:1.1.1"
}
4.3.2 实现 Initializer 接口
class TestInitializer: Initializer<Unit> {/*** 初始化操作,返回的初始化结果将被缓存,其中 context 参数是 Application*/override fun create(context: Context) {SamplesLogger.i("Startup Test onCreate: ")}/*** 依赖关系,返回值是一个依赖组件的列表,如果不需要依赖于其它组件,返回一个空列表。* App Startup 在初始化当前组件时,会保证所依赖的组件已经完成初始化。*/override fun dependencies(): MutableList<Class<out Initializer<*>>> {SamplesLogger.i("Startup Test dependencies: ")return mutableListOf()}}
4.3.3 自动初始化
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="xxx"><application...><providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"android:exported="false"tools:node="merge"><meta-dataandroid:name="xxx.TestInitializer"android:value="androidx.startup" /></provider></application></manifest>
- 注意点:
- (1)组件名必须是
androidx.startup.InitializationProvider
; - (2)需要声明
android:exported="false"
,以限制其他应用访问此组件; - (3)要求
android:authorities
在整个手机唯一,通常使用${applicationId}
作为前缀; - (4)需要声明
tools:node="merge"
,确保manifest merger tool
能够正确解析冲突的节点; - (5)
meta-data
name
为组件的Initializer
实现类全限定名,value
为androidx.startup
。
4.3.4 手动初始化
- 在组件需要进行懒加载时(耗时任务),可以进行手动初始化。需要手动初始化的
Initializer
不需要在AndroidManifest
中进行声明,也不应该被其它组件依赖。调用以下方即可进行手动初始化:
AppInitializer.getInstance(context).initializeComponent(TestInitializer::class.java)
- 注意事项:
App Startup
中会缓存初始化后的结果,重复调用initializeComponent()
不会导致重复初始化。
4.3.5 取消自动化
- 假如有些库已经配置了自动初始化,而我们又希望进行懒加载时,就需要利用
manifest merger tool
的合并规则来移除这个库对应的Initializer
。具体如下:
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="xxx"><application...><providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"android:exported="false"tools:node="merge"><meta-dataandroid:name="xxx.TestInitializer"tools:node="remove" /></provider></application></manifest>
4.3.6 禁止所有的自动初始化
- 假如需要禁止
App Startup
自动初始化,同样也需要利用manifest merger tool
的合并规则:
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="xxx"><application...><providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"tools:node="remove" /></application></manifest>
5、Android Startup 方案进行初始化
5.1 概念
Android Startup
提供一种在应用启动时能够更加简单、高效的方式来初始化组件。开发人员可以使用Android Startup
来简化启动序列,并显式地设置初始化顺序与组件之间的依赖关系。 与此同时Android Startup
支持同步与异步等待,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。Android Startup
初始化流程图:
5.2 优缺点
- 优点:使用
Andorid StartUp
框架,可以简化启动序列并显式设置初始化依赖顺序,且支持同步与异步等待,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。 - 缺点:稳定性怀疑,没有进行验证。
5.3 使用步骤:
5.3.1 dependencies 配置
repositories {mavenCentral()
}dependencies {implementation 'io.github.idisfkj:android-startup:1.1.0'
}
5.3.2 实现 AndroidStartup 接口
class TestInitializer: AndroidStartup<String>() {/*** 用来控制 create() 方法调时所在的线程,返回 true 代表在主线程执行*/override fun callCreateOnMainThread(): Boolean = true/*** 用来控制当前初始化的组件是否需要在主线程进行等待其完成。如果返回 true,将在主线程等待,并且阻塞主线程*/override fun waitOnMainThread(): Boolean = false/*** 组件初始化方法,执行需要处理的初始化逻辑,支持返回一个 T 类型的实例*/override fun create(context: Context): String? {// todo somethingSamplesLogger.i("Android Startup Test onCreate: ")return this.javaClass.simpleName}/*** 返回 String 类型的 list 集合。用来表示当前组件在执行之前需要依赖的组件*/override fun dependenciesByName(): List<String>? {SamplesLogger.i("Android Startup Test dependenciesByName: ")return super.dependenciesByName()}}
- 注意:
dependenciesByName()
方法会被回调多次。
5.3.3 自动初始化
// AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="xxx"><application...><providerandroid:name="com.rousetime.android_startup.provider.StartupProvider"android:authorities="${applicationId}.android_startup"android:exported="false"><meta-dataandroid:name="xxx.TestInitializer"android:value="android.startup" /></provider></application></manifest>
5.3.4 手动初始化
StartupManager.Builder().addStartup(TestInitializer()).build(this).start().await()
5.3.5 更多请参考
- 《Android Startup 中文文档》
6、上述四种方案对比
- 基于上述四种方案,简单写了一个
Demo
示例,以 10 为基数,然后分别冷启动 5 次,得出下述数据,仅供参考。 - 查看启动的时间,其对应的
adb
命令:
adb shell am start -W -n 包名/类名
6.1 ContentProvider 方案进行初始化时间对照表
类型 | TotalTime | WaitTime | MethodTime |
---|---|---|---|
单次 | 386 | 388 | 22 |
单次 | 405 | 408 | 24 |
单次 | 388 | 390 | 23 |
单次 | 388 | 390 | 24 |
单次 | 397 | 400 | 25 |
平均 | 392.8 | 395.2 | 23.6 |
6.2 Initializer 方案进行初始化时间对照表
类型 | TotalTime | WaitTime | MethodTime |
---|---|---|---|
单次 | 444 | 446 | 25 |
单次 | 452 | 454 | 25 |
单次 | 455 | 457 | 26 |
单次 | 433 | 435 | 25 |
单次 | 425 | 428 | 23 |
平均 | 441.8 | 444 | 24.8 |
6.3 Jetpack App Startup 方案进行初始化时间对照表
类型 | TotalTime | WaitTime | MethodTime |
---|---|---|---|
单次 | 395 | 397 | 22 |
单次 | 385 | 387 | 23 |
单次 | 389 | 391 | 21 |
单次 | 394 | 396 | 23 |
单次 | 374 | 377 | 20 |
平均 | 387.4 | 389.6 | 21.8 |
6.4 Andorid Startup 方案进行初始化时间对照表
类型 | TotalTime | WaitTime | MethodTime |
---|---|---|---|
单次 | 449 | 451 | 27 |
单次 | 431 | 433 | 27 |
单次 | 430 | 431 | 28 |
单次 | 432 | 434 | 27 |
单次 | 435 | 437 | 27 |
平均 | 435.4 | 437.2 | 27.2 |
6.5 对比总结
- 通过对比上述四种方案,其实都是有其存在的价值,具体取决于当前你的使用场景,然后根据场景选取不同的实现方案。
7、参考资料
- 《Android | 使用 ContentProvider 无侵入获取 Context》 – 彭旭锐 著
- 《组件化:代码隔离也难不倒组件的按序初始化》 —— leobert-lan 著
- 《合并多个清单文件》 —— Android Developers
- 《App Startup》 —— Android Developers
- 《探究 | App Startup真的能减少启动耗时吗? 》 – jimuzz 著
- 《Android | App Startup 可能比你想象中要简单》 – 彭旭锐 著
- 《从源码看 Jetpack(5)Startup 源码详解》 —— 叶志陈 著
- 《我为何弃用 Jetpack 的 App Startup?》 —— 午后一小憩 著
- 《Android Startup》 —— Android Startup GitHub
- 《Android Startup 中文文档》 – Android Startup GitHub
- 《更快!这才是我想要的 Android Startup 库!》 —— idisfkj 著
Android App Initializer 实现方案相关推荐
- android app测试方案,Android App响应时间测试方案优化
前言 在我之前写的一篇文章Android App响应时间测试方法研究提到一种比较通用的测试启动时间或者响应时间的方案,即利用Monkeyrunner的图片对比接口进行截图比较.但是由于MonkeyRu ...
- Android APP 热修复方案
1.百川Hotfix 不仅仅只基于AndFix,而是灵活切换热部署和冷部署的方案:实现了资源.SO文件.类修复的实时生效,同时采用了傻瓜式接入方案,完全不侵入打包过程,对用户提供了可视化的UI界面打补 ...
- 【爱加密】Android App应用安全加固详细步骤
原文地址:点击打开链接 随着各种牌子手机的不断推出,各种Android手机应用,即Android App也是满天飞.但是,在这个山寨.黑客遍布的世界,如何保证Android App的安全是广大Andr ...
- Android App 线上热修复方案
热修复一词恐怕最早应用在微软.为了巩固其windows系统和office的市场占有率,微软开发并维护了一套线上修复方案,用于修复漏洞及特定问题(LDR),避免延续到发版解决(GDR),详见HotFix ...
- Android APP全局黑白化实现方案
作者:小帅 链接:https://zhuanlan.zhihu.com/p/587516253 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 在清明节时各大APP都 ...
- android 动态库获取路径问题,一种Android App在Native层动态加载so库的方案
这篇文章通过实战案例,介绍了一种有条理的组织Native层代码层级结构的方法.并且,在良好的代码层级.作用分工的基础上,实现了动态的按需加载.卸载so库.文章的最后,还介绍了实践过程中遇到的困难以及对 ...
- APP(ios、Android)实现充值的方案
APP(ios.Android)实现充值的方案 Android 安卓手机对于付费审核没有规则,可以做微信或支付宝的三方接口调用.也可以直接做银行卡充值. ios ios对于应用的充值功能有明确的限制, ...
- android app升级方案,App版本升级方案
方案一:后端处理逻辑 - 通过指定数字版本号判断 app/ipad升级 入参数: appid app应用类型:1.A商城android app.2.A商城android ipad.3.A商城 ios ...
- Android App 线上热修复方案Xposed
热修复一词恐怕最早应用在微软.为了巩固其windows系统和office的市场占有率,微软开发并维护了一套线上修复方案,用于修复漏洞及特定问题(LDR),避免延续到发版解决(GDR),详见HotFix ...
最新文章
- 【CV】深度学习中Epoch, Batch, Iteration的含义
- Excel中 提取字符串中的字符或数字
- CSS自定义消息提示
- 智能助手被指侵犯用户隐私,美三大科技巨头或面临调查;传小米已解散其VR头显Mi VR团队;特斯拉成立反欺诈部门……...
- HashMap的结构及源码分析
- 出门问问CEO李志飞:当语音成为基石技术,消费场景如何进一步落地?
- 获得N^2个往返接力数字表格的算法
- python常用_30个Python常用小技巧
- Eclipse JSP 页面设置 charset=UTF-8
- 软考计算机硬件知识,软考程序员考点计算机硬件基础知识之存储器简介
- java导出excel水印_java实现导出带有水印的excel
- 基于springboot的员工管理系统整合Mybatis操作
- 二字动词 复盘赋能_【格格读书成长营】用复盘来为自己赋能
- 办公网建设设备选型及报价参考(500人规模)
- scrapy 用爬虫规则指定爬行轨迹自动抓取
- 【深度学习Deep Learning】资料大全
- 网络安全该从何入手?
- surface pro java_全新SurfacePro评测 可以理解为SurfacePro4的进化版
- 本地时间和GMT(UTC)时间的转换
- NLP系列(3)_用朴素贝叶斯进行文本分类(下)
热门文章
- 【弘成基】运用资料整理
- android手机root status,相应黑科技梳理
- 魔蝎座提供伤感QQ日志_一转身可能就是一世
- 【java毕业设计】基于java+GUL的超级玛丽游戏GUL设计与实现(毕业论文+程序源码)——超级玛丽游戏
- Oracle怎么获取json类型字符串值,sql解析json格式字段 如何获取json中某个字段的值?...
- jt808、obd采集源码
- 攻城略地mysql充值_攻城掠地充值有巨惠!平台折扣5.0超划算
- 无线电能传输 wpt 磁耦合谐振 过零检测 matlab simulink仿真 pwm MOSFET,过零检测模块 基于二极管整流的无线电能传输设计
- 软件框架-无绪开发4
- 百度云直播的操作流程