10分鐘瞭解Android專案構建流程

Jensen95發表於2019-03-03

前言

上兩篇部落格中提到了構建過程的問題,之前畢業在準備面試的過程中,對這個部分有過較為認真的學習,也進行了部落格記錄,但是實際工作過程中,如果是在寫業務邏輯上,那麼這方面的問題接觸的就會比較少了。逐漸的淡忘了,其次,之前所寫的文章條理性也不是很強,同時,最近準備進行Gradle外掛的一系列部落格的產出,其中將會涉及到很多與專案構建相關的內容。所以此文也將成為後續文章的一個鋪墊。

構建過程

專案的構建: 當我們開啟一個專案,我們可以看到的是我們寫的Java Code檔案or Other JVM Code,資原始檔,Build配置檔案,但是通過run the project,我們就可以得到一個在我們的Andoid裝置上可以執行的Apk,上線應用市場,還需要我們對其進行簽名處理,來確保我們App的唯一性和安全性。整個過程就是所謂的專案構建。

如何實現整個構建的過程,對於每一個構建的步驟,都需要相應的功能模組來進行,比如Java Code編譯,如何打成dex包等等,而這Android則為我們提供了相應的工具,在Android Studio命令列視窗中,我們可以通過相應的命令列來進行控制,但是,整個構建過程涉及到很多的步驟,很多的工具的使用,如果都通過命令列來進行控制,勢必會相當麻煩,因此Androd Studio等IDE則對整個過程進行了一個打包,當我們在Run project的時候,底層的打包工具就會被呼叫,打包流程都會自動執行。然後我們只需要對構建檔案按照自己的需求進行相應的配置,就可以構建出自己所需要的專案。

那麼,整個Andoid專案的構建過程中,都執行了那些構建的任務呢?

首先看一下,Google官方為我們提供的詳細的構建過程圖

構建過程概述

如果你接觸Android開發已經有一段時間了,我想當你看到這張圖的時候,就會覺得很清晰。但是更多的可能會一頭霧水,如果之前沒有閱讀相關的資料的話,那麼,接下來,將針對上述的構建過程,先給出一個概述,這樣你將會整個構建流程在心中有一個框架,然後針對其中具體的細節,進行進一步詳細的講解。

圖中綠色標註為其中用到的相應工具,藍色代表的是中間生成的各類檔案型別。

  • 首先aapt工具會將資原始檔進行轉化,生成對應資源ID的R檔案和資原始檔。
  • adil工具會將其中的aidl介面轉化成Java的介面
  • 至此,Java Compiler開始進行Java檔案向class檔案的轉化,將R檔案,Java原始碼,由aidl轉化來的Java介面,統一轉化成.class檔案。
  • 通過dx工具將class檔案轉化為dex檔案。
  • 此時我們得到了經過處理後的資原始檔和一個dex檔案,當然,還會存在一些其它的資原始檔,這個時候,就是將其打包成一個類似apk的檔案。但還並不是直接可以安裝在Android系統上的APK檔案。
  • 通過簽名工具對其進行簽名。
  • 通過Zipalign進行優化,提升執行速度(原理後文會提及)。
  • 最終,一個可以安裝在我們手機上的APK了。

通過上述講解,我想對於Android專案的整個構建過程,應該有了一個很清晰的框架了,下面將針對其中的具體的細節,和前面挖的一些坑,來進行更細緻的分析,下圖是一個Android專案構建過程的詳細步驟圖。

詳細構建過程

接下來的分析,我們還是按照上述構建過程概述的順序和流程,進行具體的分析。

第1步:aapt打包資原始檔,生成R.java和編譯後的資源(二進位制檔案)

講到資原始檔的處理,我們先來看一下Android中的資原始檔有那些呢?Android應用程式資源可以分為兩大類,分別是assets和res:
   1. assets類資源放在工程根目錄的assets子目錄下,它裡面儲存的是一些原始的檔案,可以以任何方式來進行組織。這些檔案最終會被原裝不動地打包在apk檔案中。如果我們要在程式中訪問這些檔案,那麼就需要指定檔名來訪問。例如,假設在assets目錄下有一個名稱為filename的檔案,那麼就可以使用以下程式碼來訪問它:

AssetManager am= getAssets();    
InputStream is = assset.open("filename");  
複製程式碼

2. res類資源放在工程根目錄的res子目錄下,它裡面儲存的檔案大多數都會被編譯,並且都會被賦予資源ID。這樣我們就可以在程式中通過ID來訪問res類的資源。res類資源按照不同的用途可以進一步劃分為以下10種子型別:
layout(佈局檔案),drawable,xml,value,menu,raw,color,anim,animator,mipmap。
為了使得一個應用程式能夠在執行時同時支援不同的大小和密度的螢幕,以及支援國際化,即支援不同的國家地區和語言,Android應用程式資源的組織方式有18個維度,每一個維度都代表一個配置資訊,從而可以使得應用程式能夠根據裝置的當前配置資訊來找到最匹配的資源來展現在UI上,從而提高使用者體驗。由於Android應用程式資源的組織方式可以達到18個維度,因此就要求Android資源管理框架能夠快速定位最匹配裝置當前配置資訊的資源來展現在UI上,否則的話,就會影響使用者體驗。為了支援Android資源管理框架快速定位最匹配資源,Android資源打包工具aapt在編譯和打包資源的過程中,會執行以下兩個額外的操作:

  • 賦予每一個非assets資源一個ID值,這些ID值以常量的形式定義在一個R.java檔案中。
  • 生成一個resources.arsc檔案,用來描述那些具有ID值的資源的配置資訊,它的內容就相當於是一個資源索引表。包含了所有的id值的資料集合。在該檔案中,如果某個id對應的是string,那麼該檔案會直接包含該值,如果id對應的資源是某個layout或者drawable資源,那麼該檔案會存入對應資源的路徑。

為什麼要轉化為二進位制檔案?

  • 二進位制格式的XML檔案佔用空間更小。這是由於所有XML元素的標籤、屬性名稱、屬性值和內容所涉及到的字串都會被統一收集到一個字串資源池中去,並且會去重。有了這個字串資源池,原來使用字串的地方就會被替換成一個索引到字串資源池的整數值,從而可以減少檔案的大小。
  • 二進位制格式的XML檔案解析速度更快。這是由於二進位制格式的XML元素裡面不再包含有字串值,因此就避免了進行字串解析,從而提高速度。
    有了資源ID以及資源索引表之後,Android資源管理框架就可以迅速將根據裝置當前配置資訊來定位最匹配的資源了。

對於具體的一些操作流程,可以參考本人之前的一篇文章APK打包安裝過程或者更偏向於原始碼層級的老羅的文章。(文後參考文獻連結)

第2步:aidl

aidl,全名Android Interface Definition Language,即Android介面定義語言。是我們在編寫程式間通訊的程式碼的時候,定義的介面。
輸入:aidl字尾的檔案。輸出:可用於程式通訊的C/S端java程式碼,位於build/generated/source/aidl。

第3步:Java原始碼編譯

我們有了R.java和aidl生成的Java檔案,再加上工程的原始碼,現在可以使用javac進行正常的java編譯生成class檔案了。

輸入:java source的資料夾(另外還包括了build/generated下的:R.java, aidl生成的java檔案,以及BuildConfig.java)。輸出:對於gradle編譯,可以在build/intermediates/classes裡,看到輸出的class檔案。

第4步:程式碼混淆(proguard)

原始碼編譯之後,我們可能還會對其進行程式碼的混淆,混淆的作用是增加反編譯的難度,同時也將一些程式碼的命名進行了縮短,減少程式碼佔用的空間。混淆完成之後,會生成一個混淆前後的對映表,這個是用來在反應我們的應用執行的時候的一些堆疊資訊,可以將混淆後的資訊轉化為我們混淆前實際程式碼中的內容。
而這個過程使用的工具就是ProGuard,是一個開源的Java程式碼混淆器(obfuscation)。ADT r8開始它被預設整合到了Android SDK中。 其具備三個主要功能。

  • 壓縮 – 移除無效的類、屬性、方法等
  • 優化 – 優化bytecode移除沒用的結構
  • 混淆 – 把類名、屬性名、方法名替換為晦澀難懂的1到2個字母的名字
    當然它也只能混淆Java程式碼,Android工程中Native程式碼,資原始檔(圖片、xml),它是無法混淆的。而且對於Java的常量值也是無法混淆的,所以不要使用常量定義平文的密碼等重要資訊。同時對於混淆,我們可以通過程式碼制定去混淆那些,不去混淆那些。
-keep public class com.rensanning.example.Test
複製程式碼

第5步:轉化為dex

呼叫dx.bat將所有的class檔案轉化為classes.dex檔案,dx會將class轉換為Dalvik位元組碼,生成常量池,消除冗餘資料等。由於dalvik是一種針對嵌入式裝置而特殊設計的java虛擬機器,所以dex檔案與標準的class檔案在結構設計上有著本質的區別,當java程式編譯成class後,使用dx工具將所有的class檔案整合到一個dex檔案,目的是其中各個類能夠共享資料,在一定程度上降低了冗餘,同時也是檔案結構更加經湊,實驗表明,dex檔案是傳統jar檔案大小的50%左右。class檔案結構和dex檔案結構比對。

Dex和Class比對

第6步:apkbuilder

打包生成APK檔案。舊的apkbuilder指令碼已經廢棄,現在都已經通過sdklib.jar的ApkBuilder類進行打包了。輸入為我們之前生成的包含resources.arcs的.ap_檔案,上一步生成的dex檔案,以及其他資源如jni、.so檔案。
大致步驟為
以包含resources.arcs的.ap_檔案為基礎,new一個ApkBuilder,設定debugMode

apkBuilder.addZipFile(f);
apkBuilder.addSourceFolder(f);
apkBuilder.addResourcesFromJar(f);
apkBuilder.addNativeLibraries(nativeFileList);
apkBuilder.sealApk(); // 關閉apk檔案
generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath());

複製程式碼

第7步:對APK簽名

對APK檔案進行簽名。Android系統在安裝APK的時候,首先會檢驗APK的簽名,如果發現簽名檔案不存在或者校驗簽名失敗,則會拒絕安裝,所以應用程式在釋出之前一定要進行簽名。簽名資訊中包含有開發者資訊,在一定程度上可以防止應用被偽造。對一個APK檔案簽名之後,APK檔案根目錄下會增加META-INF目錄,該目錄下增加三個檔案:

  • MANIFEST.MF
  • [CERT].RSA
  • [CERT]

Android系統就是根據這三個檔案的內容對APK檔案進行簽名檢驗的。簽名過程主要利用apksign.jar或者jarsinger.jar兩個工具。將根據我們提供的Debug和Release兩個版本的Keystore進行相應的簽名。

MANIFEST.MF中包含對apk中除了/META-INF資料夾外所有檔案的簽名值,簽名方法是先SHA1()(或其他hash方法)在base64()。儲存形式是:Name加[SHA1]-Digest。

[CERT].SF是對MANIFEST.MF檔案整體簽名以及其中各個條目的簽名。一般地,如果是使用工具簽名,還多包括一項。就是對MANIFEST.MF頭部資訊的簽名。

[CERT].RSA包含用私鑰對[CERT].SF的簽名以及包含公鑰資訊的數字證照。

第8步:zipalign優化

Zipalign是一個Android平臺上整理APK檔案的工具,它首次被引入是在Android 1.6版本的SDK軟體開發工具包中。它能夠對打包的Android應用程式進行優化, 以使Android作業系統與應用程式之間的互動作用更有效率,這能夠讓應用程式和整個系統執行得更快。用Zipalign處理過的應用程式執行時間達到最低限度,當裝置執行APK應用程式時佔更少的RAM。

  • Zipalign如何進行優化的呢?

呼叫buildtoolszipalign,對簽名後的APK檔案進行對齊處理,使APK中所有資原始檔距離檔案起始偏移為4位元組的整數倍,從而在通過記憶體對映訪問APK檔案時會更快。同時也減少了在裝置上執行時的記憶體消耗。如果對於為何提速不理解,那麼可以看下記憶體對齊的規則以及作用該篇文章,對於記憶體對齊的好處有比較生動詳細的解釋。最終這樣我們的APK就生成完畢了。

典型的APK中內容

  • AndroidManifest.xml 程式全域性配置檔案
  • classes.dex Dalvik位元組碼
  • resources.arsc 資源索引表
  • META-INF該目錄下存放的是簽名資訊
  • res 該目錄存放資原始檔
  • assets該目錄可以存放一些配置或資原始檔

總結

至此,對於Andoid專案構建過程的分析已經完成,當然,並沒與深入到原始碼層級的分析,本文的旨在對於構建過程流程上的瞭解和其中一些優化的原因所在,為後續通過Gradle外掛hook構建過程來做一定的操作,做一個鋪墊。

參考文章

Android APK 簽名原理及方法

改善android效能工具篇【zipalign】

Android應用程式資源的編譯和打包過程分析

Android資源管理框架(AssetManager)簡要介紹和學習計劃

Android程式碼混淆之ProGuard

相關文章