Qigsaw:https://github.com/iqiyi/Qigsaw, Qigsaw 是爱奇艺自主研发的动态化框架,其核心优势如下:
- 利用 Android App Bundle 开发套件,极速开发体验;
- 支持 Android App Bundle 所有功能特性,"山寨" Play Core Library 公开接口实现,开发者阅读官方文档即可愉快开发;
- 任何进程均可动态加载插件,支持 Android 四大组件动态加载;
- 如果您的应用有出海需求,可无缝切换至 Android App Bundle 方案;
- 仅一处 Hook,少量私有 API 访问,保证框架稳定性。Android 动态化方案,在国内已蓬勃发展数年之久,其核心目的是减少应用包体积,提升应用安装率。
简而言之,Qigsaw 可以让我们在国内使用 Android App Bundle,并且可以无缝切换到 Google Play.
Qigsaw 打包流程图:
由于项目计划使用 Qigsaw,并做一些二次开发,所以需要先深入理解 Qigsaw 的机制和原理。目前准备从 2 个方面来着手研究:
- BuildSrc plugin
- Split native code
本篇文章主要是 buildSrc 插件实现的分析。
目录:
- Dynamic feature plugin
- App plugin
1. Dynamic feature plugin
Qigsaw 提供了两个插件,分别作用于 App 和 Dynamic feature, 先来看看 Dynamic feature 部分。
需要注意几个点:
1.1 SplitInstallHelper.loadResources()
主要为了解决 feature apk 的资源访问问题,具体的实现原理将会在 split native code 篇进行分析,处理完的 Activity 代码如下:
package com.iqiyi.qigsaw.sample.java;
import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import com.google.android.play.core.splitinstall.SplitInstallHelper;
public class JavaSampleActivity extends Activity {
public JavaSampleActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2114060288);
}
public Resources getResources() {
SplitInstallHelper.loadResources(this, super.getResources());
return super.getResources();
}
}
1.2 {$projectName}SplitLibraryLoader
会创建 {$projectName}SplitLibraryLoader 类,如工程名为 java,则会创建 javaSplitLibraryLoader 类:
package com.iqiyi.android.qigsaw.core.splitlib;
public class javaSplitLibraryLoader {
public javaSplitLibraryLoader() {
}
public void loadSplitLibrary(String var1) {
System.loadLibrary(var1);
}
}
这个类的作用是什么呢?首先我们要知道两点:
- Qigsaw 是基于对于 com.google.android.play.core 对外暴露的接口,进行了自定义实现。因为 AAB 目前只能对 Google play 上发布应用起作用,所以开发者重新实现了一套 com.google.android.play.core 包名的第三方库,这样就可以做到在国内市场,与国外应用市场无缝迁移。
- Qigsaw 提供两种加载方式加载插件 apk,单 classloader 和多 calssloader 模式,单 classloader 涉及私有 api 访问,而多classloader 不涉及私有 api 访问。
这个类的存在就是为了解决多 classloader 模式下的 so 加载问题:
// 该方法会使用调用方的 classloader 从中获取 so 信息并加载
System.loadLibrary(str);
2. App plugin
用于处理 app 模块,整体的流程如下:
2.1 register ComponentInfoTransform
该 transform 作用是通过每个 Dynamic Feature 库的 merged manifest 文件收集所有的组件信息,包括:Activity, Service, BroadcastReceiver, ContentProvider 以及 Application. 然后通过收集的组件列表,创建一个名为 ComponentInfo 的类,如下:
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() {
}
}
2.2 qigsawProcessDebugManifest
修改 merged manifest 和 bundle manifest 文件,主要是处理 ContentProvider 结点。
为每个 ContentProvider 创建名为 providerName + "_Decorated_" + splitName 的代理类,其中 providerName 为原始 provider 类名,splitName 为插件 apk 对应的名称,并且该类继承 SplitContentProvider。如下:
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() {
}
}
之后将 base apk 的 Manifest.xml 文件中该 provider 替换掉,如下所示:
<provider android:name="com.iqiyi.qigsaw.sample.java.JavaContentProvider_Decorated_java" android:authorities="java.feature"
android:enabled="true"
android:exported="false"
android:splitName="java"/>
这么做的原因是在 app 启动时 provider 的执行时机是比较靠前的:
Application->attachBaseContext ==>ContentProvider->onCreate ==>Application->onCreate ==>Activity->onCreate
在这个过程中插件 apk 并没有加载进来,一定会报 ClassNotFound。所以将插件 apk 的 provider 生成一个代理类,然后替换掉,如果插件没有加载进来,代理类什么也不执行即可。
2.3 qigsawProcessDebugOldApk
在 app module 中配置 qigsawSplit 时,可以看到 oldApk 这个属性,主要用于更新 split 版本:
def qigsawPath = file("qigsaw/")
qigsawSplit {
/**
* optional,default 'null'
* if you want to update split version, oldApk must be set.
*/
oldApk = "${qigsawPath}/app.apk"
...
}
qigsawProcessDebugOldApk 这个 task 做的事情如下:
(outputs 目录为:/Qigsaw/app/build/intermediates/qigsaw/old_apk/debug/)
2.4 generateDebugQigsawConfig
generateDebugQigsawConfig 依赖于 generateBuildConfigTask, generateDebugQigsawConfig 作用是生成 qigsaw 的 java 配置文件,方便 native 代码中快速获取相关配置,具体流程如下:
QigsawConfig.java 内容如下:
package com.iqiyi.qigsaw.sample;
public final class QigsawConfig {
public static final boolean QIGSAW_MODE = Boolean.parseBoolean("true");
public static final String QIGSAW_ID = "1.0.0_d9f55d4";
public static final String VERSION_NAME = "1.0.0";
public static final String DEFAULT_SPLIT_INFO_VERSION = "1.0.0_1.0.0";
public static final String[] DYNAMIC_FEATURES = new String[]{"java", "assets", "native"};
public QigsawConfig() {
}
}
2.5 qigsawAssembleDebug
qigsawAssembleDebug 依赖 assembleTask,主要是处理 split apk 和配置文件内置问题。
生成的 apk 截图:
qigsaw json 配置文件,该⽂件记录了插件信息,包括下载地址:
{
"qigsawId": "9.11.0_0593ffca2",
"appVersionName": "9.11.0",
"builtInUrlPrefix": "native://",
"splits": [{
"splitName": "WubaHuangyeFeature",
"url": "http://10.252.209.45:3007/file/WubaHuangyeFeature.apk",
"builtIn": false,
"onDemand": true,
"size": 1914057,
"applicationName": "com.wuba.huangyefeature.HuangyeApp",
"version": "1.0@1",
"md5": "89295a009015a1c400cd2cf4e0696e5a",
"minSdkVersion": 19,
"dexNumber": 3
}, {
"splitName": "WubaHouseFeature",
"url": "http://10.252.209.45:3007/file/WubaHouseFeature.apk",
"builtIn": false,
"onDemand": true,
"size": 7866002,
"applicationName": "com.wuba.housefeature.HouseApp",
"version": "1.0@1",
"md5": "124eaaca8b2489837a4b88a1c5e225fd",
"minSdkVersion": 19,
"dexNumber": 3
}, {
"splitName": "WubaPincheFeature",
"url": "native://libsplit_WubaPincheFeature.so",
"builtIn": true,
"onDemand": false,
"size": 558633,
"version": "1.0@1",
"md5": "1c338962f8a89edc48f2c1ac95b71649",
"minSdkVersion": 19,
"dexNumber": 3
}, {
"splitName": "WubaJobFeature",
"url": "http://10.252.209.45:3007/file/WubaJobFeature.apk",
"builtIn": false,
"onDemand": true,
"size": 5016932,
"applicationName": "com.wuba.jobfeature.JobApp",
"version": "1.0@1",
"md5": "970d88c082e7fc9647978a85b3657542",
"minSdkVersion": 19,
"dexNumber": 3,
"nativeLibraries": [{
"abi": "armeabi-v7a",
"jniLibs": [{
"name": "libaesutil.so",
"md5": "c26a5631ebad7ec47d19c837328526e4",
"size": 17960
}]
}]
}, {
"splitName": "WubaCarFeature",
"url": "http://10.252.209.45:3007/file/WubaCarFeature.apk",
"builtIn": false,
"onDemand": true,
"size": 5415268,
"applicationName": "com.wuba.carfeature.CarApp",
"version": "1.0@1",
"md5": "18b57baf0dae6745e8de3b2b27f6a937",
"minSdkVersion": 19,
"dexNumber": 3
}, {
"splitName": "WubaFinanceFeature",
"url": "http://10.252.209.45:3007/file/WubaFinanceFeature.apk",
"builtIn": false,
"onDemand": true,
"size": 22210,
"version": "1.0@1",
"md5": "d58650eb0b50e2782a5b2370f823859f",
"minSdkVersion": 19,
"dexNumber": 3
}],
"abiFilters": ["armeabi-v7a"]
}
App plugin 完整逻辑图如下: