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


大部分文件都認(rèn)識(shí),除了R.txt ,于是雙擊

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

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

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