探討apk轉(zhuǎn)aar的可行性
碎碎念
最近不忙了诡壁,閑下來就有時間去思考济瓢,如何讓打包更輕松(寫更少的腳本,做更少的事情)妹卿。因為工作的原因旺矾,日常不是在打包就是在修改打包腳本的路上榄檬。這邊想采用apk轉(zhuǎn)aar的主要原因是想直接通過AS來出包了遍坟,在我的猜想下,打包速度應(yīng)該會提升逾苫,并且不需處理目前使用apktool解包替換資源回編铺纽,遇到的各種問題扒吁。但是現(xiàn)實往往與想象差距甚遠,打包速度并沒有明顯的提升(哪怕少了解包的這個過程室囊,對比apktool的完成流程并沒有占據(jù)較大的優(yōu)勢)雕崩。而且轉(zhuǎn)aar本身也需要使用到apktool(畢竟有人維護,而且一直以來也是用他 )融撞。
前期查資料階段盼铁,先給出我覺得有用的資料
庫配置不正確
如果您的應(yīng)用依賴于使用舊版 Android SDK Build Tools 構(gòu)建的第三方庫,您的應(yīng)用可能會在運行時崩潰尝偎,且不會顯示任何錯誤或警告饶火。之所以會發(fā)生此崩潰鹏控,可能是因為在創(chuàng)建庫的過程中,將 R.java
字段聲明為 final
肤寝,從而導(dǎo)致所有資源 ID 都被內(nèi)嵌在該庫的類中当辐。
AAPT2 依賴于在構(gòu)建應(yīng)用時能夠?qū)?ID 重新分配給庫資源。如果該庫將這些 ID 視為 final
并將其內(nèi)嵌在庫 dex 中鲤看,便會出現(xiàn)運行時不匹配的情況缘揪。
如需解決此錯誤,請與庫創(chuàng)建者聯(lián)系义桂,以使用最新版本的 Android SDK Build Tools 重新構(gòu)建該庫找筝,然后重新發(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的煩惱凌那, 替換資源文件兼雄,以及理論上應(yīng)該能提升打包速度==》指二次打包,并且as能幫我們處理合并AndroidMainfests)
誠然帽蝶,如果cp能直接提供aar是最好的赦肋,但是很多時候拿到我們手里的都是apk,為此以往都是采用apk二次打包励稳,運用apktool解包佃乘,大部分都是通過python腳本來修改文件。
與 JAR 文件不同驹尼,AAR 文件會為 Android 應(yīng)用提供以下功能:
- AAR 文件可以包含多項 Android 資源和一個清單文件趣避,讓您除了能夠在 Java 類和方法中進行捆綁以外,還能夠在布局和可繪制對象等共享資源中進行捆綁新翎。
- AAR 文件可以包含 C/C++ 庫程帕,供應(yīng)用模塊的 C/C++ 代碼使用住练。
庫模塊開發(fā)注意事項
在開發(fā)庫模塊和相關(guān)應(yīng)用時,請注意以下行為和限制愁拭。
向 Android 應(yīng)用模塊添加對庫模塊的引用后讲逛,您可以設(shè)置它們的相對優(yōu)先級。在構(gòu)建時岭埠,庫會按照優(yōu)先級由低到高的順序逐一與應(yīng)用合并盏混。
-
資源合并沖突
構(gòu)建工具會將庫模塊中的資源與相關(guān)應(yīng)用模塊的資源合并。如果這兩個模塊中都定義了給定的資源 ID枫攀,系統(tǒng)會使用應(yīng)用中的資源括饶。
如果多個 AAR 庫之間發(fā)生沖突株茶,系統(tǒng)會使用依賴項列表中首先列出的庫(靠近
dependencies
塊頂部)中的資源来涨。為了避免常用的資源 ID 發(fā)生資源沖突,請考慮使用對模塊具有唯一性(或在所有項目模塊之間具有唯一性)的前綴或其他一致的命名方案启盛。
-
在多模塊構(gòu)建中蹦掐,系統(tǒng)會將 JAR 依賴項視為傳遞依賴項
在向輸出 AAR 的庫項目添加 JAR 依賴項時,JAR 會由庫模塊進行處理僵闯,并與其 AAR 打包在一起卧抗。
不過,如果您的項目包含庫模塊鳖粟,并且此模塊已被應(yīng)用模塊使用社裆,應(yīng)用模塊便會將庫的本地 JAR 依賴項視為傳遞依賴項。在這種情況下向图,本地 JAR 將由使用它的應(yīng)用模塊進行處理泳秀,而不是由庫模塊進行處理。這是為了加快庫代碼更改導(dǎo)致的增量構(gòu)建的速度榄攀。
由本地 JAR 依賴項導(dǎo)致的所有 Java 資源沖突都必須在使用相應(yīng)庫的應(yīng)用模塊中解決嗜傅。
-
庫模塊可以依賴于外部 JAR 庫
您可以開發(fā)一個依賴于外部庫(例如 Google 地圖外部庫)的庫模塊。在這種情況下檩赢,相關(guān)應(yīng)用必須針對包含此外部庫的目標(例如 Google API 插件)進行構(gòu)建吕嘀。另外也要注意,庫模塊和相關(guān)應(yīng)用都必須在其清單文件的
<uses-library>
元素中聲明外部庫贞瞒。 -
應(yīng)用模塊的
minSdkVersion
必須等于或大于庫定義的版本庫是作為相關(guān)應(yīng)用模塊的一部分進行編譯的偶房,因此,庫模塊中使用的 API 必須與應(yīng)用模塊支持的平臺版本兼容军浆。
-
每個庫模塊都會創(chuàng)建自己的 R 類
在您構(gòu)建相關(guān)應(yīng)用模塊時蝴悉,庫模塊會先編譯到 AAR 文件中,然后再添加到應(yīng)用模塊中瘾敢。因此拍冠,每個庫都有自己的
R
類尿这,并根據(jù)庫的軟件包名稱命名。所需的所有軟件包中都會創(chuàng)建從主模塊和庫模塊生成的R
類庆杜,包括主模塊的軟件包和庫的軟件包射众。 -
庫模塊可以包含自己的 ProGuard 配置文件
如果有用于構(gòu)建和發(fā)布 AAR 的庫項目晃财,您可以向庫的構(gòu)建配置添加 ProGuard 配置文件叨橱,并且 Android Gradle 插件規(guī)則適用于您指定的 ProGuard 規(guī)則。構(gòu)建工具會將此文件嵌入到為庫模塊生成的 AAR 文件中断盛。在您將庫添加到應(yīng)用模塊后罗洗,庫的 ProGuard 文件會附加到應(yīng)用模塊的 ProGuard 配置文件 (
proguard.txt
)。通過將 ProGuard 文件嵌入到庫模塊中钢猛,您可以確保依賴于相應(yīng)庫的應(yīng)用模塊不必手動更新其 ProGuard 文件即可使用此庫伙菜。當 Android Studio 構(gòu)建系統(tǒng)構(gòu)建您的應(yīng)用時,它會同時使用來自應(yīng)用模塊和庫的指令命迈。因此無需按照單獨的步驟在庫上運行代碼縮減器贩绕。
如需向庫項目添加 ProGuard 規(guī)則,您必須使用
consumerProguardFiles
屬性(位于庫的build.gradle
文件的defaultConfig
塊內(nèi))指定文件名稱壶愤。例如淑倾,以下代碼段會將lib-proguard-rules.txt
設(shè)為庫的 ProGuard 配置文件:不過,如果庫模塊是要編譯到 APK 中的多模塊構(gòu)建的一部分征椒,并且不會生成 AAR娇哆,您應(yīng)該只在使用相應(yīng)庫的應(yīng)用模塊上運行代碼縮減。如需詳細了解 ProGuard 規(guī)則及其用法勃救,請參閱縮減碍讨、混淆處理和優(yōu)化應(yīng)用。
-
測試庫模塊的方法與測試應(yīng)用的方法相同
主要區(qū)別在于剪芥,庫及其依賴項會作為測試 APK 的依賴項自動包含在內(nèi)垄开。這意味著測試 APK 不僅包含自己的代碼,還包含庫的 AAR 及其所有依賴項税肪。由于沒有單獨的“被測應(yīng)用”溉躲,因此
androidTest
任務(wù)只會安裝(和卸載)測試 APK。在合并多個清單文件時益兄,Gradle 會遵循默認的優(yōu)先級順序锻梳,并將庫的清單合并到測試 APK 的主清單中。
AAR 文件詳解
AAR 文件的文件擴展名為 .aar
净捅,Maven 工件類型應(yīng)該也是 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/
(用于導(dǎo)出原生庫)
開始
在查閱了大量資料废亭,并思考后,我得出了apk可以轉(zhuǎn)換成aar的結(jié)論.并手動通過apk生成了一個aar然后導(dǎo)入到工程里面具钥,并成功運行后豆村,開始著手編碼的過程。
首先骂删,隨便搞個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文件是用來干嘛的了。
官方關(guān)于aar的說明
官方文檔關(guān)于aar上明確說明aar文件本身是一個 zip 文件恋追。唯一的必需條目是 /AndroidManifest.xml凭迹。
那就直接刪掉aar的R.txt然后直接運行項目罚屋,結(jié)果就是程序崩毀日志顯示找不到對應(yīng)的R文件苦囱,那么這個R文件應(yīng)該是用來生成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文件的對應(yīng)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伍宦,那也就是提前固定了對應(yīng)的資源id而已芽死,當不存在public.xml的資源也就不存在固定乏梁,也就重新生成資源id。并沒有任何問題关贵。(在寫游戲sdk方面掌呜,一般都不使用R.id的方式來獲取資源id。很大一部分原因就是為了規(guī)避資源id重新分配坪哄,導(dǎo)致的id錯亂問題质蕉。關(guān)于為什么使用動態(tài)方式獲取id就不會有這個問題,可以參考抖音的分享翩肌,文中稱為資源文件反射
我更愿意稱動態(tài)獲取資源id
)
有了上述的理論支持模暗,那么由apk2aar可以簡化成如下圖所示
1.關(guān)于AndroiManifest.xml的修改,可以參考合并aar念祭,主要按照as生成 aar的AndroidManifest.xml的形式去修改
2.關(guān)于unknown文件兑宇,我之所以這么做是基于引入了okhttp后,apk解包后會在unknown下有publicsuffixes.gz粱坤,而我這么做也是根據(jù)引用okhttp后生成的aar來的(用fat-aar隶糕,為我節(jié)省了思考的時間,感謝)
3.用dex2jar提取jar站玄,并刪除需要替換的類(因為需要更新這部分代碼)枚驻,資源不用做處理,因為如果多個 AAR 庫之間發(fā)生沖突株旷,系統(tǒng)會使用依賴項列表中首先列出的庫(靠近 dependencies 塊頂部)中的資源再登。
所以我又寫了代碼,整合到了tool里面,使用請參考晾剖。因為代碼還很粗糙锉矢,按照最開始的設(shè)想轉(zhuǎn)aar只需運行一次所以并沒有過多考慮性能。并且由于使用aar用as出包并沒有速度上的優(yōu)勢齿尽,已經(jīng)失去了繼續(xù)的動力(按照最開始的想法沽损,因為轉(zhuǎn)aar一次性的耗費的時間先忽略,那么打包因為沒了apktool的解包回編循头,理論上只有編譯apk這步绵估,速度應(yīng)該明顯提升。但是事與愿違贷岸,實測并沒有優(yōu)勢)壹士,所以,也就沒必要優(yōu)化偿警,而且由于躏救,缺少大量樣本,可能存在,我認知外的情況盒使。
希望能起到拋磚引玉的作用崩掘,共勉。
ps:
補充說明:AS的aar之所以不用固定id是因為多個aar的話可能有沖突少办,我目前的使用因為我能確保只有一個aar是用固定id的所以不會出現(xiàn)問題