简介
App Startup 库提供了一种在应用程序启动时初始化组件的简单、高效的方法。库开发人员和应用程序开发人员都可以使用 App Startup 来简化启动顺序并明确设置初始化顺序。
App Startup 允许您定义共享单个内容提供者的组件初始值设定项,而不是为您需要初始化的每个组件定义单独的内容提供程序。这可以显着缩短应用程序启动时间。
使用方法
单个Initializer
通过下面的配置在项目中引入Startup库:
dependencies {
implementation("androidx.startup:startup-runtime:1.0.0")
}
要完成某些初始化工作,我们需要自定义一个Initializer类,比如如下代码:
package com.yb.testjetpack
import android.content.Context
import android.util.Log
import androidx.startup.Initializer
class MyInitializer : Initializer<Unit> {
companion object {
private const val TAG = "MyInitializer"
}
override fun create(context: Context): Unit {
Log.d(TAG, "create: do some init...")
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return arrayListOf()
}
}
然后在项目的AndroidManifest.xml文件中注册这个Initializer:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.yb.testjetpack.MyInitializer"
android:value="androidx.startup" />
</provider>
通过以上简单的3步即可将Startup库应用在项目中,我们运行项目时,MyInitializer 类中的onCreate 方法会自动得到调用。
多个Initializer
以上介绍的是项目中只有一个Initializer的情况,其实项目中也可以定义多个不同的Initializer。
在上面的代码中,我们可以发现Initializer 接口中有个dependencies 方法,这个方法的返回值是一个数组,代表当前Initializer的初始化依赖的其他Initializer,下面举个例子说明应用中存在多个不同Initializer时的处理方法:
假设当前应用中有三个不同的Initializer:
MyInitializer FirstInitializer SecondInitializer
这三个Initializer的初始化有一些特定的顺序:SecondInitializer 的初始化依赖FirstInitializer ,而FirstInitializer 的初始化依赖MyInitializer ,即MyInitializer 最先执行,其次是FirstInitializer 执行,最后是SecondInitializer ,那么这三个Initializer的写法如下:
class MyInitializer : Initializer<Unit> {
companion object {
private const val TAG = "MyInitializer"
}
override fun create(context: Context): Unit {
Log.d(TAG, "create: do some init...")
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return arrayListOf()
}
}
class FirstInitializer : Initializer<String> {
companion object {
private const val TAG = "FirstInitializer"
}
override fun create(context: Context): String {
Log.d(TAG, "create: init FirstInitializer...")
return "FirstInitializer"
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return arrayListOf(MyInitializer::class.java)
}
}
class SecondInitializer : Initializer<Unit> {
companion object {
private const val TAG = "SecondInitializer"
}
override fun create(context: Context) {
Log.d(TAG, "create: init SecondInitializer...")
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return arrayListOf(FirstInitializer::class.java)
}
}
然后在AndroidManifest.xml文件中注册Initializer。
你可以将这三个Initializer都注册,像这样:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.yb.testjetpack.MyInitializer"
android:value="androidx.startup" />
<meta-data
android:name="com.yb.testjetpack.FirstInitializer"
android:value="androidx.startup" />
<meta-data
android:name="com.yb.testjetpack.SecondInitializer"
android:value="androidx.startup" />
</provider>
也可以只注册最后一个被调用的Initializer
比如以上三个Initializer的调用顺序是:MyInitializer – FirstInitializer – SecondInitializer
那么在AndroidManifest.xml文件中,注册SecondInitializer 即可:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.yb.testjetpack.SecondInitializer"
android:value="androidx.startup" />
</provider>
Initializer懒加载
以上介绍的单一Initializer和多个Initializer在注册到AndroidManifest.xml文件中后,不需要在代码中显式调用,App启动时即可自动完成Initializer中onCreate 方法的执行,其原理会在后面详细说明。
某些时候我们可能并不需要模块在App启动时就立刻初始化,而是希望通过调用Java代码完成初始化。
如果你只想禁用单个Initializer的自动初始化,可以这么做:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.yb.testjetpack.MyInitializer"
tools:node="remove" />
</provider>
注意meta-data 中的tools:node="remove" 属性,这个配置并非简单的删除条目,而是确保合并工具也从所有其他合并的清单文件中删除条目。
如果你想禁用所有的Initializer的自动初始化,可以这么做:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
然后使用Java或Kotlin代码完成某个Initializer手动的初始化,比如下面的代码展示了在点击TextView后初始化MyInitializer:
findViewById<TextView>(R.id.text).setOnClickListener {
AppInitializer.getInstance(this).initializeComponent(MyInitializer::class.java)
}
Startup库原理解析
你是不是和我一样好奇为啥Startup库不需要显式的执行初始化代码即可自动帮我们完成create方法中代码的执行?
通过Startup库的源码可以找到答案:Startup库使用了ContentProvider,而ContentProvider在注册到AndroidManifest.xml文件中后,ContentProvider中的onCreate 方法会在Application的attachBaseContext 方法之后,onCreate 方法之前被调用。本篇不详细讨论ContentProvider的启动流程,仅仅解析Startup库的源码。
Startup库的源码非常少,仅有5个类,如下图所示: 其中Initializer是一个接口,其中定义了两个方法:create() dependencies() ,源码如下:
public interface Initializer<T> {
@NonNull
T create(@NonNull Context context);
@NonNull
List<Class<? extends Initializer<?>>> dependencies();
}
根据源码上的注释可以知道,T表示即将被初始化的实例类型。
Startup库之所以可以在App启动时自动被执行,主要的类还是InitializationProvider ,这个类继承了ContentProvider,其onCreate方法源码如下:
@Override
public boolean onCreate() {
Context context = getContext();
if (context != null) {
AppInitializer.getInstance(context).discoverAndInitialize();
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
这里的关键代码是AppInitializer.getInstance(context).discoverAndInitialize();
discoverAndInitialize() 方法会去AndroidManifest.xml文件中查找<meta-data> 标签,并过滤出值为androidx.startup 的标签,接着通过反射去实例化我们自定义的Initializer,代码如下:
void discoverAndInitialize() {
try {
Trace.beginSection(SECTION_NAME);
ComponentName provider = new ComponentName(mContext.getPackageName(),
InitializationProvider.class.getName());
ProviderInfo providerInfo = mContext.getPackageManager()
.getProviderInfo(provider, GET_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);
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);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Discovered %s", key));
}
doInitialize(component, initializing);
}
}
}
}
} catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
throw new StartupException(exception);
} finally {
Trace.endSection();
}
}
<T> T doInitialize(
@NonNull Class<? extends Initializer<?>> component,
@NonNull Set<Class<?>> initializing) {
synchronized (sLock) {
boolean isTracingEnabled = Trace.isEnabled();
try {
if (isTracingEnabled) {
Trace.beginSection(component.getSimpleName());
}
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);
}
}
}
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initializing %s", component.getName()));
}
result = initializer.create(mContext);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initialized %s", component.getName()));
}
initializing.remove(component);
mInitialized.put(component, result);
} catch (Throwable throwable) {
throw new StartupException(throwable);
}
} else {
result = mInitialized.get(component);
}
return (T) result;
} finally {
Trace.endSection();
}
}
}
写到这里你应该已经知道Startup库为甚么能自动执行初始化操作了吧,再想想我们使用LeakCanary库时,只是在build.gradle文件中添加了依赖:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
再不需要其他代码的调用,LeakCanary就能自动检测内存泄漏问题,这种隐藏式的初始化,其原理和Startup库是一样的,都使用了ContentProvider。
Startup库优点
使用Startup库在某些情况下可以加速我们App的启动过程,具体可以查看如下两篇文章:
|