探討apk轉(zhuǎn)aar的可行性
碎碎念
最近不忙了,閑下來就有時間去思考,如何讓打包更輕松(寫更少的腳本,做更少的事情)。因為工作的原因,日常不是在打包就是在修改打包腳本的路上。這邊想采用apk轉(zhuǎn)aar的主要原因是想直接通過AS來出包了,在我的猜想下,打包速度應該會提升,并且不需處理目前使用apktool解包替換資源回編,遇到的各種問題。但是現(xiàn)實往往與想象差距甚遠,打包速度并沒有明顯的提升(哪怕少了解包的這個過程,對比apktool的完成流程并沒有占據(jù)較大的優(yōu)勢)。而且轉(zhuǎn)aar本身也需要使用到apktool(畢竟有人維護,而且一直以來也是用他 )。
前期查資料階段,先給出我覺得有用的資料
庫配置不正確
如果您的應用依賴于使用舊版 Android SDK Build Tools 構建的第三方庫,您的應用可能會在運行時崩潰,且不會顯示任何錯誤或警告。之所以會發(fā)生此崩潰,可能是因為在創(chuàng)建庫的過程中,將 R.java 字段聲明為 final,從而導致所有資源 ID 都被內(nèi)嵌在該庫的類中。
AAPT2 依賴于在構建應用時能夠?qū)?ID 重新分配給庫資源。如果該庫將這些 ID 視為 final 并將其內(nèi)嵌在庫 dex 中,便會出現(xiàn)運行時不匹配的情況。
如需解決此錯誤,請與庫創(chuàng)建者聯(lián)系,以使用最新版本的 Android SDK Build Tools 重新構建該庫,然后重新發(fā)布該庫。
從 res/ 讀取資源的唯一方法是使用資源 ID
保存在 assets/ 目錄中的文件沒有資源 ID,因此您無法通過 R 類或在 XML 資源中引用它們。您可以改為采用類似普通文件系統(tǒng)的方式查詢 assets/ 目錄中的文件,并利用 AssetManager 讀取原始數(shù)據(jù)。
不過,如果您只需要讀取原始數(shù)據(jù)(例如視頻文件或音頻文件)的能力,則可將文件保存在 res/raw/ 目錄中,并利用 openRawResource() 讀取字節(jié)流。
結(jié)論
根據(jù)上面分析可知,如果我們通過修改apk,把dex里面視為 'final', 的代碼 修改為引用R.java文件,并把分散在不同路徑的R文件合并為一個(把不同的引用都指向同一個), 并生成R.txt , 那么Apk文件是可以轉(zhuǎn)化為AAR的
轉(zhuǎn)換為AAR的好處主要就是,可以直接通過AS出包(免去處理拆分dex,65536的煩惱, 替換資源文件,以及理論上應該能提升打包速度==》指二次打包,并且as能幫我們處理合并AndroidMainfests)
誠然,如果cp能直接提供aar是最好的,但是很多時候拿到我們手里的都是apk,為此以往都是采用apk二次打包,運用apktool解包,大部分都是通過python腳本來修改文件。
與 JAR 文件不同,AAR 文件會為 Android 應用提供以下功能:
- AAR 文件可以包含多項 Android 資源和一個清單文件,讓您除了能夠在 Java 類和方法中進行捆綁以外,還能夠在布局和可繪制對象等共享資源中進行捆綁。
- AAR 文件可以包含 C/C++ 庫,供應用模塊的 C/C++ 代碼使用。
庫模塊開發(fā)注意事項
在開發(fā)庫模塊和相關應用時,請注意以下行為和限制。
向 Android 應用模塊添加對庫模塊的引用后,您可以設置它們的相對優(yōu)先級。在構建時,庫會按照優(yōu)先級由低到高的順序逐一與應用合并。
-
資源合并沖突
構建工具會將庫模塊中的資源與相關應用模塊的資源合并。如果這兩個模塊中都定義了給定的資源 ID,系統(tǒng)會使用應用中的資源。
如果多個 AAR 庫之間發(fā)生沖突,系統(tǒng)會使用依賴項列表中首先列出的庫(靠近
dependencies塊頂部)中的資源。為了避免常用的資源 ID 發(fā)生資源沖突,請考慮使用對模塊具有唯一性(或在所有項目模塊之間具有唯一性)的前綴或其他一致的命名方案。
-
在多模塊構建中,系統(tǒng)會將 JAR 依賴項視為傳遞依賴項
在向輸出 AAR 的庫項目添加 JAR 依賴項時,JAR 會由庫模塊進行處理,并與其 AAR 打包在一起。
不過,如果您的項目包含庫模塊,并且此模塊已被應用模塊使用,應用模塊便會將庫的本地 JAR 依賴項視為傳遞依賴項。在這種情況下,本地 JAR 將由使用它的應用模塊進行處理,而不是由庫模塊進行處理。這是為了加快庫代碼更改導致的增量構建的速度。
由本地 JAR 依賴項導致的所有 Java 資源沖突都必須在使用相應庫的應用模塊中解決。
-
庫模塊可以依賴于外部 JAR 庫
您可以開發(fā)一個依賴于外部庫(例如 Google 地圖外部庫)的庫模塊。在這種情況下,相關應用必須針對包含此外部庫的目標(例如 Google API 插件)進行構建。另外也要注意,庫模塊和相關應用都必須在其清單文件的
<uses-library>元素中聲明外部庫。 -
應用模塊的
minSdkVersion必須等于或大于庫定義的版本庫是作為相關應用模塊的一部分進行編譯的,因此,庫模塊中使用的 API 必須與應用模塊支持的平臺版本兼容。
-
每個庫模塊都會創(chuàng)建自己的 R 類
在您構建相關應用模塊時,庫模塊會先編譯到 AAR 文件中,然后再添加到應用模塊中。因此,每個庫都有自己的
R類,并根據(jù)庫的軟件包名稱命名。所需的所有軟件包中都會創(chuàng)建從主模塊和庫模塊生成的R類,包括主模塊的軟件包和庫的軟件包。 -
庫模塊可以包含自己的 ProGuard 配置文件
如果有用于構建和發(fā)布 AAR 的庫項目,您可以向庫的構建配置添加 ProGuard 配置文件,并且 Android Gradle 插件規(guī)則適用于您指定的 ProGuard 規(guī)則。構建工具會將此文件嵌入到為庫模塊生成的 AAR 文件中。在您將庫添加到應用模塊后,庫的 ProGuard 文件會附加到應用模塊的 ProGuard 配置文件 (
proguard.txt)。通過將 ProGuard 文件嵌入到庫模塊中,您可以確保依賴于相應庫的應用模塊不必手動更新其 ProGuard 文件即可使用此庫。當 Android Studio 構建系統(tǒng)構建您的應用時,它會同時使用來自應用模塊和庫的指令。因此無需按照單獨的步驟在庫上運行代碼縮減器。
如需向庫項目添加 ProGuard 規(guī)則,您必須使用
consumerProguardFiles屬性(位于庫的build.gradle文件的defaultConfig塊內(nèi))指定文件名稱。例如,以下代碼段會將lib-proguard-rules.txt設為庫的 ProGuard 配置文件:不過,如果庫模塊是要編譯到 APK 中的多模塊構建的一部分,并且不會生成 AAR,您應該只在使用相應庫的應用模塊上運行代碼縮減。如需詳細了解 ProGuard 規(guī)則及其用法,請參閱縮減、混淆處理和優(yōu)化應用。
-
測試庫模塊的方法與測試應用的方法相同
主要區(qū)別在于,庫及其依賴項會作為測試 APK 的依賴項自動包含在內(nèi)。這意味著測試 APK 不僅包含自己的代碼,還包含庫的 AAR 及其所有依賴項。由于沒有單獨的“被測應用”,因此
androidTest任務只會安裝(和卸載)測試 APK。在合并多個清單文件時,Gradle 會遵循默認的優(yōu)先級順序,并將庫的清單合并到測試 APK 的主清單中。
AAR 文件詳解
AAR 文件的文件擴展名為 .aar,Maven 工件類型應該也是 aar。此文件本身是一個 zip 文件。唯一的必需條目是 /AndroidManifest.xml。
此外,AAR 文件可能包含以下一個或多個可選條目:
/classes.jar/res//R.txt/public.txt/assets//libs/name.jar-
/jni//abi_name/name.so(其中abi_name是 Android 支持的 ABI 之一) /proguard.txt/lint.jar/api.jar-
/prefab/(用于導出原生庫)
開始
在查閱了大量資料,并思考后,我得出了apk可以轉(zhuǎn)換成aar的結(jié)論.并手動通過apk生成了一個aar然后導入到工程里面,并成功運行后,開始著手編碼的過程。
首先,隨便搞個apk和aar然后進行對比(同一個工程的同一個model)


大部分文件都認識,除了R.txt ,于是雙擊

這看起來有點類似public.xml的文件,那就根據(jù)public.xml來生成R.txt吧,然后用Sublime來用public.xml生成R.txt,然后悲催的發(fā)現(xiàn)行數(shù)對不上,對比發(fā)現(xiàn)少了styleable (之前修正ApkIdTool填坑也是這個老朋友)

事已至此,不能簡單的由public.xml生成R.txt,所以我不由得開始琢磨起這R文件是用來干嘛的了。
官方關于aar的說明
官方文檔關于aar上明確說明aar文件本身是一個 zip 文件。唯一的必需條目是 /AndroidManifest.xml。
那就直接刪掉aar的R.txt然后直接運行項目,結(jié)果就是程序崩毀日志顯示找不到對應的R文件,那么這個R文件應該是用來生成R.java的吧
畫重點
在查閱了大量資料與實踐之后得出R.txt 是決定在aar的包名下生成R文件的內(nèi)容, 所以有無R.txt將決定是否在aar文件的AndroidMainFest.xml的packageName下的路徑下創(chuàng)建R.java。
那么?!嗯?
可能有的小伙伴已經(jīng)想到了我要干嘛了。apk文件本身就包含了常量化的資源id,如果我按照as生成的aar的方式生成aar,那我就需要把常量化的資源id改成動態(tài)引用包名下的R文件的對應id,并生成R.txt。然后我還需要把用來固定資源id的apk解包后生成的public.xml文件給刪除掉。單單就對資源id由final改成動態(tài)引用,我就要做這么多操作!
既然apk已經(jīng)分配了id而且也有了固定資源id的public.xml,那么我能不能直接就用這些常量化的id呢?于是,我又手動合成了一個aar,運行沒有任何問題。
其實理論上也是不存在任何問題的,因為最后都是調(diào)用aapt2來生成資源id,當項目引用aar的時候,res里面包含public.xml,那也就是提前固定了對應的資源id而已,當不存在public.xml的資源也就不存在固定,也就重新生成資源id。并沒有任何問題。(在寫游戲sdk方面,一般都不使用R.id的方式來獲取資源id。很大一部分原因就是為了規(guī)避資源id重新分配,導致的id錯亂問題。關于為什么使用動態(tài)方式獲取id就不會有這個問題,可以參考抖音的分享,文中稱為資源文件反射我更愿意稱動態(tài)獲取資源id)
有了上述的理論支持,那么由apk2aar可以簡化成如下圖所示

1.關于AndroiManifest.xml的修改,可以參考合并aar,主要按照as生成 aar的AndroidManifest.xml的形式去修改
2.關于unknown文件,我之所以這么做是基于引入了okhttp后,apk解包后會在unknown下有publicsuffixes.gz,而我這么做也是根據(jù)引用okhttp后生成的aar來的(用fat-aar,為我節(jié)省了思考的時間,感謝)
3.用dex2jar提取jar,并刪除需要替換的類(因為需要更新這部分代碼),資源不用做處理,因為如果多個 AAR 庫之間發(fā)生沖突,系統(tǒng)會使用依賴項列表中首先列出的庫(靠近 dependencies 塊頂部)中的資源。
所以我又寫了代碼,整合到了tool里面,使用請參考。因為代碼還很粗糙,按照最開始的設想轉(zhuǎn)aar只需運行一次所以并沒有過多考慮性能。并且由于使用aar用as出包并沒有速度上的優(yōu)勢,已經(jīng)失去了繼續(xù)的動力(按照最開始的想法,因為轉(zhuǎn)aar一次性的耗費的時間先忽略,那么打包因為沒了apktool的解包回編,理論上只有編譯apk這步,速度應該明顯提升。但是事與愿違,實測并沒有優(yōu)勢),所以,也就沒必要優(yōu)化,而且由于,缺少大量樣本,可能存在,我認知外的情況。
希望能起到拋磚引玉的作用,共勉。
ps:
補充說明:AS的aar之所以不用固定id是因為多個aar的話可能有沖突,我目前的使用因為我能確保只有一個aar是用固定id的所以不會出現(xiàn)問題