貨拉拉 Android 動態(tài)資源管理系統(tǒng)原理與實踐(下)

點擊上方藍字關注我,知識會給你力量

so資源動態(tài)化方案
so資源打包問題
在打包so資源的過程中,我們遇到了如下問題。
如何移除apk中的so文件,并將他們收集起來? 如何將多個so文件壓縮打包,并生成對應的信息? 如何保證第三方sdk缺少so文件時,不崩潰? so資源打包解決方案
移除并收集apk中的so文件
看到移除 so文件可能有些同學會問,這不是只要在as中刪除libs目錄就搞定了么?這樣會有幾個問題
對于多個module的工程,我們需要逐個刪除每個module下的libs目錄,麻煩而且容易出錯。 對于三方aar包中的so文件,我們就沒法刪除了。 so文件變化需要人工維護,容易出錯。
出于以上考慮,我們認為,在編譯時期,自動刪除并收集so文件是最優(yōu)解,那么在編譯時期進行以上操作呢?我們注意到as在進行build時,會有大量的系統(tǒng)提供的task在運行,那么這些系統(tǒng)task是否就完成了編譯并收集各個地方的so文件,并把他們打包進apk的任務呢?

看一眼這幅超級復雜的apk構建流程圖,嗯,可以看到,系統(tǒng)確實會在apkBuilder構建前,將本地的c/c++文件編譯成so庫,并將第三方的so庫一起打包到apk中,我們需要尋找的就是收集所有so庫的系統(tǒng)Task

通過查找資料,我們發(fā)現(xiàn),確實有2個系統(tǒng)task會用來處理合并so庫并且刪除debug符號(注意,task名稱可能與此處不完全相同)。
| Task名稱 | 實現(xiàn)類 | 作用 | 結果保存目錄 |
|---|---|---|---|
| mergeDebugNativeLibs | MergeNativeLibsTask | 合并所有依賴的 native 庫 | intermediates/merged_native_libs |
| stripDebugDebugSymbols | StripDebugSymbolsTask | 從 Native 庫中移除 Debug 符號 | intermediates/stripped_native_libs |
一般來說,應該在stripSymbols結束后去剔除 stripped_native_libs 目錄下的文件。 但是剔除debug符號操作,可能導致不同as版本得到的so文件md5碼不相同。 因此,我們采用了可配置方案,可以由用戶配置決定,在MergeNativeLibsTask或者stripDebugDebugSymbols后,執(zhí)行刪除輸出文件夾中so文件操作。 第三方 so 一般都是 Release 編譯出來的,不進行strip影響也不大。而我們自己的so文件,則strip操作可能會對so體積造成較大影響。 下面我們以在MergeNativeLibsTask之后,執(zhí)行刪除輸出文件夾中so文件的方式,進行講解。
由于我們有多個gradle task需要執(zhí)行,因此我們創(chuàng)建了一個名為dynamic_plugin的android plugin工程,內(nèi)部包含了多個gradle task。關于as中新建插件的方法,請自行搜索其他博客,本文因為篇幅問題,不進行講解。
在我們的dynamic_plugin插件內(nèi)部,我們新建一個名為DeleteAndCopySo的gradle task并將它插入到系統(tǒng)的merge和strip之間,利用該Task完成刪除merged_native_libs目錄下對應so文件,并將其拷貝到我們指定的新目錄下。這樣apk打包時,就不會包含動態(tài)化的so文件了
//獲取系統(tǒng)的mergeTask
Task mergeNativeTask = TaskUtil.getMergeNativeTask(project);
//獲取系統(tǒng)的skipTask
Task stripTask = TaskUtil.getStripSymbol(project);
//創(chuàng)建我們的DeleteAndCopySo task
Task deleteTask = project.getTasks().create(PluginConst.Task.DELETE_SO);
deleteTask.doLast(new Action<Task>() {
@Override
public void execute(Task task) {
deleteAndCopySo(project, param);
}
});
//將我們的Task插入到merge和strip之間
stripTask.dependsOn(deleteTask);
deleteTask.dependsOn(mergeNativeTask);
如何將多個so文件壓縮打包,并生成對應的信息?
上一步中,我們已經(jīng)將so文件從系統(tǒng)apk構建流程中刪除,并且拷貝到了指定目錄下。那么現(xiàn)在我們應該做什么呢?
將so文件打包成.zip壓縮包。 生成該資源對應的實體類DynamicPkgInfo。包括文件id,文件名稱,文件類型,版本號,下載地址等基本信息,以及文件md5,文件長度等校驗信息。以及壓縮包下的所有子文件及文件夾相關信息。 將該zip文件上傳到服務器,以方便下載和使用。
對于上述這些步驟,在我們的貨拉拉動態(tài)管理系統(tǒng)初始版本中,我們采用了自己打zip包,自己寫java代碼來生成資源信息的方式。
但是在后來的使用過程中,我們發(fā)現(xiàn),手動進行這些步驟,很繁瑣且容易出錯,我們需要有一種自動化的方式進行上述過程。
我們在dynamic_plugin插件內(nèi)部,再新增一個ZipSoTask來進行壓縮so文件夾,以及生成資源信息常量的操作。該task在DeleteAndCopySo之后,stripe系統(tǒng)task之前執(zhí)行。
//執(zhí)行將so文件夾壓縮成.zip操作
List<DynamicUtil.ZipInfo> zips = DynamicUtil.zipFolder(new File(param.getmInputSo()),outDir);
//根據(jù)so文件和zip壓縮包信息,生成md5,length等校驗信息并存儲
DynamicUtil.createPkgDatas(mPkgList,zips,PluginConst.Type.SO);
//根據(jù)資源信息類生產(chǎn)java文件
param.getmFileCreate().createFile(mPkgList,param);
前2步,壓縮so文件,和根據(jù)so文件,zip文件生成校驗信息并存儲比較簡單,就不詳細說了。 第3步,根據(jù)前面的信息,直接生產(chǎn)java文件,我們使用了第三方的開源庫javapoet。 JavaPoet 是 Square 公司推出的開源 Java代碼生成框架,提供接口生成 Java 源文件。這個框架功能非常有用,我們可以很方便的使用它根據(jù)注解、數(shù)據(jù)庫模式、協(xié)議格式等來對應生成代碼。通過這種自動化生成代碼的方式,可以讓我們用更加簡潔優(yōu)雅的方式要替代繁瑣冗雜的重復工作。 大致的生產(chǎn)代碼如下,首先生成一個DynamicResConst類,之后遍歷zip壓縮資源列表,為列表中的每一個資源,生成一個static final的常量,表示每個資源,最后生成java文件。
//創(chuàng)建DynamicResConst類,用來存儲資源實體常量
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder( "DynamicResConst" )
.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
//遍歷資源列表,生成對應實體類DynamicPkgInfo
for (DynamicPkgInfo pkg : pkgs) {
FieldSpec fsc = createField(pkg);
typeBuilder.addField(fsc);
}
//插件java文件,并寫入
JavaFile javaFile = JavaFile.builder(param.getmCreateJavaPkgName(), typeBuilder.build()).build();
try {
javaFile.writeTo(new File(param.getmOutputPath()));
} catch (Exception e) {
}
至于最后一步,將so壓縮包上傳到服務器,我們在配置文件中提供了一個上傳方法,不過默認實現(xiàn)為空,用戶可以手動上傳也可以修改默認方法實現(xiàn)自動上傳。自動生成的資源文件中,版本號需要手動修改控制,下載地址手動上傳的話,也需要手動修改。
保證第三方sdk在缺少so文件時,不崩潰
很多三方sdk都要求在應用啟動時,進行初始化,一個使用so庫的類的典型類代碼如下:
public class ThirdLib{
//靜態(tài)方法加載so庫
static{
System.loadLibrary("third");
}
//native方法示例
public native void testNative();
//java方法示例
public void test();
//......其他內(nèi)容省略
}
如果此時so庫沒有被加載好,直接使用ThirdLib類,則會執(zhí)行static代碼段中的System.loadLibrary方法,導致UnsatisfiedLinkError的錯誤,造成App崩潰。由于我們無法直接修改第三方sdk的源碼,因此我們只能采用動態(tài)字節(jié)碼技術,替換掉System.loadLibrary方法了。 我們采用android的transform加asm技術,動態(tài)的將System.loadLibrary替換成我們自己的SoLoadUtil中的loadLibrary方法。 Gradle Transform 是 Android 官方提供給開發(fā)者在項目構建階段,即由 .class 到 .dex 轉(zhuǎn)換期間修改 .class 文件的一套 API, 無論是class還是jar都可以控制。 ASM是一種通用Java字節(jié)碼操作和分析框架。它可以用于修改現(xiàn)有的class文件或動態(tài)生成class文件。

具體執(zhí)行替換的代碼如下,在Asm框架中的MethodVisitor類中,重寫visitMethodInsn方法,判斷該方法的擁有者,名稱和參數(shù)列表和System.loadLibrary對應,則我們將他替換為我們的SoLoadUtil.loadLibrary方法
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if(TextUtil.equals(owner, PluginConst.SYSTEM_CLASS) &&
TextUtil.equals(name, PluginConst.LOAD_LIBRARY_METHOD) &&
TextUtil.equals(descriptor, PluginConst.LOAD_LIBRARY_DESC)){
owner = "com/xxx/xxx/dynamicres/util/SoLoadUtil" ;
mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, descriptor, false);
return;
}
mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
替換后的方法主要邏輯為,使用第三方庫Relinker替代System.loadLibrary方法進行so文件加載,并且catch住加載異常,來防止應用直接奔潰,并且在加載so庫異常時,將該庫的名稱保存下來,在我們的so包被正常下發(fā)加載后,再次調(diào)用本方法,將so庫load到系統(tǒng)中。
protected void realSoLoad(Context c, String libName) {
try {
ReLinker. recursively ().loadLibrary(c, libName);
removeFormWaitList(libName);
} catch (Throwable t) {
addToWaitList(libName);
}
}
對于so庫沒有加載完成,直接使用ThirdLib類導致System.loadLibrary方法被調(diào)用,導致的應用崩潰問題,我們已經(jīng)解決了。 而對于直接調(diào)用ThirdLib類的testNative方法,導致的應用崩潰問題,則無法解決。因此需要看情況決定是否能夠接受該種崩潰,以及是否將引發(fā)該問題的so庫進行動態(tài)化。
我們只需要在工程的主Application中,直接調(diào)用loadSo方法,對so動態(tài)資源進行加載。加載完成后,so庫就能正常使用了。
public void loadSo(DynamicSoInfo soInfo, ILoadSoListener listener) {
if (soInfo == null) {
return;
}
//根據(jù)本機abi,獲取適合的動態(tài)資源實體類DynamicPkgInfo
DynamicPkgInfo pkg = soInfo.getPkgInfo(Build.SUPPORTED_ABIS);
if (pkg == null) {
return;
}
//如果該so資源,已經(jīng)被加載緩存過了,直接listener的成功回調(diào),并返回
if (isLoadAndDispatchSo(pkg, listener)) {
return;
}
//開啟資源加載,和普通資源流程一致
DynamicResManager manager = DynamicResManager.getInstance();
manager.load(pkg, new DefaultLoadResListener() {
@Override
public void onSucceed(LoadResInfo info) {
super.onSucceed(info);
//so成功下載校驗后,執(zhí)行加載邏輯
handleLoadSoSucceed(pkg, info, listener);
}
});
}
so資源加載和應用問題
在so資源的加載和應用過程中,我們發(fā)現(xiàn)了如下問題
如何判斷系統(tǒng)需要哪些so文件,并按需正確加載? 如何下載so文件,并保證它的正確性? 如何將下載的動態(tài)so文件,正確應用到系統(tǒng)中? so資源加載和應用解決方案
如何判斷系統(tǒng)需要哪些so文件,并正確下載安裝?
我們把arm64-v8a,armeabi-v7a等abi分開打包,上傳到服務器。使用時,本地判斷abi支持,下載對應的abi包。這樣做的優(yōu)點是節(jié)省流量和下載后占據(jù)的空間。
至于判斷系統(tǒng)需要哪些abi的so包,并按需正確應用,則比較簡單,讀取系統(tǒng)的SUPPORTED_ABIS常量,這里包含了系統(tǒng)支持的abi列表,而排在前面的表示優(yōu)先級更高。我們只要遍歷它,然后查找我們的動態(tài)資源包是否有匹配,就達到了正確加載的目標。
private Map<String,DynamicPkgInfo> mSoInfos;
public DynamicPkgInfo getPkgInfo(){
//獲取本地系統(tǒng)支持的abi列表
String[] supportAbis = Build.SUPPORTED_ABIS;
if(supportAbis==null || supportAbis.length== 0 ){
return null;
}
//遍歷abi支持列表
for(String abi : supportAbis){
//從so動態(tài)資源中,查找對應的abi信息
DynamicPkgInfo pkg = mSoInfos.get(abi);
//找到則直接返回該信息
if(pkg != null){
return pkg;
}
}
return null;
}
如何下載so文件,并保證它的正確性?
復用通過資源加載流程即可。
如何將下載的動態(tài)so文件,正確應用到系統(tǒng)中?
這里需要首先了解一下,系統(tǒng)加載so庫的工作流程,當我們調(diào)用 System#loadLibrary("xxx" ) 后,Android Framework 都干了些了啥?Android 的 so 加載機制,大致可以分為以下四個環(huán)節(jié)。
安裝 APK 包的時候,PMS 根據(jù)當前設備的 abi 信息,從 APK 包里拷貝相應的 so 文件。 啟動 APP 的時候, Android Framework 創(chuàng)建應用的 ClassLoader 實例,并將當前應用相關的所有 so 文件所在目錄注入到當前 ClassLoader 相關字段。 調(diào)用 System.loadLibrary("xxx"), framework 從當前上下文 ClassLoader 實例(或者用戶指定)的目錄數(shù)組里查找并加載名為 libxxx.so 的文件。 調(diào)用 so 相關 JNI 方法。
而我們這里,由于so文件不存在于apk當中,而是需要動態(tài)下載,所以我們顯然不能直接使用系統(tǒng)的System.loadLibrary方法加載so文件。
而動態(tài)加載so的方法,在熱修復和插件化框架中,已經(jīng)比較成熟了,我們參考了市面上的開源框架后,選擇了騰訊的Tinker框架的加載方案,即使用反射classloader 將 so 包的路徑寫入 nativeLibraryPathElements 數(shù)組最前面,其流程圖和解釋如下圖所示 。注意,此方法不同的android版本將有不同的實現(xiàn)。下面示例代碼基于android9.0版本。
private static void install(ClassLoader classLoader, File soFolder) throws Throwable {
Field pathListField = findField(classLoader, "pathList" );
Object dexPathList = pathListField.get(classLoader);
Field nativeLibraryDirectories = findField(dexPathList, "nativeLibraryDirectories" );
List<File> libDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
libDirs.add(0, soFolder);
Field systemNativeLibraryDirectories =
findField(dexPathList, "systemNativeLibraryDirectories" );
List<File> systemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
Method makePathElements =
findMethod(dexPathList, "makePathElements" , List.class);
libDirs.addAll(systemLibDirs);
Object[] elements = (Object[]) makePathElements.
invoke(dexPathList, libDirs);
Field nativeLibraryPathElements = findField(dexPathList, "nativeLibraryPathElements" );
nativeLibraryPathElements.setAccessible(true);
nativeLibraryPathElements.set(dexPathList, elements);
}
pathList變量:DexPathList類的實例。 nativeLibraryDirectories列表:包含了本App自帶so文件的查找路徑(如data/app/包名/lib/arm64) systemNativeLibraryDirectories列表:包含系統(tǒng)so文件查找路徑(如system/lib64) makePathElements:系統(tǒng)使用此方法,為所有so文件,生成對應的 NativeLibraryElement對象 nativeLibraryPathElements數(shù)組:系統(tǒng)用來存儲所有的so文件路徑
當外界調(diào)用System.loadLibrary方法時,系統(tǒng)最終會調(diào)用到DexPathList類的findLibrary方法,該方法會在nativeLibraryPathElements數(shù)組中查找對應的路徑,我們將自己的so加入到nativeLibraryPathElements最前面,由此達到動態(tài)加入so的目標。
so資源動態(tài)化的tips
為何要使用Relinker加載So文件
假如我們有2個so文件,libA.so 和 libB.so,libA依賴libB,則當我們調(diào)用System.loadLibrary("libA") 的時候,android framework 會通過上面提到的調(diào)用鏈最終通過 dlopen 加載 libA.so 文件,并接著通過其依賴信息,自動使用 dlopen 加載 libB.so。 在 Android N 以前,只要將 libA.so 和 libB.so 所在的文件目錄路徑都注入到當前 ClassLoader 的 nativeLibraryPathElements 里,則在加載 so 插件的時候,這兩個文件都能正常被找到。 從 N 開始,libA.so 能正常加載,而 libB.so 會出現(xiàn)加載失敗錯誤。 因為Android Native 用來鏈接 so 庫的 Linker.cpp dlopen 函數(shù) 的具體實現(xiàn)變化比較大(主要是引入了 Namespace 機制):以往的實現(xiàn)里,Linker 會在 ClassLoder 實例的 nativeLibraryPathElements 里的所有路徑查找相應的 so 文件。 更新之后,Linker 里檢索的路徑在創(chuàng)建 ClassLoader 實例后就被系統(tǒng)通過 Namespace 機制綁定了,當我們注入新的路徑之后,雖然 ClassLoader 里的路徑增加了,但是 Linker 里 Namespace 已經(jīng)綁定的路徑集合并沒有同步更新,所以出現(xiàn)了 libA.so 文件能找到,而 libB.so 找不到的情況。 至于 Namespace 機制的工作原理了,可以簡單認為是一個以 ClassLoader 實例 HashCode 為 Key 的 Map,Native 層通過 ClassLoader 實例獲取 Map 里存放的 Value(也就是 so 文件路徑集合)。
解決該問題有如下幾種思路:
自定義 System.loadLibrary,加載 SO 前,先解析 SO 的依賴信息,再遞歸加載其依賴的 SO 文件,這是開源庫soLoader的解決方案。 自定義 Linker,完全自己控制 SO 文件的檢索邏輯 ,這是開源庫Relinder的解決方案。 替換 ClassLoader 。
本著不重復造輪子的原則,項目中使用了Relinker開源庫,用來加載so文件。
so庫依賴分析工具
想要把 so 動態(tài)化技術應用到 APK 的瘦身項目中來,除了分析哪些 so 文件體積占比比較大之外,最好的做法是將其依賴的所有 so 文件一定挪到插件包里。怎么了解 APK 里所有 so 文件具體的依賴信息呢?這里推薦一款 Google 開源的 APK 解析工具android-classyshark,除了提供分析 APK dex/so 依賴信息之外,它還提供了 GUI 可視化界面,非常適合快速上手。

so動態(tài)化流程
so資源應用流程
獲取系統(tǒng)支持abi列表,根據(jù)該列表,找到合適的so動態(tài)資源實體類。 如果該資源已經(jīng)被加載緩存,則直接回調(diào)加載成功。 否則,開始資源通用加載流程,并異步等待資源加載成功(流程見第5章)。 再次判斷下載校驗后的資源,是否支持本機abi。 將so包路徑加入DexPathList的數(shù)組頭部。 遍歷等待加載so列表,嘗試加載所有so文件,并將成功加載的so文件,移除該列表。 將資源id和本地路徑加入緩存,防止so被重復加載。 回調(diào)加載完成監(jiān)聽器。

SoLoadUtil.loadLibrary方法流程
從上一章我們知道,我們會使用transform api加asm框架,將系統(tǒng)的System.loadLibrary方法替換成我們的SoloadUtil.loadLibrary方法。我們替換系統(tǒng)方法的目的。一個是為了保證so庫不存在時,程序不崩潰,另外一個就是so庫下載校驗完成后,能自動完成之前失敗的加載,為此,我們設計了如下流程。
其他方法調(diào)用到我們的SoloadUtil時,我們判斷我們的加載系統(tǒng)是否初始化完成 已完成,則調(diào)用Relinkder庫嘗試加載so文件,未完成則將該so庫加入待加載隊列中。 如果Relinker加載so文件成功,我們從待加載隊列中移除so,并且完成本次加載。 否則我們依然將so文件加入待加載隊列中。 根據(jù)上面的so加載流程,當so動態(tài)資源真正下載校驗完成后,我們會遍歷待加載隊列,并完成所有之前未成功的so庫加載。

dynamic_plugin插件流程
整體流程
前面我們已經(jīng)分析了通用資源加載,內(nèi)置資源應用,完成了動態(tài)資源管理系統(tǒng)的主要部分。只剩下資源打包部分了,而所有資源的打包操作,都由dyanmic_plugin插件來完成。為了完成打包功能,我們決定在這個dynamic_plugin插件內(nèi)部,新建3個Task。
Hook System.loadLibrary方法的TransformTask。 系統(tǒng)打包流程中,刪除并拷貝so文件的DeleteAndCopySoTask。 壓縮so資源和其他多個文件資源(例如幀動畫)的ZipResTask。 為每個動態(tài)資源生成其對應的DynamicPkgInfo常量的功能,僅實現(xiàn)為一個普通方法。
所以主流程也就出來了
讀取并解析dynamic_plugin.gradle配置文件。 根據(jù)配置信息,決定是否將3個task加入任務隊列。 啟動任務隊列。

TransformTask流程
該task流程,主要就是通過tranform api和asm框架的使用,我們在其中加入了掃描class范圍的可配置項。
等待asm框架掃描class。 判斷該class名稱是否在我們配置的替換列表中,如果不在,就直接返回。 創(chuàng)建ClassVisitor和MethodVisitor,等待asm框架掃描每個方法。 如果該方法的名稱,參數(shù)列表和調(diào)用者,都和System.loadLibrary方法相符合。 我們替換為自己的SoloadUtil.loadLibrary方法。

DeleteAndCopySoTask流程
根據(jù)配置文件,找到系統(tǒng)的merge和strip task。 將我們的task插入到2個系統(tǒng)task之間,并等待系統(tǒng)回調(diào)我們的doLast方法。 遍歷系統(tǒng)的mergeTask的輸出目錄,判斷該so文件是否在我們配置的待掃描列表中。 如果配置了需要拷貝so文件,則我們將它拷貝到指定位置。 如果配置了需要刪除so文件,則我們將該so文件刪除。

ZipResTask流程
拷貝字體文件,將文件信息加入資源列表。 壓縮幀動畫文件,將壓縮后的文件信息加入資源列表。 壓縮so文件,將壓縮后的文件信息加入資源列表。 壓縮zip文件夾下文件,將壓縮后的文件信息加入資源列表。 遍歷資源文件,為其生成相應的資源實體類DynamicPkgInfo。

dynamic_plugin插件類設計
可以與第4章,整體架構圖結合起來看。
系統(tǒng)插件層
DynamicPlugin類,實現(xiàn)了系統(tǒng)gradle插件的plugin接口,為我們整個插件的入口,主要解析配置文件,并按照配置文件創(chuàng)建task信息。
DynamicParam類,提供了存儲并解析dyanmic_plugin配置文件的方法。
任務模塊層
ITask接口,代表了一個我們定義的任務。
DeleteAndCopySoTask,刪除并拷貝so文件任務。
TransfomrTask,替換系統(tǒng)System.loadLibrary方法任務。
ZipResTask,壓縮so和其他文件,并生成對應的java資源實體類方法。
底層實現(xiàn)層
SystemLoadClassVisitor類,Asm框架的class訪問類。
SystemLoadMethodVisitor類,Asm框架的method訪問類,用于替換System.loadLibrary方法。
JavaFileCreate類,使用javapoet框架產(chǎn)生java文件。
其他輔助類,在此省略
類uml圖

dynamic_config.gradle配置文件
該配置文件主要包含了配置dynamic_plugin插件運行步驟,插件輸入輸出路徑,so文件掃描路徑等信息。
dynamic_config = [
//是否執(zhí)行替換System.loadlibrary操作
is_replace_load_library: false,
//是否執(zhí)行替換System.load操作
is_replace_load : false,
//是否執(zhí)行刪除so文件操作
is_delete_so : false,
//是否執(zhí)行將so文件拷貝到其他目錄操作
is_copy_so : false,
//是否執(zhí)行將動態(tài)資源打包,并生成java文件操作
is_zip_res : false,
//是否執(zhí)行將so文件打包,并生成java文件操作
is_zip_so : false,
//是否自動上傳所有資源,上傳方法為dynamic_upload
is_upload_res : false,
//插件是否工作在Release模式下
is_release_type : isReleaseBuildType(),
//是否打印debug日志
is_debug_log : true,
//自動創(chuàng)建java文件時的包名
create_java_pkg_name : 'com.test' ,
]
/**
* 配置要刪除和拷貝的so文件
* map的key為壓縮包名稱,值為壓縮包包含的so文件列表
* key為debug_all_test時,會壓縮所有so包
*/
dynamic_scan_so_map = [
guang_dong : [ 'libpajf.so' , 'libpajf_av.so' , 'libsqlite.so' ],
]
dynamic_so_config = [
//so文件忽略列表,該表中的文件,不會被掃描。不在該列表中的文件都會被掃描
// (dynamic_scan_so_map為空時,本列表才生效)
ignore_so_files: [],
//so文件掃描abi目錄,不在該目錄下的so將不被掃描
scan_so_abis : [ "arm64-v8a" , "armeabi-v7a" ],
//拷貝出來的so文件夾前綴,ignore_so_files生效時使用
so_input_prefix: 'test' ,
]
dynamic_lib_list = [
//只有該列表中的包名,才會執(zhí)行替換System.loadlibrary操作
//輸入debug_all_test,則會替換所有System.loadLibrary方法,用于測試
scan_load_library_pkgs : [],
//在該列表中的包名或者類名,不會執(zhí)行替換System.loadlibrary操作,和上面的配置可以同時生效
ignore_load_library_pkgs: [],
]
//該配置不要改動內(nèi)容,需要改變路徑的,直接改變對應的方法內(nèi)容即可
dynamic_dir = [
//產(chǎn)生文件的輸出目錄
output : createOrGetOutputPath(),
//字體資源輸入目錄
typeface_input : createOrGetInputTypafacePath(),
//幀動畫資源輸入目錄
frame_anim_input: createOrGetInputFrameAnimPath(),
//so文件資源輸入目錄
so_input : createOrGetInputSoPath(),
//zip包輸入目錄
zip_input : createOrGetInputZipPath()
]
//該配置項,配置了android 2個gradle task的名稱
//主工程的mergeNativeLibs合并所有依賴的 native 庫
//主工程的stripDebugSymbols從 Native 庫中移除 Debug 符號。
dynamic_task = [
//自定義的task運行哪里
//true為mergeNativeLibs之后,stripDebugSymbols之前
//false為stripDebugSymbols之后,package之前
isTaskRunAfterMerge : true,
//debug狀態(tài)下,mergeNativeLibs的task名稱
debugMergeNativeLibs : "mergeDebugNativeLibs" ,
//release狀態(tài)下,mergeNativeLibs的task名稱
releaseMergeNativeLibs : "mergeReleaseNativeLibs" ,
//debug狀態(tài)下,stripDebugSymbols的task名稱
debugStripDebugSymbols : "stripDebugDebugSymbols" ,
//release狀態(tài)下,stripDebugSymbols的task名稱
releaseStripDebugSymbols: "stripReleaseDebugSymbols" ,
//debug狀態(tài)下,系統(tǒng)打包task名稱
debugPackage : "packageDebug" ,
//release狀態(tài)下,系統(tǒng)打包task名稱
releasePackage : "packageRelease" ,
//debug狀態(tài)下,mergeNativeLibs的輸出目錄
debugNativeOutputPath : " $ { projectDir }/app/build/intermediates/merged_native_libs/debug/out/lib" ,
//release狀態(tài)下,mergeNativeLibs的輸出目錄
releaseNativeOutputPath : " $ { projectDir }/app/build/intermediates/merged_native_libs/release/out/lib" ,
//debug狀態(tài)下,,stripDebugSymbols的輸出目錄
debugStripOutputPath : " $ { projectDir }/app/build/intermediates/stripped_native_libs/debug/out/lib" ,
//release狀態(tài)下,,stripDebugSymbols的輸出目錄
releaseStripOutputPath : " $ { projectDir }/app/build/intermediates/stripped_native_libs/release/out/lib" ,
]
//該閉包可以自動將文件上傳到服務器,參數(shù)列表為資源id,資源文件路徑
//我們可以再次執(zhí)行上傳服務器操作,并返回對應的url。
//當然也可以不實現(xiàn)上傳操作,并自己手動上傳資源。
dynamic_upload = {
id, path ->
println( "dynamic_upload id $ { id } ,path $ { path }" )
return 'http://url'
}
優(yōu)化效果
通過引入動態(tài)資源管理系統(tǒng),并將一鍵報警sdk相關的so文件和其他普通資源動態(tài)化后,貨拉拉用戶端的包體積減少了8M,從54M變?yōu)榱?6M。后繼將會繼續(xù)嘗試進行其他so文件的動態(tài)化。

參考文獻
https://www.jianshu.com/p/260137fdf7c5
https://mp.weixin.qq.com/s/X58fK02imnNkvUMFt23OAg
向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 點擊原文一鍵直達
專注 Android-Kotlin-Flutter 歡迎大家訪問
往期推薦
更文不易,點個“三連”支持一下??
