请点赞关注,你的支持对我意义重大。
学习路线图:
App Startup 是 Google 提供的 Android 轻量级初始化框架:
App Startup 利用了 ContentProvider 在应用启动的时候初始化的特性,提供了一个自定义 ContentProvider 来实现自动初始化。很多库都利用了 ContentProvider 的启动机制,来实现无侵入初始化,例如 LeakCanary 等
使用 AppStartup 还能够合并所有用于初始化的 ContentProvider ,减少创建 ContentProvider,并提供全局管理。
App Startup 示意图
详细的源码分析下文内容。
这一节,我们来总结 App Startup 的使用步骤。
在模块级 build.gradle
添加依赖:
模块级 build.gradle
implementation "androidx.startup:startup-runtime:1.0.0"
Initializer
接口是 App Startup 定义组件接口,用于指定组件的初始化逻辑和初始化顺序(也就是依赖关系),接口定义如下:
context
参数就是当前进程的 Application
对象;Initializer.java
public interface Initializer<T> { // 1、初始化操作,返回值将被缓存?? @NonNull T create(@NonNull Context context); // 2、依赖关系,返回值是一个依赖组件的列表 @NonNull List<Class<? extends Initializer<?>>> dependencies(); }
示例程序
// LeakCanary 2.9.1 internal class AppWatcherStartupInitializer : Initializer<AppWatcherStartupInitializer> { override fun create(context: Context) = apply { // 实现初始化操作 val application = context.applicationContext as Application AppWatcher.manualInstall(application) } override fun dependencies() = emptyList<Class<out Initializer<*>>>() }
在 Manifest 文件中将 Initializer 实现类配置到 androidx.startup.InitializationProvider
的 <meta-data>
中。
示例程序
<!-- LeakCanary 2.9.1 --> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data android:name="leakcanary.internal.AppWatcherStartupInitializer" android:value="androidx.startup"/> </provider>
要点如下:
androidx.startup.InitializationProvider
;android:exported="false"
,以限制其他应用访问此组件;android:authorities
要求在设备中全局唯一,通常使用 ${applicationId}
作为前缀;tools:node="merge"
,确保 manifest merger tool 能够正确解析冲突的节点;android:name
为组件的 Initializer 实现类的全限定类名,android:value
固定为 androidx.startup
。提示: 为什么要将
androidx.startup
设置为 value,而不是 name?因为在键值对中,name 是唯一的,而 value 是允许重复的,将androidx.startup
放到 value 的话才能允许同时配置多个相同语义的<meta-data>
。
至此,App Startup 基本的使用与配置完成,在应用启动时,App Startup 会自动收集各个模块配置的 Initializer
实现类,并按照依赖顺序依次执行。
当你的组件需要进行手动初始化,而不是自动初始化时(例如存在耗时任务),可以进行手动初始化,而且手动初始化是可以在子线程调用的,而自动初始化均是在主线程执行的。
initializeComponent()
也不会导致重复初始化;调用以下方即可进行手动初始化:
示例程序
AppInitializer.getInstance(context).initializeComponent(ExampleLoggerInitializer::class.java)
假如有些库已经配置了自动初始化,而我们又希望进行懒加载时,就需要利用 manifest merger tool
的合并规则来移除这个库对应的 Initializer。具体如下:
示例程序
<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data android:name="com.example.ExampleLoggerInitializer" tools:node="remove" /> </provider>
假如需要完全禁用 App Startup 自动初始化,同样也可以利用到 manifest merger tool
的合并规则:
示例程序
<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" tools:node="remove" />
App Startup 利用了 ContentProvider 的启动机制实现自动初始化。ContentProvider 通常的用法是为当前进程 / 远程进程提供内容服务,它们会在应用启动的时候初始化。利用这个特性,App Startup 的方案就是自定义一个 ContentProvider 的实现类 InitializationProvider
,在 onCreate(…) 方法中执行初始化逻辑。
InitializationProvider.java
已简化 public final class InitializationProvider extends ContentProvider { @Override public boolean onCreate() { Context context = getContext(); if (context != null) { // 初始化 AppInitializer.getInstance(context).discoverAndInitialize(); } else { throw new StartupException("Context cannot be null"); } return true; } @Override public Cursor query(...) { throw new IllegalStateException("Not allowed."); } @Override public String getType(...) { throw new IllegalStateException("Not allowed."); } @Nullable @Override public Uri insert(...) { throw new IllegalStateException("Not allowed."); } @Override public int delete(...) { throw new IllegalStateException("Not allowed."); } @Override public int update(...) { throw new IllegalStateException("Not allowed."); } }
由于 ContentProvider 的其他方法是没有意义的,所以都抛出了 IllegalStateException
。
从上一节可以看到,App Startup 在 InitializationProvider
中调用了AppInitializer#discoverAndInitialize()
执行自动初始化。AppInitializer
是 App StartUp 框架的核心类,整个 App Startup 框架的代码其实非常少,其中很大部分核心代码都在 AppInitializer 类中。
我将整个自动初始化过程概括为 3 个阶段:
源码摘要如下:
AppInitializer.java
private static final Object sLock = new Object(); // 后面会提到 // 记录扫描 <meta-data> 得到的初始化器(可用于判断组件是否已经自动启动) final Set<Class<? extends Initializer<?>>> mDiscovered; // 缓存每个组件的初始化结果 final Map<Class<?>, Object> mInitialized; void discoverAndInitialize() { // 1、获取 androidx.startup.InitializationProvider 组件信息 ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName()); ProviderInfo providerInfo = mContext.getPackageManager().getProviderInfo(provider, GET_META_DATA); // 2、androidx.startup 字符串 String startup = mContext.getString(R.string.androidx_startup); // 3、获取组件信息中的 meta-data 数据 Bundle metadata = providerInfo.metaData; // 4、遍历所有 meta-data 数据 if (metadata != null) { Set<Class<?>> initializing = new HashSet<>(); Set<String> keys = metadata.keySet(); for (String key : keys) { String value = metadata.getString(key, null); // 4.1 筛选 value 为 androidx.startup 的 meta-data 数据中 if (startup.equals(value)) { Class<?> clazz = Class.forName(key); // 4.2 检查指定的类是 Initializer 接口的实现类 if (Initializer.class.isAssignableFrom(clazz)) { Class<? extends Initializer<?>> component = (Class<? extends Initializer<?>>) clazz; // 4.3 将 Class 添加到 mDiscovered Set 中 mDiscovered.add(component); // 4.4 初始化此组件 doInitialize(component, initializing); } } } } } // -> 4.3 mDiscovered 用于判断组件是否已经自动启动 public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component) { return mDiscovered.contains(component); } // -> 4.4 初始化此组件(已简化) <T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) { // 1、对 sLock 加锁,我后文再说。 Object result; // 2、判断 initializing 中存在当前组件,说明存在循环依赖 if (initializing.contains(component)) { String message = String.format("Cannot initialize %s. Cycle detected.", component.getName()); throw new IllegalStateException(message); } // 3、检查当前组件是否已初始化 if (!mInitialized.containsKey(component)) { // 3.1 当前组件未初始化 // 3.1.1 记录正在初始化 initializing.add(component); // 3.1.2 通过反射实例化 Initializer 接口实现类 Object instance = component.getDeclaredConstructor().newInstance(); Initializer<?> initializer = (Initializer<?>) instance; // 3.1.3 遍历所依赖的组件(关键:优先处理依赖的组件) List<Class<? extends Initializer<?>>> dependencies = initializer.dependencies(); if (!dependencies.isEmpty()) { for (Class<? extends Initializer<?>> clazz : dependencies) { // 递归:如果所依赖的组件未初始化,执行初始化 if (!mInitialized.containsKey(clazz)) { // 注意:这里将 initializing 作为参数传入,用于判断循环依赖 doInitialize(clazz, initializing); } } } // 3.1.4 (到这里,所依赖的组件已经初始化完成)初始化当前组件 result = initializer.create(mContext); // 3.1.5 移除正在初始化记录 initializing.remove(component); // 3.1.6 缓存初始化结果 mInitialized.put(component, result); } else { // 3.2 当前组件已经初始化,直接返回 result = mInitialized.get(component); } return (T) result; }
前面我们提到使用 initializeComponent()
方法可以手动初始化,我们来看手动初始化(懒加载)的源码:
AppInitializer.java
public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) { // 调用 doInitialize(...) 方法: return doInitialize(component, new HashSet<Class<?>>()); }
其实非常简单,就是调用上一节的 doInitialize(...)
执行初始化。需要注意的是,这个方法是允许在子线程调用的,换句话说,自动初始化与手动初始化是存在线程同步问题的,那么 App Startup 是如何解决的呢?还记得我们前面有一个 sLock
没有说吗?其实它就是用来保证线程同步的锁:
AppInitializer.java
<T> T doInitialize(Class<? extends Initializer<?>> component, Set<Class<?>> initializing) { // 1、对 sLock 加锁 synchronized (sLock) { ... } }
到这里,App Startup 的内容就讲完了。可以看到 App Startup 只是一个轻量级的初始化框架,能做的事情有限。市面上有开发者开源了基于 DAU 有向无环图的初始化框架,这个我们下次再说。关注我,带你了解更多。