1. 程式人生 > >理解 Android Build 系統

理解 Android Build 系統

Android Build 系統是 Android 原始碼的一部分。關於如何獲取 Android 原始碼,請參照 Android Source 官方網站:

Android Build 系統用來編譯 Android 系統,Android SDK 以及相關文件。該系統主要由 Make 檔案,Shell 指令碼以及 Python 指令碼組成,其中最主要的是 Make 檔案。

眾所周知,Android 是一個開源的作業系統。Android 的原始碼中包含了大量的開源專案以及許多的模組。不同產商的不同裝置對於 Android 系統的定製都是不一樣的。

如何將這些專案和模組的編譯統一管理起來,如何能夠在不同的作業系統上進行編譯,如何在編譯時能夠支援面向不同的硬體裝置,不同的編譯型別,且還要提供面向各個產商的定製擴充套件,是非常有難度的。

但 Android Build 系統很好的解決了這些問題,這裡面有很多值得我們開發人員學習的地方。

對於 Android 平臺開發人員來說,本文可以幫助你熟悉你每天接觸到的構建環境。

對於其他開發人員來說,本文可以作為一個 GNU Make 的使用案例,學習這些成功案例,可以提升我們的開發經驗。

概述

Build 系統中最主要的處理邏輯都在 Make 檔案中,而其他的指令碼檔案只是起到一些輔助作用,由於篇幅所限,本文只探討 Make 檔案中的內容。

整個 Build 系統中的 Make 檔案可以分為三類:

第一類是 Build 系統核心檔案,此類檔案定義了整個 Build 系統的框架,而其他所有 Make 檔案都是在這個框架的基礎上編寫出來的。

圖 1 是 Android 原始碼樹的目錄結構,Build 系統核心檔案全部位於 /build/core(本文所提到的所有路徑都是以 Android 原始碼樹作為背景的,“/”指的是原始碼樹的根目錄,與檔案系統無關)目錄下。

圖 1. Android 原始碼樹的目錄結構
圖 1. Android 原始碼樹的目錄結構

第二類是針對某個產品(一個產品可能是某個型號的手機或者平板電腦)的 Make 檔案,這些檔案通常位於 device 目錄下,該目錄下又以公司名以及產品名分為兩級目錄,圖 2 是 device 目錄下子目錄的結構。對於一個產品的定義通常需要一組檔案,這些檔案共同構成了對於這個產品的定義。例如,/device/sony/it26 目錄下的檔案共同構成了對於 Sony LT26 型號手機的定義。

圖 2. device 目錄下子目錄的結構
圖 2. device 目錄下子目錄的結構

第三類是針對某個模組(關於模組後文會詳細討論)的 Make 檔案。整個系統中,包含了大量的模組,每個模組都有一個專門的 Make 檔案,這類檔案的名稱統一為“Android.mk”,該檔案中定義瞭如何編譯當前模組。Build 系統會在整個原始碼樹中掃描名稱為“Android.mk”的檔案並根據其中的內容執行模組的編譯。

編譯 Android 系統

執行編譯

在完成編譯環境的準備工作以及獲取到完整的 Android 原始碼之後,想要編譯出整個 Android 系統非常的容易:

開啟控制檯之後轉到 Android 原始碼的根目錄,然後執行如清單 1 所示的三條命令即可("$"是命令提示符,不是命令的一部分。):

完整的編譯時間依賴於編譯主機的配置,在筆者的 Macbook Pro(OS X 10.8.2, i7 2G CPU,8G RAM, 120G SSD)上使用 8 個 Job 同時編譯共需要一個半小時左右的時間。

清單 1. 編譯 Android 系統
 $ source build/envsetup.sh 
 $ lunch full-eng 
 $ make -j8

這三行命令的說明如下:

第一行命令“source build/envsetup.sh”引入了 build/envsetup.sh指令碼。該指令碼的作用是初始化編譯環境,並引入一些輔助的 Shell 函式,這其中就包括第二步使用 lunch 函式。

除此之外,該檔案中還定義了其他一些常用的函式,它們如表 1 所示:

表 1. build/envsetup.sh 中定義的常用函式
名稱 說明
croot 切換到原始碼樹的根目錄
m 在原始碼樹的根目錄執行 make
mm Build 當前目錄下的模組
mmm Build 指定目錄下的模組
cgrep 在所有 C/C++ 檔案上執行 grep
jgrep 在所有 Java 檔案上執行 grep
resgrep 在所有 res/*.xml 檔案上執行 grep
godir 轉到包含某個檔案的目錄路徑
printconfig 顯示當前 Build 的配置資訊
add_lunch_combo 在 lunch 函式的選單中新增一個條目

第二行命令“lunch full-eng”是呼叫 lunch 函式,並指定引數為“full-eng”。lunch 函式的引數用來指定此次編譯的目標裝置以及編譯型別。在這裡,這兩個值分別是“full”和“eng”。“full”是 Android 原始碼中已經定義好的一種產品,是為模擬器而設定的。而編譯型別會影響最終系統中包含的模組,關於編譯型別將在表 7 中詳細講解。

如果呼叫 lunch 函式的時候沒有指定引數,那麼該函式將輸出列表以供選擇,該列表類似圖 3 中的內容(列表的內容會根據當前 Build 系統中包含的產品配置而不同,具體參見後文“新增新的產品”),此時可以通過輸入編號或者名稱進行選擇。

圖 3. lunch 函式的輸出
圖 3. lunch 函式的輸出

第三行命令“make -j8”才真正開始執行編譯。make 的引數“-j”指定了同時編譯的 Job 數量,這是個整數,該值通常是編譯主機 CPU 支援的併發執行緒總數的 1 倍或 2 倍(例如:在一個 4 核,每個核支援兩個執行緒的 CPU 上,可以使用 make -j8 或 make -j16)。在呼叫 make 命令時,如果沒有指定任何目標,則將使用預設的名稱為“droid”目標,該目標會編譯出完整的 Android 系統映象。

Build 結果的目錄結構

所有的編譯產物都將位於 /out 目錄下,該目錄下主要有以下幾個子目錄:

  • /out/host/:該目錄下包含了針對主機的 Android 開發工具的產物。即 SDK 中的各種工具,例如:emulator,adb,aapt 等。
  • /out/target/common/:該目錄下包含了針對裝置的共通的編譯產物,主要是 Java 應用程式碼和 Java 庫。
  • /out/target/product/<product_name>/:包含了針對特定裝置的編譯結果以及平臺相關的 C/C++ 庫和二進位制檔案。其中,<product_name>是具體目標裝置的名稱。
  • /out/dist/:包含了為多種分發而準備的包,通過“make disttarget”將檔案拷貝到該目錄,預設的編譯目標不會產生該目錄。

Build 生成的映象檔案

Build 的產物中最重要的是三個映象檔案,它們都位於 /out/target/product/<product_name>/ 目錄下。

這三個檔案是:

  • system.img:包含了 Android OS 的系統檔案,庫,可執行檔案以及預置的應用程式,將被掛載為根分割槽。
  • ramdisk.img:在啟動時將被 Linux 核心掛載為只讀分割槽,它包含了 /init 檔案和一些配置檔案。它用來掛載其他系統映象並啟動 init 程序。
  • userdata.img:將被掛載為 /data,包含了應用程式相關的資料以及和使用者相關的資料。

Make 檔案說明

整個 Build 系統的入口檔案是原始碼樹根目錄下名稱為“Makefile”的檔案,當在原始碼根目錄上呼叫 make 命令時,make 命令首先將讀取該檔案。

Makefile 檔案的內容只有一行:“include build/core/main.mk”。該行程式碼的作用很明顯:包含 build/core/main.mk 檔案。在 main.mk 檔案中又會包含其他的檔案,其他檔案中又會包含更多的檔案,這樣就引入了整個 Build 系統。

這些 Make 檔案間的包含關係是相當複雜的,圖 3 描述了這種關係,該圖中黃色標記的檔案(且除了 $開頭的檔案)都位於 build/core/ 目錄下。

圖 4. 主要的 Make 檔案及其包含關係
圖 4. 主要的 Make 檔案及其包含關係

表 2 總結了圖 4 中提到的這些檔案的作用:

表 2. 主要的 Make 檔案的說明
檔名 說明
main.mk 最主要的 Make 檔案,該檔案中首先將對編譯環境進行檢查,同時引入其他的 Make 檔案。另外,該檔案中還定義了幾個最主要的 Make 目標,例如 droid,sdk,等(參見後文“Make 目標說明”)。
help.mk 包含了名稱為 help 的 Make 目標的定義,該目標將列出主要的 Make 目標及其說明。
pathmap.mk 將許多標頭檔案的路徑通過名值對的方式定義為對映表,並提供 include-path-for 函式來獲取。例如,通過$(call include-path-for, frameworks-native)便可以獲取到 framework 原生代碼需要的標頭檔案路徑。
envsetup.mk 配置 Build 系統需要的環境變數,例如:TARGET_PRODUCT,TARGET_BUILD_VARIANT,HOST_OS,HOST_ARCH 等。
當前編譯的主機平臺資訊(例如作業系統,CPU 型別等資訊)就是在這個檔案中確定的。
另外,該檔案中還指定了各種編譯結果的輸出路徑。
combo/select.mk 根據當前編譯器的平臺選擇平臺相關的 Make 檔案。
dumpvar.mk 在 Build 開始之前,顯示此次 Build 的配置資訊。
config.mk 整個 Build 系統的配置檔案,最重要的 Make 檔案之一。該檔案中主要包含以下內容:
  • 定義了許多的常量來負責不同型別模組的編譯。
  • 定義編譯器引數以及常見檔案字尾,例如 .zip,.jar.apk。
  • 根據 BoardConfig.mk 檔案,配置產品相關的引數。
  • 設定一些常用工具的路徑,例如 flex,e2fsck,dx。
definitions.mk 最重要的 Make 檔案之一,在其中定義了大量的函式。這些函式都是 Build 系統的其他檔案將用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package 等,關於這些函式的說明請參見每個函式的程式碼註釋。
distdir.mk 針對 dist 目標的定義。dist 目標用來拷貝檔案到指定路徑。
dex_preopt.mk 針對啟動 jar 包的預先優化。
pdk_config.mk 顧名思義,針對 pdk(Platform Developement Kit)的配置檔案。
${ONE_SHOT_MAKEFILE} ONE_SHOT_MAKEFILE 是一個變數,當使用“mm”編譯某個目錄下的模組時,此變數的值即為當前指定路徑下的 Make 檔案的路徑。
${subdir_makefiles} 各個模組的 Android.mk 檔案的集合,這個集合是通過 Python 指令碼掃描得到的。
post_clean.mk 在前一次 Build 的基礎上檢查當前 Build 的配置,並執行必要清理工作。
legacy_prebuilts.mk 該檔案中只定義了 GRANDFATHERED_ALL_PREBUILT 變數。
Makefile 被 main.mk 包含,該檔案中的內容是輔助 main.mk 的一些額外內容。

Android 原始碼中包含了許多的模組,模組的型別有很多種,例如:Java 庫,C/C++ 庫,APK 應用,以及可執行檔案等 。並且,Java 或者 C/C++ 庫還可以分為靜態的或者動態的,庫或可執行檔案既可能是針對裝置(本文的“裝置”指的是 Android 系統將被安裝的裝置,例如某個型號的手機或平板)的也可能是針對主機(本文的“主機”指的是開發 Android 系統的機器,例如裝有 Ubuntu 作業系統的 PC 機或裝有 MacOS 的 iMac 或 Macbook)的。不同型別的模組的編譯步驟和方法是不一樣,為了能夠一致且方便的執行各種型別模組的編譯,在 config.mk 中定義了許多的常量,這其中的每個常量描述了一種型別模組的編譯方式,這些常量有:

  • BUILD_HOST_STATIC_LIBRARY
  • BUILD_HOST_SHARED_LIBRARY
  • BUILD_STATIC_LIBRARY
  • BUILD_SHARED_LIBRARY
  • BUILD_EXECUTABLE
  • BUILD_HOST_EXECUTABLE
  • BUILD_PACKAGE
  • BUILD_PREBUILT
  • BUILD_MULTI_PREBUILT
  • BUILD_HOST_PREBUILT
  • BUILD_JAVA_LIBRARY
  • BUILD_STATIC_JAVA_LIBRARY
  • BUILD_HOST_JAVA_LIBRARY

通過名稱大概就可以猜出每個變數所對應的模組型別。(在模組的 Android.mk 檔案中,只要包含進這裡對應的常量便可以執行相應型別模組的編譯。對於 Android.mk 檔案的編寫請參見後文:“新增新的模組”。)

這些常量的值都是另外一個 Make 檔案的路徑,詳細的編譯方式都是在對應的 Make 檔案中定義的。這些常量和 Make 檔案的是一一對應的,對應規則也很簡單:常量的名稱是 Make 檔案的檔名除去後綴全部改為大寫然後加上“BUILD_”作為字首。例如常量 BUILD_HOST_PREBUILT 的值對應的檔案就是 host_prebuilt.mk。

這些 Make 檔案的說明如表 3 所示:

表 3. 各種模組的編譯方式的定義檔案
檔名 說明
host_static_library.mk 定義瞭如何編譯主機上的靜態庫。
host_shared_library.mk 定義瞭如何編譯主機上的共享庫。
static_library.mk 定義瞭如何編譯裝置上的靜態庫。
shared_library.mk 定義瞭如何編譯裝置上的共享庫。
executable.mk 定義瞭如何編譯裝置上的可執行檔案。
host_executable.mk 定義瞭如何編譯主機上的可執行檔案。
package.mk 定義瞭如何編譯 APK 檔案。
prebuilt.mk 定義瞭如何處理一個已經編譯好的檔案 ( 例如 Jar 包 )。
multi_prebuilt.mk 定義瞭如何處理一個或多個已編譯檔案,該檔案的實現依賴 prebuilt.mk。
host_prebuilt.mk 處理一個或多個主機上使用的已編譯檔案,該檔案的實現依賴 multi_prebuilt.mk。
java_library.mk 定義瞭如何編譯裝置上的共享 Java 庫。
static_java_library.mk 定義瞭如何編譯裝置上的靜態 Java 庫。

相關推薦

深入理解Android Build系統

概述 Android Build 系統是用來編譯 Android 系統、Android SDK 以及相關文件的一套框架。在Android系統中,Android 的原始碼中包含了許許多多的模組。 不同產商的不同裝置對於 Android 系統的定製都是不一樣的。如

理解 Android Build 系統

Android Build 系統是 Android 原始碼的一部分。關於如何獲取 Android 原始碼,請參照 Android Source 官方網站: Android Build 系統用來編譯 Android 系統,Android SDK 以及相關文件。

《深入理解Android 卷III》第五章 深入理解Android輸入系統

《深入理解Android 卷III》即將釋出,作者是張大偉。此書填補了深入理解Android Framework卷中的一個主要空白,即Android Framework中和UI相關的部分。在一個特別講究顏值的時代,本書分析了Android 4.2中WindowManagerS

Android build系統中常用LOCAL_變數

 以下內容節選自本書 編寫模組的編譯檔案,實際就是定義一系列以“LOCAL_”開頭的編譯變數,因此我們有必要弄明白這些變數的具體含義。下面是一些經常使用的LOCAL_編譯變數的說明: 變數名 說明 LOCAL_ASSET_FILES 編譯APK檔案時用於指定資源列表,通常寫成 LOCAL_

android soong build 系統

Android soong build系統介紹https://www.jianshu.com/p/80013a768a45android blueprint介紹https://www.jianshu.com/p/32c9d2b04a7bAndroid blueprint 程式

Android編譯系統詳解(一)——build/envsetup.sh

http://www.cloudchou.com/android/post-134.html 準備好編譯環境後,編譯Rom的第一步是 source build/envsetup.sh,該步驟把envsetup.sh裡的函式宣告為當前會話終端可用的命令。這些命令能讓我們

深入理解 Android 系統升級

前言 2013年7月至2015年6月在長虹擔任Android系統研發工程師,主要負責長虹智慧電視升級(OTA升級),研發平臺是MST 628 和 MTK 5327等。 摘要 隨著Android系統的快速發展,越來越多的智慧終端裝置搭載Android平臺

Android開發之深入理解Android 7.0系統許可權更改相關文件

摘要: Android 6.0之後的版本增加了執行時許可權,應用程式在執行每個需要系統許可權的功能時,需要新增許可權請求程式碼(預設許可權禁止),否則應用程式無法響應;Android 7.0在Android 6.0的基礎上,對系統許可權進一步更改,這次的許可權更改包括三個方

Android build system:構建系統的組成及其原理

Android build system 組成部分 Android build system 的組成部分:Gradle + Android plugin for Gradle android app打包流程(即構建流程): Gra

深入理解Android訊息處理系統——Looper、Handler、Thread

熟悉Windows程式設計的朋友可能知道Windows程式是訊息驅動的,並且有全域性的訊息迴圈系統。而Android應用程式也是訊息驅動的,按道理來說也應該提供訊息迴圈機制。實際上谷歌參考了Windows的訊息迴圈機制,也在Android系統中實現了訊息迴圈機制。Android通過Looper、Handl

android編譯系統分析(一)source build/envsetup.sh與lunch

雖然已經有很多人分析過Android的編譯系統的程式碼了,我也看過他們的部落格,也學到了不少知識,但單純的看別人分析,終究還是理解的不深入,所以,我還是要自己再認真的分析一遍。 想想我們編譯android系統的過程: 首先:source build/envsetup

轉:輕松理解 Android Binder,只需要讀這一篇

native 線程同步 ntp 並不是 crud 響應 抽象 過程 開源 轉自http://www.jianshu.com/p/bdef9e3178c9 在 Android 系統中,Binder 起著非常重要的作用,它是整個系統 IPC 的基石。網上已經有很多文章講述

理解Android進程創建流程(轉)

object c mman appdata sel failed scrip sca emp 不足 /frameworks/base/core/java/com/android/internal/os/ - ZygoteInit.java - Zygote

理解Android線程創建流程(轉)

ttr cal 創建失敗 指向 ear long readn nbsp bar /android/libcore/libart/src/main/java/java/lang/Thread.java /art/runtime/native/java_lang_Thread

com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: android/support/annotation/ColorRes.class

導致 .class div src jar 技術 cnblogs 沖突 信息 保存信息如上: 我在添加一個支持庫的時候遇的問題,這個庫com.yanzhenjie:album:1.0.5 這是由於v4包重復導致的,在網上我也找過多種解決方案 用了這種,方式 confi

打造一個全命令行的Android構建系統

命令 匹配 pda 符號鏈接 創建 ack https vim ott IDE都是給小白程序猿的,大牛級別的程序猿一定是命令行控,終端控,你看大牛都是使用vim,emacs 就一切搞定” 這話說的盡管有些絕對。可是也不無道理。做開發這行要想效率高,自己主動化還真是

Android常用系統廣播

參考 -s locale pro ebo heads use man 重啟 查看詳情://關閉或打開飛行模式時的廣播Intent.ACTION_AIRPLANE_M;//充電狀態,或者電池的電量發生變化;//電池的充電狀態、電荷級別改變,不能通過組建聲;Intent.ACT

Android View系統解析(下)

target 過程 getc ets 解包 有時 消息隊列 nbsp 實現 轉載請註明出處:http://blog.csdn.net/singwhatiwanna/article/details/38426471(來自singwhatiwanna的csdn博客)Andr

Android面試題3之描寫敘述下Android系統架構

都是 csdn 進行 功能 驅動程序 libraries sso 封裝 rar 描寫敘述下Android的系統架構: Android系統從下往上分為Linux內核層(linux kerner),執行庫(runtime library),應用程序框架層,

Android sensor 系統框架 (二)

port amp cap 錯誤 str 註釋 hardware war cas 連載上一篇http://www.cnblogs.com/hackfun/p/7327320.html (D) 如何加載訪問.so庫 在前一篇博客http://www.cnblogs.co