深入分析Android構(gòu)建過(guò)程
資源合并
如果項(xiàng)目引入了android support包,又或許依賴于其它第三方aar庫(kù),那構(gòu)建前會(huì)將aar解壓并與本地資源合并,這里的資源主要包括assets目錄,res目錄及Androidmanifest.xml。
當(dāng)?shù)谌揭蕾囍械腶ssets或res文件與本地文件有沖突時(shí),會(huì)優(yōu)先選用本地文件。但res/values略有不同,此目錄下的strings.xml、color.xml、styles.xml等文件會(huì)被整合到一個(gè)叫values.xml的文件中去,后與各第三方依賴中的values.xml進(jìn)行內(nèi)容上的合并,不會(huì)像res其它子目錄文件一樣直接舍棄第三方?jīng)_突文件。
Androidmanifest.xml的合并相比來(lái)說(shuō)則要復(fù)雜一些,除了第三方依賴中的manifest,項(xiàng)目還可以在不同目錄下分別擁有manifest文件。構(gòu)建過(guò)程中,會(huì)根據(jù)manifest中元素、屬性及賦值來(lái)生成一個(gè)manifest文件,并應(yīng)用于后續(xù)的打包過(guò)程。gradle為不同的manifest賦予了不同的優(yōu)先級(jí),其順序如下:
buildType 設(shè)置 > productFlavor 設(shè)置 > src/main > dependency&library
XML元素及屬性的沖突會(huì)根據(jù)以下規(guī)則進(jìn)行解決:
當(dāng)然也會(huì)有一些例外的:
uses-feature android:required與uses-library android:required默認(rèn)為true,根據(jù)or規(guī)則合并;
如未指定uses-sdk,minSdkVersion跟targetSdkVersion將被設(shè)置為1。而沖突時(shí)會(huì)使用高優(yōu)化級(jí)的設(shè)置;
若library的minSdkVersion高于src/main的設(shè)置,則會(huì)引發(fā)error,但可通過(guò)overrideLibrary解決。若未指定targetSdkVersion,則其值與minSdkVersion一致;
若library的targetSdkVersion低于src/main的設(shè)置,需要添加一些額外的權(quán)限保證library能正常運(yùn)行;
manifest元素只與子manifest元素合并;
intent-filter元素在合并中不會(huì)被改變,只會(huì)被添加到其父節(jié)點(diǎn)中去;
沖突發(fā)生時(shí),可通過(guò)合并沖突標(biāo)記進(jìn)行解決,需要引入android tools命名空間,詳情請(qǐng)參閱官方文檔。另外,manifest在對(duì)文件進(jìn)行合并后,還會(huì)根據(jù)build.gradle的設(shè)置覆蓋相關(guān)屬性。
AAPT打包
資源合并后,即進(jìn)入到編譯階段,先會(huì)把項(xiàng)目資源中的xml編譯成二進(jìn)制并生成R.java及資源索引表resources.arsc,其流程如下:
由圖可見(jiàn),assets是不需要做任何處理的,res/raw只需分配id后與assets一起直接打包到應(yīng)用程序中;基于下述原因,其它xml文件則會(huì)被編譯成二進(jìn)制。
編譯過(guò)程中,會(huì)把xml中的字符串進(jìn)行收集去重,形成字符串資源池,元素中用到字符串的地方將被替換成相應(yīng)的索引。另外,標(biāo)簽屬性/値都會(huì)轉(zhuǎn)換為資源id,進(jìn)一步減少文件大?。?br />
二進(jìn)制格式的xml把標(biāo)簽屬性/値轉(zhuǎn)換為資源id后,避免了字符串解析,從而提高了解析速度;
經(jīng)過(guò)AAPT(Android Asset Packaging Tool)處理后,會(huì)輸出2個(gè)文件:一個(gè)R.java,為項(xiàng)目各資源分配了不同的id,將和java源碼一起參與到后續(xù)的編譯過(guò)程,id為4字節(jié)無(wú)符號(hào)整數(shù),最高字節(jié)表示package id,次高字節(jié)表示type id,后2字節(jié)表示資源在當(dāng)前類(lèi)型中出現(xiàn)的序號(hào),如R.string.appname=0x7f07006b中的0x7f代表當(dāng)前正在編譯的資源包,0x07代表string類(lèi)型,0x006b代表app_name在string類(lèi)型中出現(xiàn)的序號(hào);另一個(gè)為app.ap,實(shí)際上為一個(gè)壓縮包,包含了assets、res、Androidmanifest.xml與resources.arsc
資源索引表resources.arsc記錄了從資源id到文件路徑的轉(zhuǎn)換關(guān)系,當(dāng)應(yīng)用通過(guò)Resources類(lèi)獲取res文件資源時(shí),會(huì)先從resources.arsc中拿到文件路徑,然后通過(guò)AssetManager進(jìn)行訪問(wèn)。
Application Component -> resources.arsc -> AssetManager -> apk
從上述流程中可以看到,若要進(jìn)行資源的混淆,可在分析resources.arsc格式后,修改內(nèi)容中文件路徑的指向并對(duì)資源文件進(jìn)行相應(yīng)的重命名即可。
另外,AAPT還可對(duì)png圖進(jìn)行優(yōu)化、指定文件以stored還是deflated模式添加到壓縮包中等操作。
源碼編譯
當(dāng)項(xiàng)目中包含aidl時(shí),會(huì)先調(diào)用aidl工具生成java代碼;renderscript亦然,需要先調(diào)用llvm-rs-cc,只是它不僅會(huì)自動(dòng)生成java文件,還會(huì)產(chǎn)生相應(yīng)的.bc文件,.bc文件將打包到apk中
至此,java代碼都已準(zhǔn)備完畢。下一步要進(jìn)行的是通過(guò)javac命令將java源碼編譯成.class字節(jié)碼,用以編譯的classpath包含以下內(nèi)容:
android.jar,具體版本由targetSdkVersion指定;
build.gradle中添加的第三方依賴;
編譯后可對(duì)代碼進(jìn)行混淆處理,主要包括刪除無(wú)用類(lèi)、字節(jié)碼優(yōu)化、重命名等操作,只需在build.gradle中配置混淆規(guī)則即可
buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt') proguardFile 'proguard/proguard-rules.pro' } }
生成dex
如果項(xiàng)目涉及分dex,那在調(diào)用dx命令前,需要做一些準(zhǔn)備的工作,把編譯后的class文件打包成jar包allclasses.jar,然后生成主dex中必須包含的文件列表。主要包括collect、shrink及create 3個(gè)步驟。
首先會(huì)通過(guò)Androidmanifest.xml過(guò)濾出項(xiàng)目中使用到的四大組件(Activity、Service、receiver、provider)、Application及Instrumentation,并寫(xiě)入manifest_keep.txt文件,這些都是會(huì)默認(rèn)添加到主dex的,無(wú)須手動(dòng)設(shè)置。除此之外,默認(rèn)添加的還有繼承于 BackupAgent 及 Annotation 的類(lèi)。若有額外的類(lèi)需要被加入到主dex中,可以新建一個(gè)文件并以proguard的語(yǔ)法指定,然后在build.gradle中把此文件配置到multiDexKeepProguard中去。此過(guò)程關(guān)鍵代碼如下:
void generateKeepListFromManifest() { SAXParser parser = SAXParserFactory.newInstance().newSAXParser() Writer out = new BufferedWriter(new FileWriter(getOutputFile())) try { parser.parse(getManifest(), new ManifestHandler(out)) // add a couple of rules that cannot be easily parsed from the manifest. out.write("""-keep public class * extends android.app.backup.BackupAgent {<init>();} -keep public class * extends java.lang.annotation.Annotation {*;} """) if (proguardFile != null) { out.write(Files.toString(proguardFile, Charsets.UTF_8)) } } finally { out.close() } }
這個(gè)時(shí)候,會(huì)執(zhí)行一個(gè)叫shrinkXxxMultiDexComponents(Xxx為build types名稱(chēng))的任務(wù)。實(shí)際上是調(diào)用了proguard,只是要比常規(guī)的proguard簡(jiǎn)單一些,不執(zhí)行混淆、優(yōu)化跟預(yù)檢幾個(gè)步驟,只需要shrink即可,以allclasses.jar為輸入、manifest_keep.txt為混淆配置文件,把指定內(nèi)容及其引用標(biāo)記起來(lái),然后添加到componentClasses.jar中去。
public void execute(ProGuardTask proguardComponentsTask) { proguardComponentsTask.dontobfuscate(); proguardComponentsTask.dontoptimize(); proguardComponentsTask.dontpreverify(); // 方法未完,略過(guò)...}
到了CreateMainDexList,會(huì)調(diào)用dx命令,傳入allclasses.jar、componentClasses.jar,分析后者依賴,把它直接引用的類(lèi)也添加到主dex中,并生成新的multidex配置文件maindexlist.txt,至此,準(zhǔn)備工作完成。
經(jīng)過(guò)上一階段編譯的處理,已經(jīng)生成了標(biāo)準(zhǔn)的java字節(jié)碼,可在標(biāo)準(zhǔn)的java虛擬機(jī)上運(yùn)行。但android使用了它特有的dalvik虛擬機(jī),這就需要我們?yōu)樗峁┝硪徊煌母袷健x工具為此而出現(xiàn),可將.classes文件轉(zhuǎn)換添加到dalvik可執(zhí)行文件.dex中去。當(dāng)項(xiàng)目發(fā)展到一定規(guī)模,需要進(jìn)行分dex處理時(shí),可通過(guò)上述步驟生成的maindexlist.txt指定dex該如何拆分。
遺憾的是,以上關(guān)于分dex的內(nèi)容都是理想的情況,現(xiàn)實(shí)卻很殘酷。如果項(xiàng)目中開(kāi)啟了proguard,那它會(huì)在分dex的shrink處理前完成,導(dǎo)致allclasses.jar是混淆處理后的代碼,而manifest_keep.txt卻未曾混淆,后續(xù)生成componentClasses.jar 及 maindexlist.txt 的過(guò)程也就都不再可靠了。要解決這個(gè)問(wèn)題,在shrink前通過(guò)混淆輸出的符號(hào)表mapping.txt對(duì)manifest_keep.txt進(jìn)行修正是個(gè)不錯(cuò)的選擇。
打包簽名
此時(shí)萬(wàn)事俱備,只要把資源包app.ap_、可執(zhí)行文件classes.dex及項(xiàng)目(包含第三方依賴)中的非源碼文件一起添加到壓縮包中去,我們的安裝包(.apk文件)也就生成了。
另外,apk需要經(jīng)過(guò)簽名才可以發(fā)布??赏ㄟ^(guò)jarsigner工具完成。
zipalign
文件對(duì)齊并非android構(gòu)建的必要步驟,但對(duì)齊處理后可提高系統(tǒng)訪問(wèn)安裝包資源的效率。即使執(zhí)行了zipalign,也只有以stored模式添加到apk中的文件是需要對(duì)齊的。如若對(duì)圖片等資源進(jìn)行了極限壓縮或在aapt打包時(shí)選擇了deflated,那可對(duì)齊的文件也就沒(méi)多少了
通過(guò)build tools中的zipalign工具以下命令可對(duì)壓縮包進(jìn)行對(duì)齊
zipalign -f -v 4 app.apk toapp.apk
以下命令則起到了檢驗(yàn)壓縮包有沒(méi)有對(duì)齊的作用:
zipalign -c -v 4 app.apk
總結(jié)
本文主要介紹了android構(gòu)建的各個(gè)主要步驟,并重點(diǎn)講述了資源合并打包與dex生成的過(guò)程。最后,用一張圖概括下構(gòu)建的總體流程:
相關(guān)文章
輕松實(shí)現(xiàn)Android語(yǔ)音識(shí)別功能
這篇文章主要為初學(xué)者介紹了輕松實(shí)現(xiàn)Android語(yǔ)音識(shí)別功能的代碼,感興趣的小伙伴們可以參考一下2016-07-07Retrofit網(wǎng)絡(luò)請(qǐng)求和響應(yīng)處理重點(diǎn)分析講解
這篇文章主要介紹了Retrofit網(wǎng)絡(luò)請(qǐng)求和響應(yīng)處理重點(diǎn)分析,在使用?Retrofit發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí),我們可以通過(guò)定義一個(gè)接口并使用Retrofit的注解來(lái)描述這個(gè)接口中的請(qǐng)求,Retrofit會(huì)自動(dòng)生成一個(gè)實(shí)現(xiàn)該接口的代理對(duì)象2023-03-03Android FTP 多線程斷點(diǎn)續(xù)傳下載\上傳的實(shí)例
本篇文章主要介紹了Android FTP 多線程斷點(diǎn)續(xù)傳下載\上傳的實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08Android自定義View實(shí)現(xiàn)縱向跑馬燈效果詳解
對(duì)于跑馬燈效果在我們?nèi)粘J褂玫腶pp中還是很常見(jiàn)的,比如外賣(mài)app的商家公告就使用了此效果,但是它是橫向滾動(dòng)的,橫向滾動(dòng)多適用于單條信息;但凡涉及到多條信息的滾動(dòng)展示,用縱向滾動(dòng)效果會(huì)有更好的用戶體驗(yàn),今天我們通過(guò)自定義View來(lái)看看如何實(shí)現(xiàn)縱向跑馬燈效果。2016-11-11android實(shí)現(xiàn)上下滾動(dòng)的TextView
android實(shí)現(xiàn)上下滾動(dòng)的TextView,需要的朋友可以參考一下2013-05-05Android Studio導(dǎo)入Eclipse項(xiàng)目時(shí).so庫(kù)文件的解決方法
這篇文章主要介紹了Android Studio導(dǎo)入Eclipse項(xiàng)目時(shí)項(xiàng)目中".so"庫(kù)文件的解決方法,需要的朋友可以參考下2018-06-06Android實(shí)現(xiàn)九宮格(GridView中各項(xiàng)平分空間)的方法
這篇文章主要介紹了Android實(shí)現(xiàn)九宮格(GridView中各項(xiàng)平分空間)的方法,涉及Android針對(duì)GridView操作的相關(guān)技巧,需要的朋友可以參考下2015-06-06Android TabHost如何實(shí)現(xiàn)頂部選項(xiàng)卡
這篇文章主要介紹了Android TabHost如何實(shí)現(xiàn)頂部選項(xiàng)卡,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Android onSaveInstanceState和onRestoreInstanceState觸發(fā)的時(shí)機(jī)
這篇文章主要介紹了Android onSaveInstanceState和onRestoreInstanceState觸發(fā)的時(shí)機(jī)的相關(guān)資料,需要的朋友可以參考下2017-05-05