美文网首页Android开发Android开发经验谈Android技术知识
爱奇艺框架Qigsaw插件Application初始化流程

爱奇艺框架Qigsaw插件Application初始化流程

作者: 于卫国 | 来源:发表于2020-05-12 16:18 被阅读0次

    本文对爱奇艺框架Qigsaw对Feature库Application初始化流程和对ContentProvider的处理进行了分析。

    《咏鹅 》
    鹅,鹅,鹅,曲项向天歌,
    白毛浮绿水,红掌拨清波。
    -骆宾王

    前言

    在上一篇文章中我们对爱奇艺框架Qigsaw的插件部分进行了源码分析,今天来看下Qigsaw是如何完成Feature库Application的初始化和对ContentProvider的处理。

    记录Feature库组件信息

    在上一篇文章中分析Qigsaw插件部分源码时忽略了一个Transform,Qigsaw在application插件上注册了一个ComponentInfoTransform,用于创建一个记录Feature库组件信息的类,生成类如下,字段名格式为splitname+"_ACTIVITIES",其他组件类似,如果包含多个组件中间会以逗号分隔:

    package com.iqiyi.android.qigsaw.core.extension;
    
    public class ComponentInfo {
        public static final String native_ACTIVITIES = "com.iqiyi.qigsaw.sample.ccode.NativeSampleActivity";
        public static final String java_ACTIVITIES = "com.iqiyi.qigsaw.sample.java.JavaSampleActivity";
        public static final String java_APPLICATION = "com.iqiyi.qigsaw.sample.java.JavaSampleApplication";
    
        public ComponentInfo() {
        }
    }
    

    这个类中记录了所有Feature库的三大组件Activity、Service、Receiver和Feature库的Application信息。这里并没有记录ContentProvider的信息,如果Feature库组件中包含ContentProvider,Qigsaw会创建一个ContentProvider的代理类,命名格式为providername+"_Decorated_"+splitname:

    package com.iqiyi.qigsaw.sample.java;
    
    import com.iqiyi.android.qigsaw.core.splitload.SplitContentProvider;
    
    public class JavaContentProvider_Decorated_java extends SplitContentProvider {
        public JavaContentProvider_Decorated_java() {
        }
    }
    

    由于ContentProvider的初始化位于Application的attachBaseContext和onCreate之间,所以如果Feature库未安装并且Feature库中含有ContentProvider组件时肯定会造成ClassNotFound异常,所以Qigsaw会在打包过程中使用这个代理类替换真正的ContentProvider,避免由于Feature库未安装导致应用崩溃。


    ComponentInfoTransform中的执行逻辑大致流程如下:


    Feature库Application初始化流程

    Feature库Application初始化流程分为两种情况,一种是Feature库已经安装打开应用时的初始化,另一种是执行安装命令后的Feature库Application初始化。

    已安装的Feature库Application初始化流程

    打开应用时Feature库的Application初始化流程大致如下:


    这里的关键步骤是从系统中获取已安装的splitname,当API>=21时,从PackageInfo中获取splitname:

    SplitAABInfoProvider.java

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private String[] getSplitInstallInfo() {
        try {
            PackageInfo packageInfo;
            return (packageInfo = context.getPackageManager().getPackageInfo(packageName, 0)) != null ? packageInfo.splitNames : null;
        } catch (Throwable var2) {
            SplitLog.printErrStackTrace(TAG, var2, "App is not found in PackageManager");
            return null;
        }
    }
    

    当API<21时,会从ApplicationInfo的metaData的shadow.bundletool.com.android.dynamic.apk.fused.modules中获取splitname:

    SplitAABInfoProvider.java

    private Set<String> getFusedModules() {
        Set<String> fusedModules = new HashSet<>();
        ApplicationInfo appInfo;
        try {
            appInfo = context.getPackageManager().getApplicationInfo(packageName, PackageManager.GET_META_DATA);
        } catch (Throwable e) {
            SplitLog.printErrStackTrace(TAG, e, "App is not found in PackageManager");
            return fusedModules;
        }
        if (appInfo != null && appInfo.metaData != null) {
            String fusedName;
            if ((fusedName = appInfo.metaData.getString("shadow.bundletool.com.android.dynamic.apk.fused.modules")) != null && !fusedName.isEmpty()) {
                Collections.addAll(fusedModules, fusedName.split(",", -1));
                fusedModules.remove("");
                return fusedModules;
            } else {
                SplitLog.d(TAG, "App has no fused modules.");
                return fusedModules;
            }
        } else {
            SplitLog.d(TAG, "App has no applicationInfo or metaData");
            return fusedModules;
        }
    }
    

    然后根据splitname从刚才ComponentInfoTransform中生成的ComponentInfo类中反射获取对应的ApplicationName,然后使用ClassLoader完成初始化,在Application的onCreate中调用Feature库的Application的onCreate,至此打开应用时Feature库的Application初始化流程已经完成。

    安装时Feature库Application初始化流程

    了解了已安装时Feature库Application的初始化流程,再来看安装时Feature库Application初始化流程就比较简单了,安装时会传入splitname等信息,会先判断该split是否已经安装过,如果安装过就会直接跳过,如果没有安装过会根据splitname从ComponentInfo类中反射获取对应的ApplicationName,然后使用ClassLoader完成初始化。

    除了Application初始化还会对真正的ContentProvider完成初始化。
    ContentProviderProxy.java

    public abstract class SplitContentProvider extends ContentProviderProxy {
        ...
    }
    
    public abstract class ContentProviderProxy extends ContentProvider {
        ...
        @Override
        public void attachInfo(Context context, ProviderInfo info) {
            String className = getClass().getName();
            String[] cuts = className.split(NAME_INFIX);
            this.realContentProviderClassName = cuts[0];
            this.splitName = cuts[1];
            super.attachInfo(context, info);
            this.providerInfo = new ProviderInfo(info);
            AABExtension.getInstance().put(splitName, this); //在hashmap中记录所有代理ContentProvider,用于split安装后激活真正的provider
        }
        ...
    }
    

    AABExtension.java

    void put(String splitName, ContentProviderProxy providerProxy) {
        List<ContentProviderProxy> providerProxies = sSplitContentProviderMap.get(splitName);
        if (providerProxies == null) {
            providerProxies = new ArrayList<>();
            sSplitContentProviderMap.put(splitName, providerProxies);
        }
        providerProxies.add(providerProxy);
    }
    

    在ContentProviderProxy代理类中会将该代理类记录在AABExtension类中的hashmap中,然后在Feature库Application完成初始化后对真正的ContentProvider完成初始化。

    总结

    首先介绍了下ComponentInfoTransform的作用用于创建一个记录所有Feature库组件的类,针对ContentProvider会创建一个代理类,在打包时会将清单文件中真正的ContentProvider替换为代理类,避免Feature库未安装时导致ClassNotFound异常。针对已安装的Feature在应用打开时会从系统获取splitname,然后根据splitname从创建的ComponentInfo中获取对应applicationname,最后使用ClassLoader完成Feature库的Application的加载和初始化;安装时除了Feature库的Application的初始化还会对真正的ContentProvider完成初始化。

    相关文章

      网友评论

        本文标题:爱奇艺框架Qigsaw插件Application初始化流程

        本文链接:https://www.haomeiwen.com/subject/qcuknhtx.html