做爲個Android developer ,對APK(AndroidPackage)想必是再熟悉不過的了。咱們在 Gradle 中點擊下 build 或者經過命令行 gradlew tasks
,AndroidStudio 就會開始執行構建流程,最終輸出APK文件。 這件事我常常幹,也習覺得常了,可是有時也會偶爾想一想,那一串串代碼是如何變成 apk 的呢,期間經歷了那些流程呢?html
先放張官網的構建流程圖 前端
典型 Android 應用模塊的構建流程一般依循下列步驟:
1.編譯器將您的源代碼轉換成 DEX(Dalvik Executable) 文件(其中包括 Android 設備上運行的字節碼),將全部其餘內容轉換成已編譯資源。
2.APK 打包器將 DEX 文件和已編譯資源合併成單個 APK。 不過,必須先簽署 APK,才能將應用安裝並部署到 Android 設備上。
3.APK 打包器使用調試或發佈密鑰庫簽署您的 APK:
a.若是您構建的是調試版本的應用(即專用於測試和分析的應用),打包器會使用調試密鑰庫簽署您的應用。 Android Studio 自動使用調試密鑰庫配置新項目。
b.若是您構建的是打算向外發佈的發佈版本應用,打包器會使用發佈密鑰庫簽署您的應用。 要建立發佈密鑰庫,請閱讀在 Android Studio 中籤署您的應用。
4.在生成最終 APK 以前,打包器會使用 zipalign 工具對應用進行優化,減小其在設備上運行時佔用的內存。
複製代碼
官網給出的流程圖仍是比較抽象的,不少細節都隱藏了,如下是Google官方發佈的一張很是經典的Apk打包流程圖。接下來會將基於下圖的流程進行簡單分析。java
資源文件(res文件夾下的文件)經過 AAPT(Android Asset Packaging Tool)打包生成R.java
類(資源索引表)以及.arsc
資源文件。android
通過aapt生成的R文件佔4個字節c++
public static final int design_appbar_state_list_animator=0x7f020000;git
0x7f
表示packageID,用來限定資源的來源。系統資源包是ox01,SharedLibrary類型資源包是0x00, 普通App包則是0x7f;02
表示typeID,用來表示資源類型,如drawable、layouts、anims、color、menu等;0000
表示EvtryID,指的是每個資源在對應的TypID中出現的順序。aapt生成的.arsc
資源文件對應咱們將apk解壓(apk本質是一個zip壓縮包)獲得的Resources.arsc,它實際上就是App的資源索引表。簡單來講,經過R.java文件與Resources.arsc就能夠定位到資源的內存地址。感興趣的能夠看看這篇博客數組
aapt 編譯源碼的入口在 frameworks/base/tools/aapt/Main.cpp
,其中對 assert文件夾路徑、res文件夾路徑、AndroidManifest文件等會採起不一樣的策略bash
對asset目錄下的資源不進行編譯,assets目錄下的資源會被原封不動的打入apk中,也就是說assets不會被壓縮;aapt會對res下drawable資源進行壓縮處理(raw目錄下除外)架構
aapt將文本xml資源文件編譯成二進制資源文件的方法buildResources函數在frameworks/base/tools/aapt/Resource.cpp
能夠找到,這裏着重關注了下overlay特性。併發
overlay是android中用來處理編譯時替換資源的一種方式。好比說咱們經過 aapt -S 命令指定了一個res路徑res1,這時候咱們再使用 -S 命令指定另外一個res路徑res2,若是res1 和res2 下的values/string.xml 都有對同一個String ID ,最後只會使用前面的(res1)描述。能夠理解爲overlay會以最早定義的路徑做爲基準包。
有一種狀況,假如咱們在res2下定義了一個資源Sting a,可是基準包沒有定義它,那麼就會報錯,這時候就能夠須要加入 --auto-add-overlay
,把新的資源都添加進去。
如下是aapt源碼中會進行buildResources的流程
status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
{
// First, look for a package file to parse. This is required to
// be able to generate the resource information.
sp<AaptGroup> androidManifestFile =
assets->getFiles().valueFor(String8("AndroidManifest.xml"));
if (androidManifestFile == NULL) {
fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
return UNKNOWN_ERROR;
}
status_t err = parsePackage(bundle, assets, androidManifestFile);
if (err != NO_ERROR) {
return err;
}
...
// apply the overlay files to the base set
if (!applyFileOverlay(bundle, assets, &drawables, "drawable") ||
!applyFileOverlay(bundle, assets, &layouts, "layout") ||
!applyFileOverlay(bundle, assets, &anims, "anim") ||
!applyFileOverlay(bundle, assets, &animators, "animator") ||
!applyFileOverlay(bundle, assets, &interpolators, "interpolator") ||
!applyFileOverlay(bundle, assets, &transitions, "transition") ||
!applyFileOverlay(bundle, assets, &xmls, "xml") ||
!applyFileOverlay(bundle, assets, &raws, "raw") ||
!applyFileOverlay(bundle, assets, &colors, "color") ||
!applyFileOverlay(bundle, assets, &menus, "menu") ||
!applyFileOverlay(bundle, assets, &fonts, "font") ||
!applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
return UNKNOWN_ERROR;
}
...
//preProcess & makeFileResources
...
// compile resources
// Finally, we can now we can compile XML files
}
複製代碼
在Gradle中能夠經過aaptOptions
的DSL來對aapt進行配置,在 Android.mk
語法中則有LOCAL_AAPT_FLAGS
配置項
這裏只是對aapt流程簡單的說明了下,具體的細節仍是蠻多的,從xml解析到具體類型的編譯流程、到二進制Manifest生成等細節都未展開,因爲本人c++能力有限,不少東西也沒看懂,也就不誤人子弟了。
若是有aidl文件,會經過aidl工具(源碼位於system/tools/aidl)打包成java接口類
AIDL(Android Interface Definition Language),是Android接口定義語言。目的是爲了方便實現進程間通訊,尤爲是在涉及多進程併發狀況下的進程間通訊。它的本質是對Binder通訊的封裝,對Binder通訊感興趣的同窗能夠看看Gityuan的Binder系列文章完全理解Android Binder通訊架構
R.java+工程源碼+aidl.java經過javac生成.class文件。
Javac 編譯過程大體能夠分爲3個階段
解析與填充符號表過程
解析的步驟包括詞法分析與語法分析兩個過程
詞法分析
是將源代碼的字符串流轉變爲標記(Token)集合,單個字符是程序編寫過程的最小元素,而標記是編譯過程的最小元素。關鍵字、變量名、運算符等均可以成爲標記
語法分析
是根據 Token 序列構造抽象語法樹的過程,抽象語法樹是一種用來描述程序代碼語法結構的樹形表示方式,語法樹的每個節點都表明程序中的一個語法結構,如包、類型、修飾符、接口等
插入式註解處理器的註解處理過程
插入式註解處理器,能夠讀取、修改、添加抽象語法樹中的任意元素。Android中的APT(Annotation Processing Tool)就是在這個階段工做的。
語義分析與字節碼生成過程
語法分析後,編譯器得到了程序代碼的抽象語法樹表示,語法樹能表示一個結構正確的源程序抽象,可是沒法保證源程序是符合邏輯的。而語義分析主要是對結構正確的進行上下文有關性質的審查。語義分析通常要經歷標註檢查、數據及控制流分析、解語法糖等過程,而後纔會走到javac編譯的最後一個階段:字節碼生成。大體流程以下:
標註檢查 -> 數據及控制流分析 -> 解語法糖 -> 字節碼生成
Javac 編譯動做的入口是 com.sun.tools.javac.main.JavaCompiler
類,主要邏輯集中在 compile()和 compile2()方法中,感興趣的能夠去看看
源碼.class文件和第三方jar或者library經過dx工具打包成dex文件。
上面生成的 .class 文件雖然已經能夠在 JVM 環境中運行,可是若是要在 Android 運行時環境中執行還須要特殊的處理,那就是 dx 處理,它會對 .class 文件進行翻譯、重構、解釋、壓縮等操做。
關於dex的操做,咱們瞭解最多的可能就是 Tinker 的熱修復方案了,Tinker 的基本思想是利用雙親委派原則,將patch的相關dex放在數組前端,保證classloader先到patch中查找加載。若是想要對細節更一步瞭解,如如何保證資源id不變,資源Diff是怎麼作的,對dex文件格式有所瞭解是必不可少的。關於dex的文件格式,官網有詳細介紹,這裏就不作說明了。
AndroidStudio有提供 proguard、D八、R8等工具來處理這一流程。Android 還會針對 Dalvik 虛擬機和 Art 虛擬機對dex進行優化
dexopt 是對 dex 文件 進行 verification 和 optimization 的操做,其對 dex 文件的優化結果變成了 odex 文件,這個文件和 dex 文件很像,只是使用了一些優化操做碼(譬如優化調用虛擬指令等)。
dex2oat 是對 dex 文件的 AOT 提早編譯操做,其須要一個 dex 文件,而後對其進行編譯,結果是一個本地可執行的 ELF 文件,能夠直接被本地處理器執行。
apkbuilder工具會將全部沒有編譯的資源、.arsc資源、.dex文件打包到一個完成apk文件中
jarsigner工具會對未簽名的apk驗證簽名。獲得一個簽名後的apk(signed.apk)
能夠經過在命令行中輸入jarsigner來獲取詳情信息,若是沒有特殊需求,使用下面命令便可完成簽名
jarsigner -verbose -keystore [私鑰存放路徑] -signedjar [簽名後文件存放路徑] [未簽名的文件路徑] [您的證書名稱]
zipAlign工具對6中的signed.apk進行對齊處理
所謂對齊,主要過程是將APK包中全部的資源文件距離文件起始偏移爲4字節整數倍,這樣經過內存映射訪問apk文件時的速度會更快。對齊的做用主要是爲了減小運行時內存的使用。
名稱 | 功能介紹 | 路徑 |
---|---|---|
aapt | Android資源打包工具 | ${ANDROID_SDK_HOME}/platform-tools/appt |
aidl | Android接口描述語言轉化爲.java文件的工具 | ${ANDROID_SDK_HOME}/platform-tools/aidl |
javac | Java Compiler | ${JDK_HOME}/javac |
dex | 轉化.class文件爲Davik VM能識別的.dex文件 | ${ANDROID_SDK_HOME}/platform-tools/dx |
apkbuilder | 生成apk包 | ${ANDROID_SDK_HOME}/tools/opkbuilder |
jarsigner | .jar文件的簽名工具 | ${JDK_HOME}/jarsigner |
zipalign | 字節碼對齊工具 | ${ANDROID_SDK_HOME}/tools/zipalign |