Jetpack Startup源码分析
文章目录
Jetpack给我们提供一种组件,可以让应用启动时的初始化操作放在一起,在我们启动应用的时候进行初始化。
Startup的用法很简单,就需要我们实现Initializer
接口,并在AndroidManifest清单文件进行<provider>
的注册并给该provider添加相关的类的meta-data即可使用。
简单的用法
清单文件对androidx.startup.InitializationProvider
进行注册
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.android.jetpackstartup.ExampleInitializer"
android:value="androidx.startup" />
</provider>
创建我们的Initializer并实现Initializer接口
class ExampleInitializer : Initializer<Unit> {
override fun create(context: Context) {
ToastUtils.init(context)
return
}
//依赖项
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return ArrayList()
}
}
运行:
![image-20210608004145440](https://i-blog.csdnimg.cn/blog_migrate/a1aacffcf4a2bee1ba26ee600c92f859.png)
源码分析
通过下图可以看到Startup所涉及的类不多,我们通过源码看看实现方式。
![image-20210607233231654](https://i-blog.csdnimg.cn/blog_migrate/1da99818abbacf1cf04a22b38e6ce3f5.png)
InitializationProvider
我们首先看InitializationProvider
:
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class InitializationProvider extends ContentProvider {
@Override
public boolean onCreate() {
Context context = getContext();//获取上下文
if (context != null) {
AppInitializer.getInstance(context).discoverAndInitialize();//查找Initializer并进行初始化
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
@Nullable
@Override
public Cursor query(
@NonNull Uri uri,
@Nullable String[] projection,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
throw new IllegalStateException("Not allowed.");
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int delete(
@NonNull Uri uri,
@Nullable String selection,
@Nullable String[] selectionArgs) {
throw new IllegalStateException("Not allowed.");
}
@Override
public int update(
@NonNull Uri uri,
@Nullable ContentValues values,
@Nullable String selection,
@Nullable String[] selectionArgs) {
throw new IllegalStateException("Not allowed.");
}
}
InitializationProvider是一个ContentProvider,如果有用过LeakCanary2并且看过该库的源码或对LeakCanary2
的自动初始化有过了解的话就会知道LeakCanary2
其实也是用ContentProvider来实现初始化,即不用开发人员手动进行初始化。我在制作一个开源库(还没完全完成)的时候也是使用了这个方式在debug时进行无侵入式初始化。
ContentProvider初始化时机
ContentProvider的onCreate方法是在Application的attachBaseContext
之后在onCreate
之前。
我们可以通过自定义Applciation
与ContentProvider
来验证这一点
class MApplication : Application() {
companion object {
private const val TAG = "MApplication"
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
Log.d(TAG, "attachBaseContext: ")
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate: ")
}
}
class MContentProvider : ContentProvider() {
companion object {
private const val TAG = "MContentProvider"
}
override fun onCreate(): Boolean {
Log.d(TAG, "onCreate: ")
return true
}
....
}
<application
...
...
>
<provider
android:name=".MContentProvider"
android:authorities="${applicationId}.mProvider"
android:exported="false" />
</application>
![image-20210607234509950](https://i-blog.csdnimg.cn/blog_migrate/ec626f83e8e3f26e14a1bfbe4dcba621.png)
前面补了一下ContentProvider的初始化时机与一些开源库除了用于内容提供还用来做别的用途的案例举例。
回头看InitializationProvider
,该ContentProvider的最主要的也是唯一一个有用的方法是onCreate
方法。
@Override
public boolean onCreate() {
Context context = getContext();//获取上下文
if (context != null) {
AppInitializer.getInstance(context).discoverAndInitialize();//查找Initializer并进行初始化
} else {
throw new StartupException("Context cannot be null");
}
return true;
}
在该方法中获取了AppInitializer
单例对象并调用了discoverAndInitialize
方法。
AppInitializer
首先看一下AppInitializer
的单例是如何实现的。
@NonNull
final Map<Class<?>, Object> mInitialized;//已初始化缓存
@NonNull
final Set<Class<? extends Initializer<?>>> mDiscovered;//已发现缓存
AppInitializer(@NonNull Context context) {
mContext = context.getApplicationContext();
mDiscovered = new HashSet<>();
mInitialized = new HashMap<>();
}
private static volatile AppInitializer sInstance;
public static AppInitializer getInstance(@NonNull Context context) {
if (sInstance == null) {
synchronized (sLock) {
if (sInstance == null) {
sInstance = new AppInitializer(context);
}
}
}
return sInstance;
}
通过上述代码可以知道这是一个很标准的双检锁(DCL)单例模式。
AppInitializer#discoverAndInitialize
我们继续看discoverAndInitialize
方法:
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);//通过PackageManager获取ProviderInfo信息 Bundle metadata = providerInfo.metaData;//获取metaData String startup = mContext.getString(R.string.androidx_startup);// if (metadata != null) {//如果metadata不为空 Set<Class<?>> initializing = new HashSet<>();//初始化内容缓存HashMap Set<String> keys = metadata.keySet();//获取键集合,这里的键就是我们定义的name即我们的类名 for (String key : keys) {//遍历 String value = metadata.getString(key, null);//获取value if (startup.equals(value)) {//如果value为androidx_startup Class<?> clazz = Class.forName(key);//获取Class if (Initializer.class.isAssignableFrom(clazz)) {//该类实现了Initializer接口 Class<? extends Initializer<?>> component = (Class<? extends Initializer<?>>) clazz;//Class类型转换 mDiscovered.add(component);// if (StartupLogger.DEBUG) {//如果是Debug打印日志 StartupLogger.i(String.format("Discovered %s", key)); } doInitialize(component, initializing);//调用初始化方法 } } } } } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) { throw new StartupException(exception); } finally { Trace.endSection();//性能检测工具结束 }}
在discoverAndInitialize
方法中通过组件名与PackageManager获取ProviderInfo,并从ProviderInfo获取metaData并获取该metaData获取键集合,该集合为我们定义的name即我们的类名。在该meta-data的value为androidx_startup时候调用反射获取该类并判断是否实现了Initializer接口,如果实现了将该类放进已发现HashMap中进行缓存。最终调用doInitialize(component, initializing);
方法进行初始化。
AppInitialize#doInitialize
@NonNull final Map<Class<?>, Object> mInitialized; @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)) {//如果initializing中已经有相同的类 String message = String.format( "Cannot initialize %s. Cycle detected.", component.getName() ); throw new IllegalStateException(message); } Object result; if (!mInitialized.containsKey(component)) {//已初始化缓存中没有此类 initializing.add(component);//将其加入到初始化中HashMap中 try { Object instance = component.getDeclaredConstructor().newInstance();//通过反射获取构造函数并进行实例化 Initializer<?> initializer = (Initializer<?>) instance;//转换类型 List<Class<? extends Initializer<?>>> dependencies = initializer.dependencies();//获取该Initializer的依赖,主要用于初始化排序,比如A、B、C,其中A依赖与B所以B需要在A之前进行初始化。 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);//调用initializer的create方法,在此时调用了我们的create方法我们在此方法中的初始化逻辑进行调用 if (StartupLogger.DEBUG) { StartupLogger.i(String.format("Initialized %s", component.getName())); } initializing.remove(component);//从初始化中HashMap移除该内容 mInitialized.put(component, result);//加入到已初始化缓存中 } catch (Throwable throwable) { throw new StartupException(throwable); } } else {//如果已经初始化设置返回对象 result = mInitialized.get(component); } return (T) result;//返回对象 } finally { Trace.endSection(); } } }
通过doInitialize
方法我们可以知道此方法是一个递归方法,在我们编写的Initializer没有被初始化即不在mInitialized这个HashMap中的时候,进行初始化相关的逻辑,在Initializer的依赖项不为空的时候通过递归优先进行依赖项的初始化,之后进行该Initiallizer的create调用,在初始化完毕后将初始化过的对象加入到已初始化缓存中。
总结
StartUp的源码内容很少(嗯,非常非常少),实现方式也很简单,但是它却很精妙,Startup通过自己实现ContentProvider通过ContentProvider在Application的attachBaseContext之后调用onCreate的特性,这么做可以实现对初始化方法的自动调用,也同时可以优化应用启动时间,通过阅读Startup等源码可以知道设计模式的巧妙用法、面向对象的三大特性的合理使用等技巧,通过阅读与分析这些优秀的项目、组件源码可以给自己提供很不错的思路与技巧,阅读源码对自己的提升帮助还是很大的。