Jetpack ---- App Startup源码解析
1. 解决的问题
一般需要初始化的sdk都会对外提供一个初始化方法供外界调用,如:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Sdk1.init(this);
}
}
对调用者很不友好。另一种做法是使用ContentProvider初始化,如下:
public class Sdk1InitializeProvider extends ContentProvider {
@Override
public boolean onCreate() {
Sdk1.init(getContext());
return true;
}
...
}
然后在AndroidManifest.xml文件中注册这个privoder,如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.zhengzhengxiaogege.sdk1">
<application>
<provider
android:authorities="${applicationId}.init-provider"
android:name=".Sdk1InitializeProvider"
android:exported="false"/>
</application>
</manifest>
authorities在这里并没有固定的要求,填写什么值都是可以的,但必须保证这个值在整个手机上是唯一的,所以通常会使用 ${applicationId} 作为前缀,以防止和其他应用程序冲突。
这样初始化的逻辑就由Sdk开发者在内部完成了。
那么,自定义的这个Sdk1InitializeProvider 它会在什么时候执行呢?我们来看一下这张流程图:
可以看到,一个应用程序的执行顺序是这个样子的。首先调用Application的attachBaseContext()方法,然后调用ContentProvider的onCreate()方法,接下来调用Application的onCreate()方法
。
有没有觉得ContentProvider的onCreate()方法中进行初始化这种设计方式很巧妙?它可以将库的用法进一步简化,不需要你主动去调用初始化接口,而是将这个工作在背后悄悄自动完成了。
那么有哪些库使用了这种设计方式呢?这个真的有很多了,比如说Facebook的库,Firebase的库,还有我们所熟知的WorkManager,Lifecycles等等。这些库都没有提供一个像LitePal那样的初始化接口,其实就是使用了上述的技巧。
看上去如此巧妙的技术方案,那么它有没有什么缺点呢?
有,缺点就是,ContentProvider会增加许多额外的耗时。
毕竟ContentProvider是Android四大组件之一,这个组件相对来说是比较重量级的。也就是说,本来我的初始化操作可能是一个非常轻量级的操作,依赖于ContentProvider之后就变成了一个重量级的操作了。
关于ContentProvider的耗时,Google官方也有给出一个测试结果:
这是在一台搭载Android 10系统的Pixel2手机上测试的情况。可以看到,一个空的ContentProvider大约会占用2ms的耗时,随着ContentProvider的增加,耗时也会跟着一起增加。如果你的应用程序中使用了50个ContentProvider,那么将会占用接近20ms的耗时。
注意这还只是空ContentProvider的耗时,并没有算上你在ContentProvider中执行逻辑的耗时。
这个测试结果告诉我们,虽然刚才所介绍的使用ContentProvider来进行初始化的设计方式很巧妙,但是如果每个第三方库都自己创建了一个ContentProvider,那么最终我们App的启动速度就会受到比较大的影响。
但是,如果一个app依赖了很多需要初始化的sdk,如果都放在一个ContentProvider中会导致此ContentProvider代码数量增加。而且每增加一个需要初始化的sdk都要对该ContentProvider文件做改动,不方便合作开发。而如果每个sdk都采用同样的方式将会带来性能问题。App Startup library可以有效解决这个问题。
2. 使用App StartUp
(1) 添加依赖
在App模块的build.gradle文件中添加依赖:
dependencies {
implementation "androidx.startup:startup-runtime:1.0.0"
}
(2) 实现Initializer< T >接口
app通过Initializer< T >接口接入App Startup,需要实现两个方法
public interface Initializer<T> {
@NonNull
T create(@NonNull Context context);
@NonNull
List<Class<? extends Initializer<?>>> dependencies();
}
例如有一个Sdk1如下:
public class Sdk1 {
private static final String TAG = "Sdk1";
private static Context sApplicationContext;
private static volatile Sdk1 sInstance;
public static void init(Context applicationContext){
sApplicationContext = applicationContext;
Log.e(TAG, "Sdk1 is initialized");
}
public static Sdk1 getInstance(){
if (sInstance == null) {
synchronized (Sdk1.class){
if (sInstance == null) {
sInstance = new Sdk1();
}
}
}
return sInstance;
}
private Sdk1(){
}
public void printApplicationName(){
Log.e(TAG, sApplicationContext.getPackageName());
}
}
sdk1对外提供Sdk1类,包含初始化方法init(Context),实例获取方法getInstance()和对外的服务方法printApplicationName()。为了使用App Startup,需要提供一个初始化器如下:
public class