Qigsaw 源码分析 编译过程

Posted 化作孤岛的瓜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qigsaw 源码分析 编译过程相关的知识,希望对你有一定的参考价值。

本文主要是对Qigsaw的源码分析。Qigsaw是一种动态组件化技术,可以有效减少包size,进行热更新等调优功能,在实际应用开发中能带来不错的收益与启发。

基于2021/7/14版本分析(https://github.com/iqiyi/Qigsaw)

Qigsaw分为编译过程→安装过程→加载过程 三个阶段进行源码分析。

如何调试gradle:

Android如何创建Gradle插件开发工程及调试_xialonghua的专栏-CSDN博客_gradle插件开发

目录

包目录结构说明:

1 QigsawAppBasePlugin

1.1 Transform部分:

1.2 Task部分

2 QigsawDynamicFeaturePlugin

2.1 SplitLibraryLoaderTransform

总结



包目录结构说明:

app:测试工程

assets:测试用资源模块

java:测试用java模块

native:测试用native模块

downloader:下载模块

buildSrc:编译期module

playcorelibrary:google app bundle 套件

下面的都是加载和安装过程组件

splitcommon

splitcore

splitdownloader

splitextension

splitinstaller

splitloader

splitreporter

splitrequester

编译过程的主要逻辑在buildSrc的模块中。

META-INF.gradle-plugins下一共注册了两个plugin,其中

QigsawAppBasePlugin:主要给app工程使用(apply plugin: 'com.iqiyi.qigsaw.application')

QigsawDynamicFeaturePlugin:主要给split工程使用(apply plugin: 'com.iqiyi.qigsaw.dynamicfeature')

1 QigsawAppBasePlugin

首先设置qigsawSplit配置参数,包括版本号,mappging文件,是否将插件上传cdn等。

1.1 Transform部分:

注册了SplitComponentTransform和SplitResourcesLoaderTransform两个Transform。

(1) 注册 [SplitComponentTransform]

主要负责记录所有动态库的四大组件。

  • 定义了一个文件:splitManifestParentDir,路径为:Qigsaw/app/build/intermediates/qigsaw/split-outputs/manifests/debug,里面存着动态库的manifest,然后遍历定义的三个动态库:dynamicFeatures = [':java', ':assets', ':native']从splitManifestParentDir中遍历各个动态库的manifest,把application和四大组件信息都存在内存
  • 然后在路径intermediates/transforms/processSplitComponent/debug/50/com/iqiyi/android/qigsaw/core/extension下通过ASM创建一个ComponentInfo类,并把除ContentProvider外的其他信息构建为常量属性:
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() 
    

为ContentProvider单独构建代理类:

Qigsaw/app/build/intermediates/transforms/processSplitComponent/debug/50/com/iqiyi/qigsaw/sample/java

public class JavaContentProvider_Decorated_java extends SplitContentProvider 
    public JavaContentProvider_Decorated_java() 
    

(2) 注册 [SplitResourcesLoaderTransform]

主要负责在各种组件注入loadResources方法

使用到了WaitableExecutor 提供并发编译能力,提高编译速度

  • 如果是基线包,则获取build.gradle中输入的baseContainerActivities参数(需要加载插件包resource资源的Activity数组),插件包则遍历mergedManifest,获取四大组件Set。
  • 将获取的参数通过SplitResourcesLoaderInjector进行处理,SplitResourcesLoaderInjector中包含:

1.SplitActivityWeaver

在Activity内所有getResouces方法中注入SplitInstallHelper.loadResources方法。

2.SplitServiceWeaver

在Services内的onCreate方法中注入SplitInstallHelper.loadResources方法,没有onCreate就注入一个。

3.SplitReceiverWeaver

在Receiver的onReceiver方法中注入SplitInstallHelper.loadResources方法。

(3) SplitInstallHelper类

主要是收集Split中四大组件和application中的Resources,并实现全局Resouces替换。目的是为了防止加载的动态split,出现资源找不到的问题。

通过SplitCompatResourcesLoader(实现接口SplitResourcesLoader)来实现功能。

SplitResourcesLoader:

public interface SplitResourcesLoader 
    void loadResources(@NonNull Context context, @NonNull Resources resources) throws Throwable;

    void loadResources(@NonNull Context context, @NonNull Resources preResources, @NonNull String splitApkPath) throws Throwable;
  1. 首先resources.getAssets()得到AssetManager,然后反射调用getApkAssets获取ApkAsset,再反射调用其getAssetPath获取以及加载的资源路径:assetPaths。
  2. 此时会去比较已经加载split的资源路径splitResPath,不包含则执行下一步 installSplitResDirs(final Context context, final Resources resources, final List<String> splitResPaths)
  1. api21以上,反射AssetManager的addAssetPath去添加路径。
  2. 21以下,处理比较复杂,需要首先获取所有已加载资源的资源路径resDirs,然后把split(插件)的所有资源路径splitResPaths插在头部,然后以此构建一个新的AssetManager,再用其构建一个新的Resouces,然后就是实现全局的Resouces刷新。
  1. 遍历当前Context和ActivityThread中的所有mActivities,设置新的Resouces。
  2. 获取ActivityThread和ResourcesManager中所有的Resouces,然后遍历替换之前老的Resouces。
  1. 获取ActivityThread中的LoadedApk(在mPackages和mResourcePackages中),替换其中老的mResources对象。

实现全局Resouces替换,在21以上只需要通过AssetManager去添加。在低版本需要把全局Activity,Resources,以及ActivityThread中的LoadedApk所有的Resources都替换成新的。

Transform部分总结:

主要是通过ASM记录了Application和四大组件信息(包括Manifest)到临时文件,然后在所有用到Resources的地方做了hook,防止出现运行时资源找不到的问题。

1.2 Task部分

在Transform部分工作完成之后,QigsawAppBasePlugin的Transform配置工作已经完成,在project.afterEvaluate中开始执行各种task逻辑。

首先初始化了许多task和file的临时变量,然后按以下执行顺序(执行qigsawAssembleDebug的顺序)开始执行Task:

(1) 单个split的 [copySplitManifest Task]

获取intermediates/merged_manifests/release/AndroidManifest.xml 并转移到

intermediates/qigsaw/split-outputs/manifests/debug 中 (存疑,怎么做的拆分?)

(2) 单个split的 [processSplitApk Task]

在assembleTask执行后执行,将split生成的apk:

Qigsaw/features/assets/build/outputs/apk/debug/assets-debug.apk

解压至临时文件区:

/Qigsaw/app/build/intermediates/qigsaw/split-outputs/unzip/debug/assets

然后再签名,打包,生成单个的apk包:

Qigsaw/app/build/intermediates/qigsaw/split-outputs/apks/debug/assets-master.apk

和一个json文件记录信息:

Qigsaw/app/build/intermediates/qigsaw/split-outputs/split-info/debug/assets.json


	"splitName": "assets",
	"builtIn": true,
	"onDemand": true,
	"version": "1.0@1",
	"minSdkVersion": 14,
	"dexNumber": 2,
	"apkData": [
		"abi": "master",
		"url": "assets://qigsaw/assets-master.zip",
		"md5": "bb164a5ba248cbbc05dc9f74a1d1ba41",
		"size": 12804
	]

(3) ExtractTargetFilesFromOldApk [extractTargetFilesFromOldApk(Debug/Release) Task]

如果在参数中设置有oldApk,则把它复制到路径intermediates/qigsaw/old-apk/target-files/Debug下。

(4) GenerateQigsawConfig [generate(Debug/Release)QigsawConfig Task]

构建QigsawConfig,比较简单不赘述。

(5) CreateSplitDetailsFileTask [qigsawAssemble(Debug/Release) Task]

重点来了,这个是文档中提到的(如何运行)构建base APK和split APK的打包Task,qigsawAssembleDebug(发布用Release)

  • 保存abi信息json到assets目录
  • 获取所有split的信息SplitDetails,存储至Qigsaw/app/build/intermediates/qigsaw/split-details/debug/qigsaw_1.0.0_1.0.0.json内

以及更新记录_update_record_.json。

  • 然后开始遍历所有的split,判断新的split名字和老的(上次编译的)是否一样,如果有变化则会标记更新了状态splitDetails.updateRecord.updateMode = SplitDetails.UpdateRecord.VERSION_CHANGED
  • 如果是没有更新(首次安装),则把当前的split的apk从

intermediates/qigsaw/split-outputs/apks/debug/java-master.apk

拷贝至打包的路径下,格式改为zip:

intermediates/merged_assets/debug/out/qigsaw/java-master.zip

Task部分总结:

主要是将split构建的apk,转移到本地的安装目录下,以备后面的安装过程使用。

2 QigsawDynamicFeaturePlugin

注册了SplitResourcesLoaderTransform (QigsawAppBasePlugin中有,刚分析过,不再赘述)与SplitLibraryLoaderTransform

2.1 SplitLibraryLoaderTransform

通过asm创建类名为"com.iqiyi.android.qigsaw.core.splitlib." + project.name + "SplitLibraryLoader"的类。

总结

编译过程主要是做了几件事情:

1.记录application和四大组建信息,resource插桩(热更新常见方式)

2.拷贝split的apk文件到指定目录,为后面的加载做准备

下一篇将会分析加载过程。

以上是关于Qigsaw 源码分析 编译过程的主要内容,如果未能解决你的问题,请参考以下文章

Android——Qigsaw 源码分析 编译过程

Android——Qigsaw 源码分析 初始化过程

Android——Qigsaw 源码分析 初始化过程

Android——Qigsaw 源码分析 初始化过程

Android——Qigsaw 源码分析 初始化过程

Android——Qigsaw 源码分析 安装过程