App Startup
App Startup是用来在应用进程启动的时候初始化其他组件的。
一般我们初始化的操作都放在Application的onCreate方法中或者ContentProvider的onCreate方法中,因为ContentProvider的onCreate方法会在进程创建的时候调用。
但是ContentProvider的创建成本比较高,并且它的onCreate方法会延长应用的启动时间,所以如果应用中或三方库中定义了多个ContentProvider,势必会对应用的性能产生影响。
App Startup 就是为了解决这个问题,它可以是应用的所有初始化操作(包括三方库和应用自定义内容)共用同一ContentProvider,并且可以控制初始化的顺序。
首先,需要在gradle中添加依赖
dependencies {
implementation "androidx.startup:startup-runtime:1.0.0"
}
其次,实现接口Initializer<T>,并提供它的两个方法的实现。
Initializer的源码如下:
/**
* {@link Initializer}s can be used to initialize libraries during app startup, without
* the need to use additional {@link android.content.ContentProvider}s.
*
* @param <T> The instance type being initialized
*/
public interface Initializer<T> {
/**
* Initializes and a component given the application {@link Context}
*
* @param context The application context.
*/
@NonNull
T create(@NonNull Context context);
/**
* @return A list of dependencies that this {@link Initializer} depends on. This is
* used to determine initialization order of {@link Initializer}s.
* <br/>
* For e.g. if a {@link Initializer} `B` defines another
* {@link Initializer} `A` as its dependency, then `A` gets initialized before `B`.
*/
@NonNull
List<Class<? extends Initializer<?>>> dependencies();
}
在create方法中需要初始化并返回组件的实例。
dependencies方法中指定了初始化当前组件需要依赖哪些其他的组件,这个方法决定了组件初始化的顺序。比如B组件依赖于A组件,那A在B之前初始化。
可以看一下WorkManagerInitializer的源码
/**
* Initializes {@link androidx.work.WorkManager} using {@code androidx.startup}.
*/
public final class WorkManagerInitializer implements Initializer<WorkManager> {
private static final String TAG = Logger.tagWithPrefix("WrkMgrInitializer");
@NonNull
@Override
public WorkManager create(@NonNull Context context) {
// Initialize WorkManager with the default configuration.
Logger.get().debug(TAG, "Initializing WorkManager with default configuration.");
WorkManager.initialize(context, new Configuration.Builder().build());
return WorkManager.getInstance(context);
}
@NonNull
@Override
public List<Class<? extends androidx.startup.Initializer<?>>> dependencies() {
return Collections.emptyList();
}
}
假设,我们的应用同样依赖一个叫做ExampleLogger的三方库,并且ExampleLogger依赖于WorkManager,那我们可以定义一个ExampleLoggerInitializer
// Initializes ExampleLogger.
class ExampleLoggerInitializer implements Initializer<ExampleLogger> {
@Override
public ExampleLogger create(Context context) {
// WorkManager.getInstance() is non-null only after
// WorkManager is initialized.
return ExampleLogger(WorkManager.getInstance(context));
}
@Override
public List<Class<Initializer<?>>> dependencies() {
// Defines a dependency on WorkManagerInitializer so it can be
// initialized after WorkManager is initialized.
return Arrays.asList(WorkManagerInitializer.class);
}
}
这样就能确保 WorkManager在 ExampleLogger之前初始化。
最后,需要在manifest中进行声明
App Startup中包含一个InitializationProvider,它是ContentProvider的子类,用来发现和调用我们定义的initializers。App Startup会首先检查manifest中定义的<meta-data>,然后初始化已经发现的initializers的依赖组件。
比如,我们上边定义的WorkManagerInitializer和ExampleLoggerInitializer,我们只需要在<meta-data>中定义ExampleLoggerInitializer,因为ExampleLoggerInitializer依赖于WorkManagerInitializer,所以App Startup可以找到WorkManagerInitializer并先进行初始化。
<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>
tools:node="merge"可以确保merge工具可以正确的处理冲突。
我们也可以手动的初始化组件。
首先,要先关闭自动初始化。
对单个组件,删除对应的<meta-data>即可。
<meta-data android:name="com.example.ExampleLoggerInitializer"
tools:node="remove" />
对全部的组件,可以删掉provider的声明。
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
然后,在需要使用组件的地方手动调用初始化方法。
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer.class);
从源码中分析原理
public class InitializationProvider extends ContentProvider {
@Override
public final boolean onCreate() {
Context context = getContext();
if (context != null) {
AppInitializer.getInstance(context).discoverAndInitialize();
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
}
我们可以看到InitializationProvider继承了ContentProvider,并在其onCreate方法中调用了AppInitializer. discoverAndInitialize方法
@SuppressWarnings("unchecked")
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();
}
}
@NonNull
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
<T> T doInitialize(
@NonNull Class<? extends Initializer<?>> component,
@NonNull Set<Class<?>> initializing) {
synchronized (sLock) {
boolean isTracingEnabled = Trace.isEnabled();
try {
if (isTracingEnabled) {
// Use the simpleName here because section names would get too big otherwise.
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();
}
}
}
代码比较简单明了,大家可以自行查看。
缺点
1、App Startup对于直接在Application.onCreate方法中进行初始化的方法优势不是很明显。
2、对于每一个需要初始化的组件都要定义一个Initializer,是不是太繁琐了?