Jetpack App Startup——SDK自动初始化,告别Init

一个项目在开发过程中,常常都伴随着很多sdk的依赖。大部分的sdk在使用时都需要在应用启动时进行初始化才能正常工作,所以在集成sdk时通常需要做如下操作:

在Application的onCreate内调用对应sdk的初始化方法,目的是:

  1. 保证在sdk使用之前,sdk需要的一些准备工作已经完成;

  2. 为sdk内部提供application context;

所以我们常常会看到类似的调用:

XxxSDK.init(application)

最近发现一种sdk自动初始化的方案,可以告别sdk集成手动初始化。

ContentProvide想必都不陌生吧

定义一个ContentProvider,然后在mainfest注册声明后,它就能在应用启动时被创建

通过分析应用的启动流程可以发现如下的执行顺序

Application attachBaseContext
           |
           |
           V
ContentProvide attachInfo
           |
           |
           V
ContentProvide onCreate
           |
           |
           V
Application onCreate

通常初始化sdk都是在Application的onCreate完成的,那么我们在ContentProvide的attachInfo和onCreate中进行sdk的初始化是不是也能保证sdk的初始化时机?通过ContentProvide是不是也能获取到了Application Context,那么开始说到的sdk初始化的两个目的是不是也同样能达到?

现在实现思路便有了:

可以在sdk内注册应以一个ContentProvide,然后在它的onCreate内自行完成sdk的初始化任务,那么在app集成该sdk时候就不用在app层显式的编写sdk初始化逻辑便可以直接调用sdk的相关功能。

这里做一个思考?

一个APP的完成往往需要依赖很多的sdk?那么如果每个sdk都去创建一个ContentProvide,是不是会大大增加APP的启动时长,那岂不是有点得不偿失了。

如何避免每个sdk都创建一个ContentProvide,能不能都共用一个ContentProvide呢,答案时可以的,并且google官方已经提供了对应的开源库。

App Startup | Android Developers (google.cn)

实现很简单,源码总共就五个文件,主要代码就几百行
在这里插入图片描述

常规流程,先看看怎么使用

1、添加版本依赖

dependencies {
    implementation("androidx.startup:startup-runtime:1.1.1")
}

2、定义一个用于初始化的类, implements Initializer<T>,这里就直接引用官方的例子吧

// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder().build()
        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        // No dependencies on other libraries.
        return emptyList()
    }
}

Initializer接口需要实现两个方法

--create():完成对应的初始化操作
--dependencies(): 需要返回一个List,Initializer实现类的Class,
这是代表初始化该任务需要依赖的其他初始化项,添加返回值后,会将该初始化任务依赖的初始化项
放到它的前面,保证它所依赖的初始化项先进行初始化。

如下例子,定义初始化类ExampleLoggerInitializer,它需要先执行上面WorkManagerInitialize的初始化任务,则实现如下:

// Initializes ExampleLogger.
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
    override fun create(context: Context): ExampleLogger {
        // WorkManager.getInstance() is non-null only after
        // WorkManager is initialized.
        return ExampleLogger(WorkManager.getInstance(context))
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // Defines a dependency on WorkManagerInitializer so it can be
        // initialized after WorkManager is initialized.
        return listOf(WorkManagerInitializer::class.java)
    }
}

3、在mainfest注册对应的Contentprovide,并声明需要执行的初始化任务

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- This entry makes ExampleLoggerInitializer discoverable. -->
    <meta-data  android:name="com.example.ExampleLoggerInitializer"
          android:value="androidx.startup" />
</provider>


//tools:node="merge"  Mainfest 合并冲突的节点标记:合并此标记中的所有属性以及所有嵌套元素
//多个sdk使用startup同时声明注册InitializationProvider进行合并


//meta-data:声明了需要执行初始化的Initializer实现类

好了,通过以上的几步就可以实现一个sdk的自动初始化,而无需再到application里面显式的手动调用初始化的方法。


说完了怎么使用的,再来简单的看看Startup内部时怎么将开始说的实现思路转变成代码的吧,总共也就几百行代码。

开头已经介绍了它的实现思路,利用Contentprovide来完成sdk内部的自动初始化,而上面的第三步在Mainfest内注册声明的InitializationProvider是不是就是对应的用来完成初始化的?那我们便从InitializationProvider来切入:

    /** 主要实现就一个onCreate方法,其他的增删改查方法不允许被调用直接抛出异常
      * 按照思路onCreate内进行初始化任务,可以发现这里只调用了AppInitializer的discoverAndInitialize方法
      * discoverAndInitialize字面理解(发现并且初始化)不难看出AppInitializer内主要的逻辑就是解析需要初始化的
      * Initializer都有哪些,然后逐一调用初始化方法
      **/
    @Override
    public final boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            Context applicationContext = context.getApplicationContext();
            if (applicationContext != null) {
                AppInitializer.getInstance(context).discoverAndInitialize();
            } else {
                StartupLogger.w("Deferring initialization because `applicationContext` is null.");
            }
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }return true;
    }

接着看AppInitializerdiscoverAndInitialize

void discoverAndInitialize() {
        try {
            ComponentName provider = new ComponentName(mContext.getPackageName(),
                    InitializationProvider.class.getName());
            ProviderInfo providerInfo = mContext.getPackageManager()
                    .getProviderInfo(provider, GET_META_DATA);
            Bundle metadata = providerInfo.metaData;
            discoverAndInitialize(metadata);
        } catch (PackageManager.NameNotFoundException exception) {
            throw new StartupException(exception);
        } finally {
        }
    }
/**
  * 该方法很简单,就是获取在Mainfest注册的InitializationProvider下声明的metadata数据
  * 拿到metadata数据干什么用呢?很显然metadata标签声明的是Initializer的实现类,即每个需要初始化的任务的
  **/

接着看拿到metadata后调用的方法

void discoverAndInitialize(@Nullable Bundle metadata) {
        String startup = mContext.getString(R.string.androidx_startup);
        try {
            if (metadata != null) {
                Set<Class<?>> initializing = new HashSet<>();
                Set<String> keys = metadata.keySet();
                //1、遍历所有的metadata标签声明的数据
                /** 还记得metadata标签都声明了哪些值吗?
                  * name: Initializer实现类
                  * value:androidx.startup
                 */
                for (String key : keys) {
                    String value = metadata.getString(key, null);
                    //2、判断每个metadata是不是都带androidx.startup
                    if (startup.equals(value)) {
                        //3、将Initializer Class添加到mDiscovered中,为解析出来的需要初始化的任务
                        Class<?> clazz = Class.forName(key);
                        if (Initializer.class.isAssignableFrom(clazz)) {
                            Class<? extends Initializer<?>> component =
                                    (Class<? extends Initializer<?>>) clazz;
                            mDiscovered.add(component);
                        }
                    }
                }
                // 4、遍历mDiscovered,将Initializer逐个执行初始化
                for (Class<? extends Initializer<?>> component : mDiscovered) {
                    doInitialize(component, initializing);
                }
            }
        } catch (ClassNotFoundException exception) {
            throw new StartupException(exception);
        }
    }

接着看doInitialize

private <T> T doInitialize(
            @NonNull Class<? extends Initializer<?>> component,
            @NonNull Set<Class<?>> initializing) {
        try {
            //1、initializing这是在上个方法中创建的map,当开始执行每个Initializer初始化之前会将其加入到
            // initializing map中,然后在执行完成后再remove掉
            // 所以此处是防止正在被出初始化的任务再次调用
            if (initializing.contains(component)) {
                String message = String.format(
                        "Cannot initialize %s. Cycle detected.", component.getName()
                );
                throw new IllegalStateException(message);
            }
            Object result;
            // 2、mInitialized记录了被初始化Initializer, 防止同一个初始化任务多次初始化
            if (!mInitialized.containsKey(component)) {
                initializing.add(component);
                try {
                    //3、这里反射创建出Initializer对象,获取到Initializer初始化任务的依赖项
                    // Initializer接口实现的dependencies返回的依赖列表
                    Object instance = component.getDeclaredConstructor().newInstance();
                    Initializer<?> initializer = (Initializer<?>) instance;
                    List<Class<? extends Initializer<?>>> dependencies =
                            initializer.dependencies();
                    //4、这里将依赖项遍历,先将依赖项进行初始化,递归回到了doInitialize方法
                    if (!dependencies.isEmpty()) {
                        for (Class<? extends Initializer<?>> clazz : dependencies) {
                            if (!mInitialized.containsKey(clazz)) {
                                doInitialize(clazz, initializing);
                            }
                        }
                    }
                  //5、最后再调用当前initializer的create方法,所以Initializer实现类的初始化任务在create中调用
                    result = initializer.create(mContext);
                    //6、初始化完成,将initializer从正在初始化的记录中移除
                    initializing.remove(component);
                    //7、initializer放入已经完成初始化的记录中mInitialized
                    mInitialized.put(component, result);
                } catch (Throwable throwable) {
                    throw new StartupException(throwable);
                }
            } else {
                result = mInitialized.get(component);
            }
            return (T) result;
        } finally {
        }
    }

到这里,整个startup的使用和实现原理都介绍完了,赶快使用体验下吧,当然我们也完全可以自己写一个Startup了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值