介绍
我们在之前的文章《LeakCanary2源码分析》中聊到,LeakCanary2区别于旧版LeadkCanary,无需调用者显式进行初始化,是因为LeakCanary2利用了ContentProvider自动初始化的特性。
而ContentProvider的这种自动初始化的特性,是在应用的启动过程中赋予的,相关代码在ActivityThread.java
中
private void handleMakeApplication(AppBindData data, ContextImpl appContext) {
//1. 通过反射创建Application, 并调用 Application.attach()
app = data.info.makeApplication(data.restrictedBackupMode, null);
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
2. 初始化ContentProvider
installContentProviders(app, data.providers);
}
}
try {
//3. 调用Application.onCreate()方法
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
....
}
}
结合注释,从上面代码我们可以看到,应用程序的创建顺序是:
也就是说,在Application.onCreate
之前,ContentProvider
已经进行了初始化。利用这个属性,就可以实现类似于LeakCanary2
一样的隐式初始化,但是这种方案也有缺点,那就是使用ContentProvider
会有额外的耗时,而且无法定义启动顺序。
从谷歌官方数据来看,一个空的ContentProvider
大约会占用2ms的耗时,随着ContentProvider
的增加,耗时也会跟着一起增加。如果使用了50个ContentProvider
,那么将会占用接近20ms的耗时。对App启动的影响还是比较明显的,针对这种情况,谷歌官方推出了App Startup
,App Startup
允许共享ContentProvider
,将所有用于初始化的ContentProvider
合并成一个,并显示定义其依赖关系,从而使App的启动速度变得更快。
基本使用
加入依赖
dependencies {
implementation "androidx.startup:startup-runtime:1.0.0-alpha03"
}
实现Initializer组件
始化组件必现实现 Initializer
接口,这个接口定义了两个方法:
public interface Initializer<T> {
@NonNull
T create(@NonNull Context context);
@NonNull
List<Class<? extends Initializer<?>>> dependencies();
}
create()
:这个方法会包含组件初始话的所有的操作,最终会返回一个实例 T
;
dependencies()
:这个方法返回一组实现了 Initializer
的类,这些都是当前组件初始化需要依赖的其他组件。可以使用此方法来控制应用程序在启动时运行初始化程序的顺序
大多数情况下我们不需要依赖其他组件,可以直接返回emptyList()
,如下所示:
class BaseInitializer : Initializer<Unit> {
private val TAG = "BaseInitializer"
override fun create(context: Context) {
Log.d(TAG, "create")
//在这里进行初始化工作
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
瞬间有种大费周章写Hello World的感觉~ 接下去需要把BaseInitializer
注册到AndroidManifest.xml
即可:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.saberhao.jetpacktest_kt.AppStartUp.BaseInitializer"
tools:node="androidx.startup" />
</provider>
上述配置,只有meta-data
中的android:name
部分我们需要指定成我们自定义的Initializer
的全路径类名,其他部分都是不能修改的。
懒加载
App Startup也提供了懒加载的方式,只需要将meta-data
中tools:value="androidx.startup"
修改为
tools:node="remove"
,同时在需要初始化的地方手动初始化:
AppInitializer.getInstance(this).initializeComponent(BaseInitializer::class.java)
依赖
上面我们说过,App Startup可以指定顺序执行,这个功能依赖于方法dependencies()
, 如果我们现在新增加LogInitializer
, 让LogInitializer
依赖于BaseInitializer
,在进行LogInitializer
初始化前会进行BaseInitializer
的初始化工作:
class LogInitializer : Initializer<Unit> {
val TAG = "LogInitializer"
override fun create(context: Context) {
Log.d(TAG, "create")
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return arrayListOf(BaseInitializer::class.java)
}
}
这个时候,我们可以不用在AndroidManifest.xml中注册BaseInitializer
,而只需注册LogInitializer
,便可以正常执行:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.saberhao.jetpacktest_kt.AppStartUp.LogInitializer"
tools:value="androidx.startup" />
</provider>
需要注意的是,禁用组件的自动初始化,也会禁用该组件的依赖项的自动初始化。
源码分析
App Startup
的开始就是 InitializationProvider
的启动,InitializationProvider
继承自ContentProvider
public final class InitializationProvider extends ContentProvider {
@Override
public boolean onCreate() {
Context context = getContext();
if (context != null) {
//寻找并初始化各个Initializer
AppInitializer.getInstance(context).discoverAndInitialize();
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
....
}
我们看看 discoverAndInitialize
的具体实现
void discoverAndInitialize() {
try {
Trace.beginSection(SECTION_NAME);
// 1.获取InitializationProvider在manifest中配置的meta-data
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();
// 2. 遍历meta-data,找到所有metadata为"androidx.startup"的元素
for (String key : keys) {
String value = metadata.getString(key, null);
if (startup.equals(value)) {
Class<?> clazz = Class.forName(key);
if (Initializer.class.isAssignableFrom(clazz)) {
//3. 根据metadata找到所有Initializer组件
Class<? extends Initializer<?>> component =
(Class<? extends Initializer<?>>) clazz;
//4. 对Initializer组件进行初始化
doInitialize(component, initializing);
}
}
}
}
} catch (...) {
throw new StartupException(exception);
} finally {
Trace.endSection();
}
}
discoverAndInitialize
通过解析 meta-data
,获得Initializer
,并对其进行初始化。
接下去,我们来看看具体对Initializer
的初始化流程:
<T> T doInitialize(...) {
synchronized (sLock) { //加锁保证操作同步
boolean isTracingEnabled = Trace.isEnabled();
try {
...
//1. 保证initializer不重复执行
Object result;
if (!mInitialized.containsKey(component)) {
initializing.add(component);
try {
//2. 通过反射创建 initializer
Object instance = component.getDeclaredConstructor().newInstance();
Initializer<?> initializer = (Initializer<?>) instance;
//3. 获取 initializer 的依赖
List<Class<? extends Initializer<?>>> dependencies =
initializer.dependencies();
// 4. 如果依赖不为空,递归初始化依赖
if (!dependencies.isEmpty()) {
for (Class<? extends Initializer<?>> clazz : dependencies) {
if (!mInitialized.containsKey(clazz)) {
doInitialize(clazz, initializing);
}
}
}
...
// 5. 调用 initializer.create 进行初始化
result = initializer.create(mContext);
if (StartupLogger.DEBUG) {
StartupLogger.i(String.format("Initialized %s", component.getName()));
}
// 6. 更新正在初始化和已经初始化映射表
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();
}
}
}
代码很简单,根据 meta-data
来获取自定义的initializer,通过反射的方式初始化initializer
,递归调用初始化initializer
的依赖,然后调用其 create
方法完成最终初始化。
App Startup
实现非常简单,应该是我们解析过的最简单的一篇了,希望对大家有帮助~