Jetpack-Startup解析和应用

  • Startup

    添加依赖:

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

    manifest中加入:

    
        
    
    

    merge属性可以确保和其他manifest合并后消除冲突;exported表示该content provider是私有的,provider中的其他属性都是固定的,authorities属性是根据applicationId确定的。

    meta-data标签中就是自定义Initializer放置的位置,可以定义多个meta-data标签声明多个Initializer,name是完整类名,value是固定的。

    class CCInitializer : Initializer {
        override fun create(context : Context) : User {
            val uu = User("CC")
            uu.name = "CC"
            print("CC")
            return uu
        }
    
        override fun dependencies() : MutableList>> {
            return mutableListOf(PPInitializer::class.java)
        }
    }
    

    其中dependencies中定义的是该Initializer所依赖的其他Initializer,在初始化当前Initializer之前会先初始化它所有依赖的Initializer,比如这里的PPInitializer会比CCInitializer优先初始化,至于dependencies中返回的Initializer集合则会按照集合的顺序从头开始初始化,下面解析代码的时候会看到。

  • 解析

    在InitializationProvider创建的时候会调用它的onCreate方法:

    @Override
    public final boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            AppInitializer.getInstance(context).discoverAndInitialize();
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }
    

    这里会调用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> initializing = new HashSet<>();
                Set 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> component =
                                    (Class>) 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();
        }
    }
    

    首先会调用PackageManager的getProviderInfo方法获取meta-data信息,mContext.getString(R.string.androidx_startup)得到的字面值就是“androidx.startup”,遍历所有的meta-data,找出所有value是“androidx.startup”的,取它们的key,也就是manifest中meta-data中name属性的值作为类名,把它的Class对象存入mDiscovered集合,然后调用doInitialize方法进行初始化:

     T doInitialize(
            @NonNull Class> component,
            @NonNull Set> 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>> dependencies =
                                initializer.dependencies();
    
                        if (!dependencies.isEmpty()) {
                            for (Class> 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();
            }
        }
    }
    

    结合代码我们看到,这里会先把当前Initializer的Class存入initializing中,然后通过反射构造当前Initializer,在调用它的create方法之前,会先调用它的dependencies方法,然后对它返回的集合进行递归doInitialize调用,这就是为什么依赖的Initializer会先初始化的原因。因为dependencies中返回的是List,所以这里的循环会按照List的顺序从头开始,所以dependencies集合中越靠前的会更先调用create方法初始化。

    注意,在discoverAndInitialize方法中调用doInitialize方法时传入的是一个空的Set集合,然后在doInitialize方法中递归传递的也都是这个Set,你可能会说Set不是无序的嘛,其实这里的Set只是为了确保不添加重复的数据,因为整个过程中只调用了它的add和remove方法,没有取过数据,所以Set的无序性是无影响的,这里的用处只是暂存待初始化的Initializer而已,你可以把它换成一个类变量,是一样的作用,因为doInitialize方法是在discoverAndInitialize中开启调用的,所以递归初始化完成后最终还是会回到discoverAndInitialize方法,所以这里使用一个方法内变量作为共享变量是更合理的,使用类变量就有些重量级了。

    AppInitializer.getInstance(this).initializeComponent(CCInitializer::class.java)
    

    最后,调用AppInitializer实例的initializeComponent方法就可以获取启动时已初始化好的相关实例了,在上面的流程中,初始化的实力会保存到mInitialized中,所以调用initializeComponent方法获取的时候会优先从mInitialized取,如果mInitialized中没有的话则会再次执行上述初始化流程,只不过此时执行的初始化只是针对单个的Initializer而已,可见,你也可以手动调用AppInitializer来完成初始化工作。

  • 应用场景

    为什么要使用Startup?

    因为ContenProvider的 onCreate 调用在 Application.attachBaseContext()Application.onCreate() 之间发生,所以很多在应用启动之后就要发挥作用的库都会选择通过在ContenProvider的onCreate方法中进行初始化,但是ContenProvider作为一个重量级组件,它本身的启动和初始化是需要一定时间的,所以如果ContenProvider数量过多会造成一定的性能损耗,所以Startup的目的就是把所有的用于初始化的ContenProvider都合并成一个,这样就减少了大量的ContenProvider创建性能损耗和内存占用。

    但是,Startup无法作用于你所依赖的第三方库,因为你没有权限去修改第三方库的manifest文件,所以第三方库中的ContenProvider还是会存在,Startup只能使你自定义的初始化工作合并到一个ContenProvider中去,所以它的场景是:

    在你需要创建一个需要在启动时初始化的类库,或者你的app在启动时就需要很多本地初始化工作的时候,你可以选择使用Startup框架来处理,而且你可以通过Startup让初始化业务分门别类、顺序化。

    总之,Startup会在项目庞大或者有类库需要的时候才会有用武之地。

你可能感兴趣的:(Jetpack-Startup解析和应用)