Android Jetpack 之 App Startup

    技术2022-07-15  47

    1. 概述

    最近这几天关注的几个公众号陆续推送关于 App Startup 的相关文章,就知道又要学习新知识了。App Startup  和 Lifecycle 、DataBinding 一样都是 Jetpack 的组件之一。目前 App Startup 还处于 alpha 版本。官网地址:https://developer.android.google.cn/topic/libraries/app-startup?hl=en。

    2. App Startup 定义

    The App Startup library provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.

    Instead of defining separate content providers for each component you need to initialize, App Startup allows you to define component initializers that share a single content provider. This can significantly improve app startup time.

    翻译如下:

    App Startup 库提供了在应用程序启动时用于初始化组件的简单、高效的方式。开发人员可以使用 App Startup 来简化启动序列,并显式地设置初始化顺序。

    App Startup 允许您定义共享单个内容提供程序的组件初始化器,而不是为每个需要初始化的组件定义单独的 contentProvider。这可以显著提高应用程序的启动时间。

    3. ContentProvider 中进行初始化

    当定义一个 ContentProvider,在 App 的启动阶段,它是在什么时候进行初始化的?看下这个 log:

    2020-07-02 14:38:16.905 6928-6928/cn.zzw.template.startupdemo E/StartUpDemo: Application attachBaseContext 2020-07-02 14:38:16.907 6928-6928/cn.zzw.template.startupdemo E/StartUpDemo: ContentProvider onCreate 2020-07-02 14:38:16.908 6928-6928/cn.zzw.template.startupdemo E/StartUpDemo: Application onCreate

    在这里可以看到 ContentProvider 的 onCreate 方法执行时间是介于 Application 的 attachBaseContext 和 onCreate 之间。所以很多库会选择创建 ContentProvider 进行初始化。关于用 ContentProvider 进行初始化的介绍可以参考下此篇文章:使用ContentProvider初始化你的Library,这里不进行过多的介绍,App Startup 就是建立在此做法的基础上。

    4. App Startup 的导入

    目前 App Startup 还处于 alpha 版本:

    dependencies { implementation "androidx.startup:startup-runtime:1.0.0-alpha01" }

    5. App Startup 的使用

    假设当前有三个库需要进行初始化 SdkA、SdkB、SdkC,这里只写展示 SdkA 的代码,SdkB、SdkC 和 SdkA 结构相似:

    class SdkA { companion object { fun getInstance(): SdkA { return Instance.instance } } private object Instance { val instance = SdkA() } }

    创建对应的初始化对象,必须接口 Initializer<T>,接口 Initializer 的代码如下:

    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 方法用于定义需要在当前对象初始化之前进行初始化的对象对应的 Initializer。

    SdkA 的对应的 SdkAInitializer:

    class SdkAInitializer : Initializer<SdkA> { override fun create(context: Context): SdkA { Log.e("StartUpDemo", "SdkAInitializer create"); return SdkA.getInstance(); } override fun dependencies(): MutableList<Class<out Initializer<*>>> { Log.e("StartUpDemo", "SdkAInitializer dependencies"); return Collections.emptyList() } }

    SdkA 的初始化不需要其他库的依赖,所以这里 dependencies 方法返回一个空列表。

    SdkB 的对应的 SdkBInitializer:

    class SdkBInitializer : Initializer<SdkB> { override fun create(context: Context): SdkB { Log.e("StartUpDemo", "SdkBInitializer create"); return SdkB.getInstance(); } override fun dependencies(): MutableList<Class<out Initializer<*>>> { Log.e("StartUpDemo", "SdkBInitializer dependencies"); return mutableListOf(SdkAInitializer::class.java) } }

    这里假设 SdkA 必须在 SdkB 之前进行初始化,所以 dependencies 返回 SdkA 对应的 SdkAInitializer。

    SdkC 的对应的 SdkCInitializer:

    class SdkCInitializer : Initializer<SdkC> { override fun create(context: Context): SdkC { Log.e("StartUpDemo", "SdkCInitializer create"); return SdkC.getInstance(); } override fun dependencies(): MutableList<Class<out Initializer<*>>> { Log.e("StartUpDemo", "SdkCInitializer dependencies"); return mutableListOf(SdkBInitializer::class.java) } }

    这里假设 SdkB 必须在 SdkC 之前进行初始化,所以 dependencies 返回 SdkB 对应的 SdkBInitializer。

    接着在 manifest 中定义 InitializationProvider:

    <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data android:name="cn.zzw.template.startupdemo.initializer.SdkCInitializer" android:value="androidx.startup" /> </provider>

    在上面的三个 sdk中,C 依赖 B,B 依赖 A,A 不依赖其他。在 <meta-data> 标签中只需要定义 SdkCInitializer。

    运行后的 log 如下:

    2020-07-02 17:01:11.446 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: Application attachBaseContext 2020-07-02 17:01:11.549 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: ContentProvider onCreate 2020-07-02 17:01:11.577 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkCInitializer dependencies 2020-07-02 17:01:11.600 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkBInitializer dependencies 2020-07-02 17:01:11.601 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkAInitializer dependencies 2020-07-02 17:01:11.601 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkAInitializer create 2020-07-02 17:01:11.601 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkBInitializer create 2020-07-02 17:01:11.605 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: SdkCInitializer create 2020-07-02 17:01:11.647 8212-8212/cn.zzw.template.startupdemo E/StartUpDemo: Application onCreate

    从log可以看出,dependencies 方法的执行顺序是 C → B → A,create 方法的执行顺序是 A → B → C。这里的 create 方法的顺序正是对象创建的顺序。

    5.1 手动初始化

    在上面的例子中,当应用运行后,所有的 SDK 都进行了初始化。可是所有的 SDK 都在启动阶段进行初始化,会导致启动速度的变慢,某些 SDK 如何在需要使用的时候才进行初始化?

    再创建一个 SdkD,SdkCInitializer 类如下:

    class SdkDInitializer : Initializer<SdkD> { override fun create(context: Context): SdkD { Log.e("StartUpDemo", "SdkDInitializer create"); return SdkD.getInstance(); } override fun dependencies(): MutableList<Class<out Initializer<*>>> { Log.e("StartUpDemo", "SdkDInitializer dependencies"); return Collections.emptyList() } }

    需要在 manifest 中通过 <meta-data> 标签中只需要定义 SdkDInitializer,并且添加  tools:node="remove":

    <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data android:name="cn.zzw.template.startupdemo.initializer.SdkCInitializer" android:value="androidx.startup" /> <meta-data android:name="cn.zzw.template.startupdemo.initializer.SdkDInitializer" android:value="androidx.startup" tools:node="remove" /> </provider>

    在 Activity 中对其进行初始化:

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) AppInitializer.getInstance(this).initializeComponent(SdkDInitializer::class.java) }

    运行后 log 如下:

    2020-07-02 18:03:21.070 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: Application attachBaseContext 2020-07-02 18:03:21.194 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: ContentProvider onCreate 2020-07-02 18:03:21.206 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkCInitializer dependencies 2020-07-02 18:03:21.213 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkBInitializer dependencies 2020-07-02 18:03:21.221 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkAInitializer dependencies 2020-07-02 18:03:21.221 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkAInitializer create 2020-07-02 18:03:21.221 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkBInitializer create 2020-07-02 18:03:21.222 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkCInitializer create 2020-07-02 18:03:21.228 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: Application onCreate 2020-07-02 18:03:21.729 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkDInitializer dependencies 2020-07-02 18:03:21.729 8850-8850/cn.zzw.template.startupdemo E/StartUpDemo: SdkDInitializer create

    从 log 中可以看到 SdkD 在最后才进行初始化。

    6. 源码分析

    在 manifest 中定义 InitializationProvider 是一个 ContentProvider:

    @RestrictTo(RestrictTo.Scope.LIBRARY) 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; } ... }

    只保留它的一个 onCreate 方法,调用了 AppInitializer 的 discoverAndInitialize 方法。

    AppInitializer 是一个单例,AppInitializer 的构造方法:

    AppInitializer(@NonNull Context context) { mContext = context.getApplicationContext(); mInitialized = new HashMap<>(); }

    在构造方法中创建一个 HashMap。

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

    首先获取 meta-data 标签,并且遍历每一个 meta-data 中的 Initializer 对象,并调用 doInitialize 方法。

    <T> T doInitialize( @NonNull Class<? extends Initializer<?>> component, @NonNull Set<Class<?>> initializing) { synchronized (sLock) { ... 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(); // 1 if (!dependencies.isEmpty()) { for (Class<? extends Initializer<?>> clazz : dependencies) { if (!mInitialized.containsKey(clazz)) { doInitialize(clazz, initializing); // 2 } } } if (StartupLogger.DEBUG) { StartupLogger.i(String.format("Initializing %s", component.getName())); } result = initializer.create(mContext); // 3 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 中:先找 需要初始化对应的 Initializer,并判断其 dependencies 返回是否为空,如果不为空,获取到对应的 Initializer,并继续执行 doInitialize(注释 2)。这样的话,所以的 dependencies 方法会先被执行完毕,只有 dependencies 都执行完了后,才会执行注释 3 中的 create 方法。

    再来看看延迟加载时候调用的 initializeComponent 方法:

    public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) { return doInitialize(component, new HashSet<Class<?>>()); }

    这里就是调用了 doInitialize 方法。

    7. 总结

    App Startup 提供了一个 ContentProvider 来运行所有依赖项的初始化,避免了把依赖项写在 Application 中,也避免每个需要初始化的库单独使用 ContentProvider 进行初始化,从而提高了 App 的启动速度。App Startup 可以自定义需要初始化的依赖的初始化顺序。App Startup 中每个需要初始化的依赖都需要实现 Initializer 接口,会导致文件的增多。App Startup 目前还处于 alpha 版本,相信在后续的版本更新中会有更多的亮点。

    Processed: 0.011, SQL: 9