Qigsaw 源码分析 编译过程
Posted 化作孤岛的瓜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qigsaw 源码分析 编译过程相关的知识,希望对你有一定的参考价值。
本文主要是对Qigsaw的源码分析。Qigsaw是一种动态组件化技术,可以有效减少包size,进行热更新等调优功能,在实际应用开发中能带来不错的收益与启发。
基于2021/7/14版本分析(https://github.com/iqiyi/Qigsaw)
Qigsaw分为编译过程→安装过程→加载过程 三个阶段进行源码分析。
如何调试gradle:
Android如何创建Gradle插件开发工程及调试_xialonghua的专栏-CSDN博客_gradle插件开发
目录
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;
- 首先resources.getAssets()得到AssetManager,然后反射调用getApkAssets获取ApkAsset,再反射调用其getAssetPath获取以及加载的资源路径:assetPaths。
- 此时会去比较已经加载split的资源路径splitResPath,不包含则执行下一步 installSplitResDirs(final Context context, final Resources resources, final List<String> splitResPaths)
- api21以上,反射AssetManager的addAssetPath去添加路径。
- 21以下,处理比较复杂,需要首先获取所有已加载资源的资源路径resDirs,然后把split(插件)的所有资源路径splitResPaths插在头部,然后以此构建一个新的AssetManager,再用其构建一个新的Resouces,然后就是实现全局的Resouces刷新。
- 遍历当前Context和ActivityThread中的所有mActivities,设置新的Resouces。
- 获取ActivityThread和ResourcesManager中所有的Resouces,然后遍历替换之前老的Resouces。
- 获取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 源码分析 编译过程的主要内容,如果未能解决你的问题,请参考以下文章