Android App Bundle為Qigsaw
的前置依賴知識點目养。
Android App Bundle 是Android
新推出的一種官方發(fā)布格式.aab
序六,可讓您以更高效的方式開發(fā)和發(fā)布應(yīng)用。借助 Android App Bundle
戈擒,您可以更輕松地以更小的應(yīng)用提供優(yōu)質(zhì)的使用體驗蟹演,從而提升安裝成功率并減少卸載量阴颖。轉(zhuǎn)換過程輕松便捷。您無需重構(gòu)代碼即可開始獲享較小應(yīng)用的優(yōu)勢云头。改用這種格式后捐友,您可以體驗?zāi)K化應(yīng)用開發(fā)和可自定義功能交付,并從中受益(PS:必須依賴于GooglePlay
)溃槐。
qigsaw
基于AAB
實現(xiàn)匣砖,同時完全仿照AAB
提供的play core library
接口加載插件,開發(fā)查閱官方文檔即可開始開發(fā)昏滴。如果有國際化需求的公司可以在國內(nèi)版和國際版上無縫切換猴鲫。同時Qigsaw
實現(xiàn)0 hook
,僅有少量私有 API 訪問谣殊,保證其兼容性和穩(wěn)定性拂共。
本篇文章主要講述Qigsaw
相關(guān)的plugin
。
Qigsaw插件
主工程進(jìn)行進(jìn)行apply plugin: 'com.iqiyi.qigsaw.application'
插件的依賴姻几;
feature
工程進(jìn)行以下依賴:
apply plugin: 'com.android.dynamic-feature'
apply plugin: 'com.iqiyi.qigsaw.dynamicfeature'
gradle.properties
文件中配置QIGSAW_BUILD=true
宜狐,才會有feature
包的一些信息生成。
com.iqiyi.qigsaw.application
com.iqiyi.qigsaw.application.properties
文件內(nèi)容為:
implementation-class=com.iqiyi.qigsaw.buildtool.gradle.QigsawAppBasePlugin
QigsawAppBasePlugin
默認(rèn)會注冊一個 SplitComponentTransform
蛇捌,在開啟QIGSAW_BUILD=true
之后還會注冊SplitResourcesLoaderTransform
抚恒。通過 Transform
實現(xiàn)對插件內(nèi)容的AOP
。
QigsawAppBasePlugin
除過注冊兩個Transform
之外络拌,為主要的是處理插件和基礎(chǔ)包信息生成Qigsaw
產(chǎn)物柑爸。
com.iqiyi.qigsaw.dynamicfeature
com.iqiyi.qigsaw.dynamicfeature.properties
文件內(nèi)容為:
implementation-class=com.iqiyi.qigsaw.buildtool.gradle.QigsawDynamicFeaturePlugin
QigsawDynamicFeaturePlugin
在開啟QIGSAW_BUILD=true
之后會注冊SplitResourcesLoaderTransform
以及SplitLibraryLoaderTransform
實現(xiàn)對插件內(nèi)容的AOP
。
SplitResourcesLoaderTransform
主要是向Activity
盒音、Service
和Receiver
類中的getResources
注入SplitInstallHelper.loadResources(this, super.getResources())
表鳍。
interface SplitComponentWeaver {
/**
* 鏈接目標(biāo)
*/
String CLASS_WOVEN = "com/google/android/play/core/splitinstall/SplitInstallHelper"
/**
* 鏈接方法
*/
String METHOD_WOVEN = "loadResources"
byte[] weave(InputStream inputStream)
}
相關(guān)注入類為:
class SplitResourcesLoaderInjector {
WaitableExecutor waitableExecutor
/**
* 預(yù)埋的 Activity
*/
Set<String> activities
Set<String> services
Set<String> receivers
SplitActivityWeaver activityWeaver
SplitServiceWeaver serviceWeaver
SplitReceiverWeaver receiverWeaver
/**部分代碼省略**/
}
其中基礎(chǔ)包和插件的區(qū)別主要是注冊的目標(biāo)不同:
基礎(chǔ)包只是讀取
build.gradle
文件中的qigsawSplit.baseContainerActivities
配置的Activity
。
而插件需要讀取
AndroidManifest.xml
文件中的Activity
祥诽、Service
和Receiver
譬圣。
SplitInstallHelper.loadResources(this, super.getResources());
的作用是將所有插件資源路徑添加到AssetManager
中,這樣各個插件就可以訪問所有的資源雄坪,關(guān)鍵實現(xiàn)代碼如下:
static Method getAddAssetPathMethod() throws NoSuchMethodException {
if (addAssetPathMethod == null) {
addAssetPathMethod = HiddenApiReflection.findMethod(AssetManager.class, "addAssetPath", String.class);
}
return addAssetPathMethod;
}
SplitComponentTransform
該Transform
主要進(jìn)行了兩個操作 :
- 讀取各個插件
apk
的Manifest
文件厘熟,創(chuàng)建ComponentInfo
類并將將各個插件apk
的Application
,Activity
,Service
,Recevier
記錄在該類的字段中,字段名稱以工程名+組件類型命名维哈,值為各個插件apk
包含的組件绳姨,如過包含多個用逗號隔開。
//com.iqiyi.android.qigsaw.core.extension.ComponentInfo
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";
}
- 為每個
provider
創(chuàng)建代理類 類名為String providerClassName=providerName+"Decorated"+splitName
阔挠,其中providerName
為原始provider
類名飘庄,splitName
為插件apk
對應(yīng)的名稱,并且該類繼承SplitContentProvider
购撼。
public class JavaContentProvider_Decorated_java extends SplitContentProvider {}
為啥這么做呢?
因為在app
啟動時provider
的執(zhí)行時機(jī)是比較靠前的跪削,
Application->attachBaseContext ==>ContentProvider->onCreate ==>Application->onCreate ==>Activity->onCreate
在這個過程中我們的插件apk并沒有加載進(jìn)來,一定會報ClassNotFound
迂求。所以我們將插件apk
的provider
生成一個代理類碾盐,然后替換掉,如果插件沒有加載進(jìn)來揩局,代理類什么也不執(zhí)行就可以了毫玖。很好的解決了我們的問題。
SplitLibraryLoaderTransform
SplitLibraryLoaderTransform
類進(jìn)行的操作是向dynamic-feature
構(gòu)建apk
的過程中凌盯,創(chuàng)建以 "com.iqiyi.android.qigsaw.core.splitlib." + project.name + "SplitLibraryLoader"
的類付枫。
// com.iqiyi.android.qigsaw.core.splitlib.assetsSplitLibraryLoader
// com.iqiyi.android.qigsaw.core.splitlib.javaSplitLibraryLoader
// com.iqiyi.android.qigsaw.core.splitlib.nativeSplitLibraryLoader
package com.iqiyi.android.qigsaw.core.splitlib;
public class javaSplitLibraryLoader {
public void loadSplitLibrary(String str) {
System.loadLibrary(str);
}
}
這個類的作用是啥呢?
下面我們來解釋一下十气,你會發(fā)現(xiàn)很有趣的励背。
-
Qigsaw
是基于對于com.google.android.play.core
對外暴露的方法,進(jìn)行了自定義實現(xiàn)砸西。因為aab
目前只能對google play
上發(fā)布應(yīng)用起作用叶眉,所以開發(fā)者重新實現(xiàn)了一套com.google.android.play.core
包名的第三方庫,這樣就可以做到在國內(nèi)市場芹枷,與國外應(yīng)用市場無縫遷移衅疙。 -
Qigsaw
提供兩種加載方式加載插件apk
,單Classloader
和多Classloader
模式鸳慈,單Classloader
涉及私有api
訪問饱溢,而多Classloader
不涉及私有api
訪問。
該類的存在就是為了解決多Classloader
模式下的so加載問題
System.loadLibrary(str);
該方法會使用調(diào)用方的classloader從中獲取so信息并加載走芋。
//java.lang.System.java
@CallerSensitive
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
由于多Classloader
模式下绩郎,每個插件都要各自的Classloader
,so
與dex
都在各自的Classloader
中記錄潘鲫,所以在多Classloader
模式下, System.loadLibrary
應(yīng)由插件apk
各自的Classloader
調(diào)用肋杖。具體實現(xiàn)可參考SplitLibraryLoaderHelper
類溉仑。
//com.iqiyi.android.qigsaw.core.splitload.SplitLibraryLoaderHelper.java
private static boolean loadSplitLibrary0(ClassLoader classLoader, String splitName, String name) {
try {
Class<?> splitLoaderCl = classLoader.loadClass("com.iqiyi.android.qigsaw.core.splitlib." + splitName + "SplitLibraryLoader");
Object splitLoader = splitLoaderCl.newInstance();
Method method = HiddenApiReflection.findMethod(splitLoaderCl, "loadSplitLibrary", String.class);
method.invoke(splitLoader, name);
return true;
} catch (Throwable ignored) {
}
return false;
}
Qigsaw編譯解析
Qigsaw打包流程
copySplitManifestDebug
實現(xiàn)feature
包下生成的AndroidManifest.xml
文件的拷貝。
目標(biāo)文件和地址:featureName/build/intermediates/merged_manifests/debug/AndroidManifest.xml
状植。
拷貝后的地址:app/build/intermediates/qigsaw/split-outputs/manifests/debug
浊竟。
拷貝后的文件名:$featureName.xml
ProcessTaskDependenciesBetweenBaseAndSplitsWithQigsaw
觸發(fā)copySplitManifestDebug任務(wù),將feature包
生成的產(chǎn)物和數(shù)據(jù)輸出到qigsawProcessDebugManifest
任務(wù)中津畸。
extractTargetFilesFromOldApk
將app_debug.apk
解壓 將assets/
目錄下所有內(nèi)容釋放到app/build/intermediates/qigsaw/old-apk/target-files/xxx
中
qigsawProcessDebugManifest
SplitComponentTransform
創(chuàng)建的$ContentProviderName_Decorated_$featureName
繼承SplitContentProvider
代替原有的Provider
振定。
因為Provider
在應(yīng)用啟動的時候就需要加載,避免這個時候feature
包沒有下載下來肉拓,先加載一個代理的Provider
后频。
generateDebugQigsawConfig
生成以下文件:
@Keep
public final class QigsawConfig {
public static final String DEFAULT_SPLIT_INFO_VERSION = "1.0.0_1.0.0";
public static final String[] DYNAMIC_FEATURES = {"java", "assets", "native"};
public static final String QIGSAW_ID = "1.0.0_c40ab5d";
public static final boolean QIGSAW_MODE = Boolean.parseBoolean("true");
public static final String VERSION_NAME = "1.0.0";
}
QIGSAW_ID
回先獲取基礎(chǔ)包的id
,如果沒有那么為當(dāng)前的QigsawId
帝簇。
processSplitApkDebug
每個feature
都需要執(zhí)行的任務(wù)徘郭,分別處理自己的的apk
并生成對應(yīng)的json
文件。
將
feature
包的apk
文件解壓到app/build/intermediates/qigsaw/split-outputs/unzip/debug/$featureName
文件丧肴;遍歷解壓
apk
中的lib
文件目錄残揉,找到支持的ABI;-
如果有
lib
文件有so
文件,那么在該目錄生成一個AndroidManifest.xml
文件芋浮;將
lib
文件和生成的AndroidManifest.xml
壓縮為protoAbiApk
;利用
aapt2
工具將protoAbiApk
到binaryAbiApk
中;將
binaryAbiApk
進(jìn)行簽名生成app/build/intermediates/qigsaw/split-outputs/apks/debug/$feature-$abi.apk
;-
生成
SplitInfo.SplitApkData
數(shù)據(jù)抱环;{ "abi": "x86", "url": "assets://qigsaw/native-x86.zip", "md5": "03a29962b87c6ed2a7961b6dbe45f532", "size": 8539 }
遍歷解壓
apk
中除過lib之前的文件目錄,壓縮為$fearure-master-unsigned.apk
纸巷。簽名生成app/build/intermediates/qigsaw/split-outputs/apks/debug/$feature-master.apk
;-
生成
SplitInfo.SplitApkData
數(shù)據(jù)镇草;{ "abi": "master", "url": "assets://qigsaw/native-master.zip", "md5": "3b89066aeaf7d2c2a59b4f3a10fef345", "size": 12824 }
-
更具
lib
文件下的數(shù)據(jù)生成SplitInfo.SplitLibData
數(shù)據(jù);{ "abi": "arm64-v8a", "jniLibs": [ { "name": "libhello-jni.so", "md5": "2938d8b40825e82715422dbdba479e4f", "size": 5896 } ] }
-
最后生成每個
feature
的SplitInfo
數(shù)據(jù)瘤旨,寫入/app/build/intermediates/qigsaw/split-outputs/split-info/debug/$featureName.json
文件梯啤;public class SplitInfo implements Cloneable, GroovyObject { private String splitName;//feature包名稱 private boolean builtIn;//!onDemand||!releaseSplitApk(releaseSplitApk是gradle中配置項) private boolean onDemand;//取自AndroidManifest.xml中的onDemand private String applicationName;//feature應(yīng)用名 private String version;//feature包中的versionname@versioncode private int minSdkVersion;//feature最低版本 private int dexNumber;//feature包中的dex數(shù)量 private Set<String> dependencies;//feature包的依賴; private Set<String> workProcesses;//feature包AndroidManifest.xml中的Activity存哲、Service因宇、Receiver、provider配置的進(jìn)程祟偷; private List<SplitInfo.SplitApkData> apkData;//SplitInfo.SplitApkData數(shù)據(jù) private List<SplitInfo.SplitLibData> libData;//SplitInfo.SplitLibData數(shù)據(jù) }
qigsawAssembleDebug
- 將
build/intermediates/qigsaw/split-outputs/split-info/debug
中的每個feature
包生成的json
合并察滑; - 將合并之后的文件與基礎(chǔ)包中的
Qigsaw
配置文件進(jìn)行對比,生成新的增量Qigsaw
配置文件修肠;- 對比規(guī)則是
verisonName
相等的時候?qū)Ρ?code>split.version贺辰,有一個不同就表示有更新; - 如果有更新,那么
QigsawId
為基礎(chǔ)包的QigsawId
,并分析和修改split
信息饲化;- 修改
split
信息的時候莽鸭,相同的splitName
對比split.version
。如果相同那么split
使用基礎(chǔ)包的split
信息吃靠,如果不同那么該split
的builtIn=false
蒋川,onDemand=true
。并將有更新的split
做記錄(updatesplits
字段值)撩笆。此時updateMode
值為VERSION_CHANGED=1; - 沒有任何修改缸浦,那么
updateMode
值為VERSION_NO_CHANGED=2夕冲; - 如果沒有基礎(chǔ)包,那么
updateMode
值為DEFAULT=0裂逐;
- 修改
- 對比規(guī)則是
- 分別判斷如果
feature
包的builtIn
是false歹鱼;- 判斷是否有上傳服務(wù),如有有那么上傳
feature
包卜高。上傳成功后將對應(yīng)的url
地址修改為可下載的http
地址弥姻。如果地址為空,或者不是http
開頭會跑異常掺涛。 - 如果沒有實現(xiàn)上傳服務(wù)那么
builtIn
置為true庭敦;
- 判斷是否有上傳服務(wù),如有有那么上傳
- 格式化
split
內(nèi)容,寫到build/intermediates/qigsaw/split-details/debug
文件目錄下薪缆。 - 將
updateMode
值寫到build/intermediates/qigsaw/split-details/debug/_update_record_.json
文件秧廉。 - 如果
updateMode
值為VERSION_NO_CHANGED,那么將intermediates/qigsaw/old-apk/target-files/debug/assets/qigsaw/qigsaw_*.json
文件拷貝到app/build/intermediates/merged_assets/debug/out/qigsaw/qigsaw_*.json
;- 否則將
app/build/intermediates/qigsaw/split-details/debug/qigsaw_*.json
文件拷貝到app/build/intermediates/merged_assets/debug/out/qigsaw/qigsaw_*.json
;
- 否則將
- 向
app/build/intermediates/qigsaw/split-details/debug/base.app.cpu.abilist.properties
寫入支持的abi
拣帽,并將其拷貝到app/build/intermediates/merged_assets/debug/out/
下面疼电; - 遍歷
feature
生成的splitinfo
信息,如果builtIn
是true;- 如果
updateMode
值為DEFAULT=0减拭,將將app/build/intermediates/qigsaw/split-outputs/apks/debug/*.apk
拷貝到app/build/intermediates/merged_assets/debug/out/qigsaw/*.zip
蔽豺; - 如果
updateMode
值為DEFAULT!=0,判斷該feature
是否是在updateSplits
中;- 如果是那么將
app/build/intermediates/qigsaw/split-outputs/apks/debug/*.apk
拷貝到app/build/intermediates/merged_assets/debug/out/qigsaw/*.zip
拧粪; - 如果不是將
app/build/intermediates/qigsaw/old-apk/target-files/debug/assets/qigsaw/*.zip
拷貝到app/build/intermediates/merged_assets/debug/out/qigsaw/*.zip
修陡;
- 如果是那么將
- 如果
產(chǎn)物
Qigsaw
配置文件
{
"qigsawId": "1.0.0_c40ab5d",
"appVersionName": "1.0.0",
"updateSplits": [
"java"
],
"splits": [
{
"splitName": "java",
"builtIn": true,
"onDemand": false,
"applicationName": "com.iqiyi.qigsaw.sample.java.JavaSampleApplication",
"version": "1.1@1",
"minSdkVersion": 14,
"dexNumber": 2,
"workProcesses": [
""
],
"apkData": [
{
"abi": "master",
"url": "assets://qigsaw/java-master.zip",
"md5": "658bc419a9d3c7812a36e61f6c5be4c4",
"size": 12822
}
]
}
{
"splitName": "native",
"builtIn": true,
"onDemand": true,
"version": "1.0@1",
"minSdkVersion": 14,
"dexNumber": 2,
"apkData": [
{
"abi": "arm64-v8a",
"url": "assets://qigsaw/native-arm64-v8a.zip",
"md5": "b01ad63db38a4ec5fad3284c573a02d3",
"size": 8545
},
{
"abi": "master",
"url": "assets://qigsaw/native-master.zip",
"md5": "3c41745a16a31e967cde8247009463f1",
"size": 12824
}
],
"libData": [
{
"abi": "arm64-v8a",
"jniLibs": [
{
"name": "libhello-jni.so",
"md5": "2938d8b40825e82715422dbdba479e4f",
"size": 5896
}
]
}
]
}
]
}
Qigsaw
加載的壓縮包
下期研究知識點
- 混淆相關(guān)使用操作;
-
Tinker
熱修改相關(guān)使用操作既们;