背景
前一篇文章,我们讲ContentProvider
的启动流程[启动] ContentProvider启动流程,提到了ContentProvider
的初始化时机是在Application
的onCreate
方法之前,所以ContentProvider
的初始化会影响到应用的启动速度。
现在很多第三方库,甚至Google官方提供的库,为了降低使用成本,都会选择注册一个ContentProvider
用于初始化。这个ContentProvider
通常什么都不做,只是调用了这些第三方库的init方法。例如Firebase
、workManager
等。
ContentProvider
的启动,是通过反射创建对象,然后调用attachInfo
方法,进而调用到onCreate方法的。即使是一个空的ContentProvider
,也需要耗费几十毫秒的创建时间,这对启动来说往往是非常关键的。
另外,零散的ContentProvider
,不便于对初始化任务进行统一管理,比如管理依赖关系,优先级等。
于是,Google官方开始下手,在Jetpack中推出了Startup
,将初始化相关的ContentProvider
都整理合成到一个InitializationProvider
,在这一个Provider
中完成所有的初始化工作。这样不仅能减少ContentProvider
的数量,减少启动耗时,同时也能更好地 管理这些初始化任务,可以实现一些简单的依赖关系管理。
Startup使用
1. 引入依赖
首先在build.gradle
中加入依赖库。
implementation "androidx.startup:startup-runtime:1.1.1"
2. 构建组件及依赖关系
使用Startup
框架初始化的组件,需要继承 Initializer
接口。
class InitializerA : Initializer<A> {
// 完成组件初始化,返回初始化后的结果
override fun create(context: Context): A {
return A.init(context)
}
// 依赖的组件
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(InitializerB::class.java)
}
}
class InitializerB : Initializer<B> {
override fun create(context: Context): B {
return B.init(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return null
}
}
Initializer
接口有两个方法:
create
方法:在这里调用组件的初始化方法,并返回初始化后的结果dependencies
方法:当前组件所有依赖组件,依赖组件会优先与当前组件被初始化。
上面的例子中,B是A的依赖组件,所以先初始化B,后初始化A。最后依赖关系,需要形成一个DAG有向无环图。
3. 自动初始化
在AndroidManifest
文件中声明组件,即可实现App启动自动初始化。
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="leavesc.lifecyclecore.core.InitializerA"
android:value="androidx.startup" />
</provider>
有几点需要注意:
-
此处只需要写
InitializerA
即可,因为InitializerB
是A的依赖项,在初始化A之前,会自动初始化B。 -
必须是
merge
,因为Manifest
里面已经有声明过InitializationProvider
了(在androidx库里),此处声明的需要与之前声明的合并。 -
手动初始化
如果Initializer
不需要一启动就初始化,则无需在AndroidManifest
中声明,直接在用到的地方懒加载即可。
val result = AppInitializer.getInstance(this).initializeComponent(InitializerA::class.java)
有几点注意:
Startup
内部会缓存Initializer
初始化的结果,所以多次调用不会导致多次初始化。- 可以用这个方法,获取自动初始化的结果。
Startup源码
当App启动时,走到InstallContentProviders
时,会初始化InitializationProvider
,调用它的attachInfo
-> onCreate
。
public final class InitializationProvider extends ContentProvider {
@Override
public boolean onCreate() {
Context context = getContext();
if (context != null) {
// 主要逻辑,调用AppInitializer完成
AppInitializer.getInstance(context).discoverAndInitialize();
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
}
InitializationProvider
就是一个普通的ContentProvider
,在它的onCreate
方法里,直接调用AppInitializer
来完成后续的初始化操作。
下面我们着重看AppInitializer
的源码。
public static AppInitializer getInstance(@NonNull Context context) {
if (sInstance == null) {
synchronized (sLock) {
if (sInstance == null) {
sInstance = new AppInitializer(context);
}
}
}
return sInstance;
}
AppInitializer
是一个单例,典型的双检索模式。
void discoverAndInitialize() {
ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName());
ProviderInfo providerInfo = mContext.getPackageManager().getProviderInfo(provider, GET_META_DATA);
// 获取InitializationProvider下配置的meta-data参数对
Bundle metadata = providerInfo.metaData;
String startup = mContext.getString(R.string.androidx_startup);
if (metadata != null) {
Set<Class<?>> initializing = new HashSet<>();
Set<String> keys = metadata.keySet();
for (String key : keys) {
String value = metadata.getString(key, null);
// 如果value等于androidx.startup
if (startup.equals(value)) {
Class<?> clazz = Class.forName(key);
// 如果key实现了Initializer接口
if (Initializer.class.isAssignableFrom(clazz)) {
Class<? extends Initializer<?>> component = (Class<? extends Initializer<?>>) clazz;
// 加入mDiscovered set
mDiscovered.add(component);
// 初始化这个component
doInitialize(component, initializing);
}
}
}
}
}
discoverAndInitialize
方法的主要作用是:
- 找到
Manifest
文件中,InitializationProvider
下的所有meta-data
参数对。 - 如果
value
等于androidx.startup
,且key
的类实现了Initializer
接口,则加入mDiscovered
set,调用doInitialize
初始化这个component
。
<T> T doInitialize(
@NonNull Class<? extends Initializer<?>> component,
@NonNull Set<Class<?>> initializing) {
// sLock是一个静态对象
synchronized (sLock) {
if (initializing.contains(component)) {
// 如果组件之间的依赖关系,出现了环,则直接抛出异常
String message = String.format("Cannot initialize %s. Cycle detected.", component.getName());
throw new IllegalStateException(message);
}
Object result;
// 判断当前组件是否已被初始化
if (!mInitialized.containsKey(component)) {
// 加入initializing set
initializing.add(component);
try {
// 初始化component对象
Object instance = component.getDeclaredConstructor().newInstance();
Initializer<?> initializer = (Initializer<?>) instance;
List<Class<? extends Initializer<?>>> dependencies =initializer.dependencies();
if (!dependencies.isEmpty()) {
// 初始化所有依赖项
for (Class<? extends Initializer<?>> clazz : dependencies) {
if (!mInitialized.containsKey(clazz)) {
doInitialize(clazz, initializing);
}
}
}
// 初始化当前component
result = initializer.create(mContext);
// 从initializing set中移除component
initializing.remove(component);
// 放入mInitialized map中
mInitialized.put(component, result);
} catch (Throwable throwable) {
throw new StartupException(throwable);
}
} else {
result = mInitialized.get(component);
}
return (T) result;
}
}
这个方法的主要流程如下:
- 在初始化之前,先判断当前的依赖关系图中是否有环,如果有环,直接抛出异常
- 判断当前组件是否已初始化,如果已初始化,直接返回
result
,防止重复初始化 - 初始化组件对象,获取它的
dependencies
,先初始化dependencies
- 所有
dependencies
初始化完成后,调用create
方法初始化组件 - 将初始化结果放入
mInitialized
map
中
总结
Startup的缺点:
- 不能无痛接入,需要修改第三方库的源码,或者等待第三方库接入,才能生效。
- 组件的create方法,都是在主线程调用,还是会影响启动。
Startup的源码非常简单,核心源码还不到200行,但是这个思路还是蛮值得借鉴的。
后面我们借鉴这个思路,实现了更完整、更灵活、更无痛的ContentProvider管理框架。
我们的新框架优点:
- 无需修改第三方库源码,即可接入。
- 可以选择在主线程或异步线程初始化组件。
- 可以选择在不同的阶段初始化不同组件,如Application初始化完、首页加载完等。
如果好奇我们是怎么做的,麻烦帮忙点点赞吧~ 点赞多的话,下篇文章就更。