Jetpack App Startup

1,341 阅读4分钟

Google推出的App Startup,主要是用于应用程序启动时,统一去管理初始化的组件库。该库可以去设置组件的初始化顺序,并且可以把所有组件都指定到一个ContentProriver里,避免存多个组件,多个contentProvider,从而提升app的启动时间,还可以通过懒加载的方式,去指定组件的初始化时机。下面内容会逐一介绍,并对App Startup进行源码解析。

一:为何使用App Startup,解决了什么问题?

一些库一般会提供给外部初始化方法,如下代码:

class MApp :Application(){
    override fun onCreate() {
        super.onCreate()
        CrashReport.initCrashReport(getApplicationContext(), "注册时申请的APPID", false)
        LeakCanary.install(this)
        XXX.init(this)
        XXX.install(this)
    }
}

当使用的库一多就会出现大量类似上面的代码,不方便管理
第二种,由于ContentProvider的onCreate()方法是在Application的onCreate()之前,所以有一些开源的库的内部自己通过ContentProvider做了初始化,从而无需外面调用初始化代码。

public class XXXProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        XXX.init(getContext());
        return true;
    }
	...
}

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.jetpackappstartuptes">
    <application>
        <provider
            android:authorities="${applicationId}.init-provider"
            android:name=".XXXProvider"
            android:exported="false"/>
    </application>
</manifest>

如果每个第三方库内部都创建自己的ContentProvider去做初始化,而当我们使用到的这种开源库一多,那么就会存在创建多个ContentProvider的问题,从而影响App的启动时间。
解决这些问题,Google推出了App Startup

二:使用

1:添加依赖

dependencies {
    // 使用App Startup
    implementation "androidx.startup:startup-runtime:1.0.0"
}

2:实现Initializer接口

// Initializer需要实现两个接口
public interface Initializer<T> {
    @NonNull
    T create(@NonNull Context context);
    @NonNull
    List<Class<? extends Initializer<?>>> dependencies();
}
class BuglyInitializer :Initializer<Unit>{
    override fun create(context: Context) {
        CrashReport.initCrashReport(context, "注册时申请的APPID", false)
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

泛型T为待初始化的Sdk对外提供的对象类型;
create(Context)方法是该Sdk初始化的地方,其参数context为Application Context,同时需要返回一个Sdk对外提供的对象实例。
dependencies()方法则需要返回一个列表,如果这个sdk是独立的没有依赖与其它的sdk,可以将该方法返回一个空列表(如上)。如果这个sdk依赖于其它的sdk,必须在其它sdk初始化之后才能初始化,则需要在dependencies()方法中指明。
如下:表示TestSDK1需要在TestSDK2初始化完后才初始化

class Test1SDKInitializer :Initializer<Test1SDK>{
    override fun create(context: Context):Test1SDK{
       Test1SDK.init(context)
       return Test1SDK.getInstance()
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        val list = ArrayList<Class<out Initializer<*>>>()
        list.add(Test2SDKInitializer::class.java)
        return list
    }
}
class Test2SDKInitializer :Initializer<Test2SDK>{
    override fun create(context: Context):Test2SDK{
       Test2SDK.init(context)
       return Test2SDK.getInstance()
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
       return emptyList()
    }
}

3:在AndroidMainfest.xml里注册provider

<provider
    android:authorities="${applicationId}.androidx-startup"
    android:name="androidx.startup.InitializationProvider"
    android:exported="false"
    tools:node="merge">
    <meta-data
       android:name="com.example.jetpackappstartuptesty.BuglyInitializer"
       android:value="@string/androidx_startup"/>
 </provider>

通常每一个Initializer对应一个标签,但是如果有些Initializer已经被一个已 经注册的Initializer依赖(比如TestSDK2Initializer已经被TestSDK1Initializer依赖),那么 可以不用在AndroidManifest.xml文件中显式地指明,因为App Startup已经通过 注册的TestSDK1Initializer找到它了。

这里的标签的value属性必须指定为字符串androidx_startup的值, 也就是("androidx.startup"),否则将不生效。

如果有一个sdk内部通过App Startup帮助使用者处理了初始化,那么sdk的AndroidManifest.xml文件中已经存在了InitializationProvider的provider标签,此时会与app模块中的冲突,因此在app模块的provider标签中指明tools:node="merge",通过AndroidManifest.xml文件的合并机制。

4:App Startup懒加载实现

有些SDK我们不需要一开始就初始化,而只是在使用之前初始化就行,那么就可以考虑使用懒加载的形式,在使用SDK的功能之前调用如下代码进行初始化

 // 使用之前调用
 AppInitializer.getInstance(applicationContext).initializeComponent(BuglyInitializer::class.java)

并且在AndroidMainfest.xml里声明remove

<provider
    android:authorities="${applicationId}.androidx-startup"
    android:name="androidx.startup.InitializationProvider"
    android:exported="false"
    tools:node="merge">
    <meta-data
       android:name="com.example.jetpackappstartuptesty.BuglyInitializer"
       android:value="@string/androidx_startup"
       tools:node="remove"/>
 </provider>

三:源码解析

App Startup比较简单,只有5个类,AppInitializer,InitializationProvider,Initializer,StartupException,StartupLogger。下面一个个看

// StartupLogger只是个log使用
public final class StartupLogger {
    private StartupLogger() {
    }
    private static final String TAG = "StartupLogger";
    static final boolean DEBUG = false;
    public static void i(@NonNull String message) {
        Log.i(TAG, message);
    }
    public static void e(@NonNull String message, @Nullable Throwable throwable) {
        Log.e(TAG, message, throwable);
    }
}
// StartupException只是定义了个异常
public final class StartupException extends RuntimeException {
    public StartupException(@NonNull String message) {
        super(message);
    }
    public StartupException(@NonNull Throwable throwable) {
        super(throwable);
    }
    public StartupException(@NonNull String message, @NonNull Throwable throwable) {
        super(message, throwable);
    }
}
// Initializer只是个定义了个接口
public interface Initializer<T> {
    @NonNull
    T create(@NonNull Context context);
    @NonNull
    List<Class<? extends Initializer<?>>> dependencies();
}

// 自定义了个InitializationProvider
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;
    }

    @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.");
    }
}

可以看到Initializer只是定义了个接口,StartupException自定义的异常类,StartupLogger打log的。InitializationProvider是自定义的ContentProvider。他的onCreate方法里调用了 AppInitializer.getInstance(context).discoverAndInitialize(); 所以关键是看AppInitializer的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);
           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();
       }
   }
  • ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName());获取InitializationProvider
  • ProviderInfo providerInfo = mContext.getPackageManager() .getProviderInfo(provider, GET_META_DATA);
    Bundle metadata = providerInfo.metaData;拿到metadata信息
  • for (String key : keys) 遍历metadata, if (startup.equals(value)) 判断value是不是androidx_startup(这就是为何value一定要是androidx_startup的原因)符合条件就把该组件添加到mDiscovered集合中。并执行组件的初始化doInitialize()
  • final Set<Class>> mDiscovered;
    mDiscovered = new HashSet<>();mDiscovered是一个HashSet

接着分析doInitialize方法

<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();
            }
        }
    }

我们可以看到,该方法主要执行了初始化,并且对依赖的组件也进行初始化

  • Object instance = component.getDeclaredConstructor().newInstance();
    Initializer initializer = (Initializer) instance; 自己的初始化

  • List<Class>> dependencies = initializer.dependencies();获取到Sdk的组件集合

    if (!dependencies.isEmpty()) {
     for (Class<? extends Initializer<?>> clazz : dependencies) {
          if (!mInitialized.containsKey(clazz)) {
               doInitialize(clazz, initializing);
          }
      }
    }       
    

    遍历做依赖的SDK的初始化。