请点赞关注,你的反对对我意义重大。

Hi,我是小彭。本文已收录到 GitHub · AndroidFamily 中。这里有 Android 进阶成长常识体系,有气味相投的敌人,关注公众号 [彭旭锐] 带你建设外围竞争力。

前言

大家好,我是小彭。

2020 年 10 月 28 日,JetPack | App Startup 1.0.0 终于迎来正式公布,正好最近在总结组件化架构专题,所以也专门学习下 App Startup 的工作原理。在这篇文章里,我将带你总结 App Startup 的应用办法 & 实现原理 & 源码剖析。有用请点赞给 Star,给小彭一点创作的能源,谢谢。


这篇文章是 Jetpack 系列文章第 13 篇,专栏文章列表:

  • 1、Lifecycle:生命周期感知型组件的根底
  • 2、LiveData:生命周期感知型数据容器(本文)
  • 3、ViewModel:数据驱动型界面控制器
  • 4、Flow:LiveData 的代替计划
  • 5、从 MVC 到 MVP、MVVM、MVI:Android UI 架构演进
  • 6、ViewBinding:新一代视图绑定计划
  • 7、Fragment:模块化的微型 Activity
  • 8、RecyclerView:可复用型列表视图
  • 9、Navigation:单 Activity 多 Fragment 的导航计划
  • 10、Dagger2:从 Dagger2 到 Hilt 玩转依赖注入(一)
  • 11、Hilt:从 Dagger2 到 Hilt 玩转依赖注入(二)
  • 12、OnBackPressedDispatcher:解决回退事件的新姿态

二、其余:

  • 1、AppStartup:轻量级初始化框架(本文)
  • 2、DataStore:新一代键值对存储计划
  • 3、Room:ORM 数据库拜访框架
  • 4、WindowManager:增强对多窗口模式的反对
  • 5、WorkManager:增强对后台任务的反对
  • 6、Compose:新一代视图开发计划

学习路线图:


1. 意识 AppStartup

1.1 App Startup 解决了什么问题?

App Startup 是 Google 提供的 Android 轻量级初始化框架:

  • 长处:应用 App Startup 框架,能够简化启动序列并显式设置初始化依赖程序,在简略、高效这方面,App Startup 根本满足需要。
  • 有余:App Startup 框架的有余也是因为它太简略了,提供的个性太过简略,往往并不能完满符合商业化需要。例如以下个性 App Startup 就无奈满足:

    • 不足异步期待: 同步期待指的是在以后线程先初始化所依赖的组件,再初始化以后组件,App Startup 是反对的,然而异步期待就不反对了。举个例子,所依赖的组件须要执行一个耗时的异步工作能力实现初始化,那么 App Startup 就无奈期待异步工作返回;
    • 不足依赖回调: 以后组件所依赖的组件初始化实现后,未收回回调。

1.2 App Startup 如何实现主动初始化?

App Startup 利用了 ContentProvider 在利用启动的时候初始化的个性,提供了一个自定义 ContentProvider 来实现主动初始化。很多库都利用了 ContentProvider 的启动机制,来实现无侵入初始化,例如 LeakCanary 等

应用 AppStartup 还可能合并所有用于初始化的 ContentProvider ,缩小创立 ContentProvider,并提供全局治理。

App Startup 示意图

具体的源码剖析下文内容。


2. App Startup 应用办法

这一节,咱们来总结 App Startup 的应用步骤。

2.1 根本用法

  • 1、增加依赖

在模块级 build.gradle 增加依赖:

模块级 build.gradle

implementation "androidx.startup:startup-runtime:1.0.0"
  • 2、实现 Initializer 接口

Initializer 接口是 App Startup 定义组件接口,用于指定组件的初始化逻辑和初始化程序(也就是依赖关系),接口定义如下:

  • 1、create(...) 初始化操作: 返回的初始化后果将被缓存,其中 context 参数就是以后过程的 Application 对象;
  • 2、dependencies() 依赖关系: 返回值是一个依赖组件的列表,如果不须要依赖于其它组件,返回一个空列表。App Startup 在初始化以后组件时,会保障所依赖的组件曾经实现初始化。

Initializer.java

public interface Initializer<T> {    // 1、初始化操作,返回值将被缓存??    @NonNull    T create(@NonNull Context context);    // 2、依赖关系,返回值是一个依赖组件的列表    @NonNull    List<Class<? extends Initializer<?>>> dependencies();}

示例程序

// LeakCanary 2.9.1internal 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<*>>>()}
  • 3、配置 <meta-data>

在 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>

要点如下:

  • 1、组件名必须是 androidx.startup.InitializationProvider
  • 2、须要申明 android:exported="false",以限度其余利用拜访此组件;
  • 3、要求 android:authorities 要求在设施中全局惟一,通常应用 ${applicationId} 作为前缀;
  • 4、须要申明 tools:node="merge",确保 manifest merger tool 可能正确解析抵触的节点;
  • 5、meta-data android:name 为组件的 Initializer 实现类的全限定类名,android:value 固定为 androidx.startup
提醒: 为什么要将 androidx.startup 设置为 value,而不是 name?因为在键值对中,name 是惟一的,而 value 是容许反复的,将 androidx.startup 放到 value 的话能力容许同时配置多个雷同语义的 <meta-data>

至此,App Startup 根本的应用与配置实现,在利用启动时,App Startup 会主动收集各个模块配置的 Initializer 实现类,并依照依赖程序顺次执行。

2.2 进阶用法

  • 1、手动初始化

当你的组件须要进行手动初始化,而不是主动初始化时(例如存在耗时工作),能够进行手动初始化,而且手动初始化是能够在子线程调用的,而主动初始化均是在主线程执行的。

  • App Startup 中会缓存初始化后的后果,反复调用 initializeComponent() 也不会导致反复初始化;
  • 要手动初始化的 Initializer 实现类不能在申明到 AndroidManifest 中,也不能被其它组件依赖,否则它仍然会主动初始化。

调用以下方即可进行手动初始化:

示例程序

AppInitializer.getInstance(context).initializeComponent(ExampleLoggerInitializer::class.java)
  • 2、勾销主动初始化

如果有些库曾经配置了主动初始化,而咱们又心愿进行懒加载时,就须要利用 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>
  • 3、禁用 App Startup

如果须要齐全禁用 App Startup 主动初始化,同样也能够利用到 manifest merger tool 的合并规定:

示例程序

<provider    android:name="androidx.startup.InitializationProvider"    android:authorities="${applicationId}.androidx-startup"    tools:node="remove" />

3. App Startup 原理剖析

3.1 App Startup 如何实现主动初始化?

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

3.2 说一下 App Startup 的初始化过程

从上一节能够看到,App Startup 在 InitializationProvider 中调用了AppInitializer#discoverAndInitialize()执行主动初始化。AppInitializer是 App StartUp 框架的外围类,整个 App Startup 框架的代码其实非常少,其中很大部分外围代码都在 AppInitializer 类中。

我将整个主动初始化过程概括为 3 个阶段:

  • 步骤 1 - 获取 <meta-data> 数据: 扫描 Manifest 中定义在 InitializationProvider 外面的 <meta-data> 数据,从中筛选出 Initializer 的配置信息;
  • 步骤 2 - 递归执行初始化器: 通过 Initializer#create() 执行每个初始化器的逻辑,并且会通过 Initializer#dependencies() 优先保障依赖项曾经初始化;
  • 步骤 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;}

3.3 手动初始化的执行过程

后面咱们提到应用 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) {        ...    }}

4. 总结

到这里,App Startup 的内容就讲完了。能够看到 App Startup 只是一个轻量级的初始化框架,能做的事件无限。市面上有开发者开源了基于 DAU 有向无环图的初始化框架,这个咱们下次再说。关注我,带你理解更多。


参考资料

  • App Startup —— Android Developers
  • 合并多个清单文件 —— Android Developers
  • AndroidX: App Startup —— Husayn Hakeem 著
  • Jetpack新成员,App Startup 一篇就懂 —— 郭霖 著
  • 我为何弃用 Jetpack 的 App Startup? —— 午后一小憩 著
  • 更快!这才是我想要的 Android Startup 库! —— idisfkj 著
  • 组件化:代码隔离也难不倒组件的按序初始化 —— leobert-lan 著
  • 从源码看 Jetpack(5)Startup 源码详解 —— 叶志陈 著
我是小彭,带你构建 Android 常识体系。技术和职场问题,请关注公众号 [彭旭锐] 私信我发问。