淺談Android打包流程

joychic發表於2019-05-24

作為個Android developer ,對APK(AndroidPackage)想必是再熟悉不過的了。我們在 Gradle 中點選下 build 或者通過命令列 gradlew tasks,AndroidStudio 就會開始執行構建流程,最終輸出APK檔案。 這件事我經常幹,也習以為常了,但是有時也會偶爾想想,那一串串程式碼是如何變成 apk 的呢,期間經歷了那些流程呢?

構建流程總覽

先放張官網的構建流程圖

淺談Android打包流程

典型 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打包流程圖。接下來會將基於下圖的流程進行簡單分析。

淺談Android打包流程

1. 打包資原始檔

資原始檔(res資料夾下的檔案)通過 AAPT(Android Asset Packaging Tool)打包生成R.java類(資源索引表)以及.arsc資原始檔。

經過aapt生成的R檔案佔4個位元組

public static final int design_appbar_state_list_animator=0x7f020000;

  • 第一位位元組0x7f表示packageID,用來限定資源的來源。系統資源包是ox01,SharedLibrary型別資源包是0x00, 普通App包則是0x7f;
  • 次一位位元組02表示typeID,用來表示資源型別,如drawable、layouts、anims、color、menu等;
  • 後2位元組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檔案等會採取不同的策略

對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++能力有限,很多東西也沒看懂,也就不誤人子弟了。

2. 處理 aidl files

如果有aidl檔案,會通過aidl工具(原始碼位於system/tools/aidl)打包成java介面類

AIDL(Android Interface Definition Language),是Android介面定義語言。目的是為了方便實現程式間通訊,尤其是在涉及多程式併發情況下的程式間通訊。它的本質是對Binder通訊的封裝,對Binder通訊感興趣的同學可以看看Gityuan的Binder系列文章徹底理解Android Binder通訊架構

3. 編譯(Compilers)

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()方法中,感興趣的可以去看看

4. dex(生成dex檔案)

原始碼.class檔案和第三方jar或者library通過dx工具打包成dex檔案。

上面生成的 .class 檔案雖然已經可以在 JVM 環境中執行,但是如果要在 Android 執行時環境中執行還需要特殊的處理,那就是 dx 處理,它會對 .class 檔案進行翻譯、重構、解釋、壓縮等操作。

關於dex的操作,我們瞭解最多的可能就是 Tinker 的熱修復方案了,Tinker 的基本思想是利用雙親委派原則,將patch的相關dex放在陣列前端,保證classloader先到patch中查詢載入。如果想要對細節更一步瞭解,如如何保證資源id不變,資源Diff是怎麼做的,對dex檔案格式有所瞭解是必不可少的。關於dex的檔案格式,官網有詳細介紹,這裡就不做說明了。

AndroidStudio有提供 proguard、D8、R8等工具來處理這一流程。Android 還會針對 Dalvik 虛擬機器和 Art 虛擬機器對dex進行優化

淺談Android打包流程

  • dexopt 是對 dex 檔案 進行 verification 和 optimization 的操作,其對 dex 檔案的優化結果變成了 odex 檔案,這個檔案和 dex 檔案很像,只是使用了一些優化操作碼(譬如優化呼叫虛擬指令等)。

  • dex2oat 是對 dex 檔案的 AOT 提前編譯操作,其需要一個 dex 檔案,然後對其進行編譯,結果是一個本地可執行的 ELF 檔案,可以直接被本地處理器執行。

5. apkbuilder(生成未簽名apk)

apkbuilder工具會將所有沒有編譯的資源、.arsc資源、.dex檔案打包到一個完成apk檔案中

6. Jarsigner(簽名)

jarsigner工具會對未簽名的apk驗證簽名。得到一個簽名後的apk(signed.apk)

可以通過在命令列中輸入jarsigner來獲取詳情資訊,如果沒有特殊需求,使用下面命令即可完成簽名

jarsigner -verbose -keystore [私鑰存放路徑] -signedjar [簽名後檔案存放路徑] [未簽名的檔案路徑] [您的證照名稱]

7. zipalign(對齊)

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

相關文章