Android开发Android开发经验谈Android技术知识

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

2020-05-12  本文已影响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完成初始化。

上一篇 下一篇

猜你喜欢

热点阅读