[启动] Jetpack Startup原理

690 阅读5分钟

背景

前一篇文章,我们讲ContentProvider的启动流程[启动] ContentProvider启动流程,提到了ContentProvider的初始化时机是在ApplicationonCreate方法之前,所以ContentProvider的初始化会影响到应用的启动速度。

现在很多第三方库,甚至Google官方提供的库,为了降低使用成本,都会选择注册一个ContentProvider用于初始化。这个ContentProvider通常什么都不做,只是调用了这些第三方库的init方法。例如FirebaseworkManager等。

ContentProvider的启动,是通过反射创建对象,然后调用attachInfo方法,进而调用到onCreate方法的。即使是一个空的ContentProvider,也需要耗费几十毫秒的创建时间,这对启动来说往往是非常关键的。

另外,零散的ContentProvider,不便于对初始化任务进行统一管理,比如管理依赖关系,优先级等。

于是,Google官方开始下手,在Jetpack中推出了Startup,将初始化相关的ContentProvider都整理合成到一个InitializationProvider,在这一个Provider中完成所有的初始化工作。这样不仅能减少ContentProvider的数量,减少启动耗时,同时也能更好地 管理这些初始化任务,可以实现一些简单的依赖关系管理。

Startup使用

1. 引入依赖

首先在build.gradle中加入依赖库。

implementation "androidx.startup:startup-runtime:1.1.1"

2. 构建组件及依赖关系

使用Startup框架初始化的组件,需要继承 Initializer 接口。

class InitializerA : Initializer<A> {

    // 完成组件初始化,返回初始化后的结果
    override fun create(context: Context): A {
        return A.init(context)
    }

    // 依赖的组件
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return listOf(InitializerB::class.java)
    }
}

class InitializerB : Initializer<B> {

    override fun create(context: Context): B {
        return B.init(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return  null
    }
}

Initializer接口有两个方法:

  • create方法:在这里调用组件的初始化方法,并返回初始化后的结果
  • dependencies方法:当前组件所有依赖组件,依赖组件会优先与当前组件被初始化。

上面的例子中,B是A的依赖组件,所以先初始化B,后初始化A。最后依赖关系,需要形成一个DAG有向无环图

3. 自动初始化

AndroidManifest文件中声明组件,即可实现App启动自动初始化。

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="leavesc.lifecyclecore.core.InitializerA"
        android:value="androidx.startup" />
</provider>

有几点需要注意:

  1. 此处只需要写InitializerA即可,因为InitializerB是A的依赖项,在初始化A之前,会自动初始化B。

  2. 必须是merge,因为Manifest里面已经有声明过InitializationProvider了(在androidx库里),此处声明的需要与之前声明的合并。

  3. 手动初始化

如果Initializer不需要一启动就初始化,则无需在AndroidManifest中声明,直接在用到的地方懒加载即可。

val result = AppInitializer.getInstance(this).initializeComponent(InitializerA::class.java)

有几点注意:

  1. Startup内部会缓存Initializer初始化的结果,所以多次调用不会导致多次初始化。
  2. 可以用这个方法,获取自动初始化的结果。

Startup源码

当App启动时,走到InstallContentProviders时,会初始化InitializationProvider,调用它的attachInfo -> onCreate

public final class InitializationProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            // 主要逻辑,调用AppInitializer完成
            AppInitializer.getInstance(context).discoverAndInitialize();
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }
}

InitializationProvider就是一个普通的ContentProvider,在它的onCreate方法里,直接调用AppInitializer来完成后续的初始化操作。

下面我们着重看AppInitializer的源码。

    public static AppInitializer getInstance(@NonNull Context context) {
        if (sInstance == null) {
            synchronized (sLock) {
                if (sInstance == null) {
                    sInstance = new AppInitializer(context);
                }
            }
        }
        return sInstance;
    }

AppInitializer是一个单例,典型的双检索模式。

   void discoverAndInitialize() {
            ComponentName provider = new ComponentName(mContext.getPackageName(), InitializationProvider.class.getName());
            ProviderInfo providerInfo = mContext.getPackageManager().getProviderInfo(provider, GET_META_DATA);
            // 获取InitializationProvider下配置的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);
                    // 如果value等于androidx.startup
                    if (startup.equals(value)) {
                        Class<?> clazz = Class.forName(key);
                        // 如果key实现了Initializer接口
                        if (Initializer.class.isAssignableFrom(clazz)) {
                            Class<? extends Initializer<?>> component = (Class<? extends Initializer<?>>) clazz;
                            // 加入mDiscovered set
                            mDiscovered.add(component);
                            // 初始化这个component
                            doInitialize(component, initializing);
                        }
                    }
                }
            }
    }

discoverAndInitialize方法的主要作用是:

  • 找到Manifest文件中,InitializationProvider下的所有meta-data参数对。
  • 如果value等于androidx.startup,且key的类实现了Initializer接口,则加入mDiscovered set,调用doInitialize初始化这个component
    <T> T doInitialize(
            @NonNull Class<? extends Initializer<?>> component,
            @NonNull Set<Class<?>> initializing) {
        // sLock是一个静态对象
        synchronized (sLock) {
                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 set
                    initializing.add(component);
                    try {
                        // 初始化component对象
                        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);
                                }
                            }
                        }
                        // 初始化当前component
                        result = initializer.create(mContext);
                        // 从initializing set中移除component
                        initializing.remove(component);
                        // 放入mInitialized map中
                        mInitialized.put(component, result);
                    } catch (Throwable throwable) {
                        throw new StartupException(throwable);
                    }
                } else {
                    result = mInitialized.get(component);
                }
                return (T) result;
        }
    }

这个方法的主要流程如下:

  • 在初始化之前,先判断当前的依赖关系图中是否有环,如果有环,直接抛出异常
  • 判断当前组件是否已初始化,如果已初始化,直接返回result,防止重复初始化
  • 初始化组件对象,获取它的dependencies,先初始化dependencies
  • 所有dependencies初始化完成后,调用create方法初始化组件
  • 将初始化结果放入mInitialized map

总结

Startup的缺点:

  • 不能无痛接入,需要修改第三方库的源码,或者等待第三方库接入,才能生效。
  • 组件的create方法,都是在主线程调用,还是会影响启动。

Startup的源码非常简单,核心源码还不到200行,但是这个思路还是蛮值得借鉴的。

后面我们借鉴这个思路,实现了更完整、更灵活、更无痛的ContentProvider管理框架。

我们的新框架优点:

  • 无需修改第三方库源码,即可接入。
  • 可以选择在主线程或异步线程初始化组件。
  • 可以选择在不同的阶段初始化不同组件,如Application初始化完、首页加载完等。

如果好奇我们是怎么做的,麻烦帮忙点点赞吧~ 点赞多的话,下篇文章就更。