一个项目在开发过程中,常常都伴随着很多sdk的依赖。大部分的sdk在使用时都需要在应用启动时进行初始化才能正常工作,所以在集成sdk时通常需要做如下操作:
在Application的onCreate内调用对应sdk的初始化方法,目的是:
-
保证在sdk使用之前,sdk需要的一些准备工作已经完成; -
为sdk内部提供application context;
所以我们常常会看到类似的调用:
XxxSDK.init(application)
最近发现一种sdk自动初始化的方案,可以告别sdk集成手动初始化。
ContentProvide想必都不陌生吧
定义一个ContentProvider,然后在mainfest注册声明后,它就能在应用启动时被创建
通过分析应用的启动流程可以发现如下的执行顺序
Application attachBaseContext
|
|
V
ContentProvide attachInfo
|
|
V
ContentProvide onCreate
|
|
V
Application onCreate
通常初始化sdk都是在Application的onCreate完成的,那么我们在ContentProvide的attachInfo和onCreate中进行sdk的初始化是不是也能保证sdk的初始化时机?通过ContentProvide是不是也能获取到了Application Context,那么开始说到的sdk初始化的两个目的是不是也同样能达到?
现在实现思路便有了:
可以在sdk内注册应以一个ContentProvide,然后在它的onCreate内自行完成sdk的初始化任务,那么在app集成该sdk时候就不用在app层显式的编写sdk初始化逻辑便可以直接调用sdk的相关功能。
这里做一个思考?
一个APP的完成往往需要依赖很多的sdk?那么如果每个sdk都去创建一个ContentProvide,是不是会大大增加APP的启动时长,那岂不是有点得不偿失了。
如何避免每个sdk都创建一个ContentProvide,能不能都共用一个ContentProvide呢,答案时可以的,并且google官方已经提供了对应的开源库。
实现很简单,源码总共就五个文件,主要代码就几百行
常规流程,先看看怎么使用
1、添加版本依赖
dependencies {
implementation("androidx.startup:startup-runtime:1.1.1")
}
2、定义一个用于初始化的类, implements Initializer<T> ,这里就直接引用官方的例子吧
class WorkManagerInitializer : Initializer<WorkManager> {
override fun create(context: Context): WorkManager {
val configuration = Configuration.Builder().build()
WorkManager.initialize(context, configuration)
return WorkManager.getInstance(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
Initializer 接口需要实现两个方法
--create():完成对应的初始化操作
--dependencies(): 需要返回一个List,Initializer实现类的Class,
这是代表初始化该任务需要依赖的其他初始化项,添加返回值后,会将该初始化任务依赖的初始化项
放到它的前面,保证它所依赖的初始化项先进行初始化。
如下例子,定义初始化类ExampleLoggerInitializer,它需要先执行上面WorkManagerInitialize的初始化任务,则实现如下:
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
override fun create(context: Context): ExampleLogger {
return ExampleLogger(WorkManager.getInstance(context))
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(WorkManagerInitializer::class.java)
}
}
3、在mainfest注册对应的Contentprovide,并声明需要执行的初始化任务
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- This entry makes ExampleLoggerInitializer discoverable. -->
<meta-data android:name="com.example.ExampleLoggerInitializer"
android:value="androidx.startup" />
</provider>
好了,通过以上的几步就可以实现一个sdk的自动初始化,而无需再到application里面显式的手动调用初始化的方法。
说完了怎么使用的,再来简单的看看Startup内部时怎么将开始说的实现思路转变成代码的吧,总共也就几百行代码。
开头已经介绍了它的实现思路,利用Contentprovide来完成sdk内部的自动初始化,而上面的第三步在Mainfest内注册声明的InitializationProvider是不是就是对应的用来完成初始化的?那我们便从InitializationProvider来切入:
@Override
public final boolean onCreate() {
Context context = getContext();
if (context != null) {
Context applicationContext = context.getApplicationContext();
if (applicationContext != null) {
AppInitializer.getInstance(context).discoverAndInitialize();
} else {
StartupLogger.w("Deferring initialization because `applicationContext` is null.");
}
} else {
throw new StartupException("Context cannot be null");
}
return true;
}return true;
}
接着看AppInitializer ,discoverAndInitialize
void discoverAndInitialize() {
try {
ComponentName provider = new ComponentName(mContext.getPackageName(),
InitializationProvider.class.getName());
ProviderInfo providerInfo = mContext.getPackageManager()
.getProviderInfo(provider, GET_META_DATA);
Bundle metadata = providerInfo.metaData;
discoverAndInitialize(metadata);
} catch (PackageManager.NameNotFoundException exception) {
throw new StartupException(exception);
} finally {
}
}
接着看拿到metadata后调用的方法
void discoverAndInitialize(@Nullable Bundle metadata) {
String startup = mContext.getString(R.string.androidx_startup);
try {
if (metadata != null) {
Set<Class<?>> initializing = new HashSet<>();
Set<String> keys = metadata.keySet();
for (String key : keys) {
String value = metadata.getString(key, null);
if (startup.equals(value)) {
Class<?> clazz = Class.forName(key);
if (Initializer.class.isAssignableFrom(clazz)) {
Class<? extends Initializer<?>> component =
(Class<? extends Initializer<?>>) clazz;
mDiscovered.add(component);
}
}
}
for (Class<? extends Initializer<?>> component : mDiscovered) {
doInitialize(component, initializing);
}
}
} catch (ClassNotFoundException exception) {
throw new StartupException(exception);
}
}
接着看doInitialize
private <T> T doInitialize(
@NonNull Class<? extends Initializer<?>> component,
@NonNull Set<Class<?>> initializing) {
try {
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.add(component);
try {
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);
}
}
}
result = initializer.create(mContext);
initializing.remove(component);
mInitialized.put(component, result);
} catch (Throwable throwable) {
throw new StartupException(throwable);
}
} else {
result = mInitialized.get(component);
}
return (T) result;
} finally {
}
}
到这里,整个startup的使用和实现原理都介绍完了,赶快使用体验下吧,当然我们也完全可以自己写一个Startup了。
|