理解 Android Build 系统
Android Build 系統(tǒng)是用來(lái)編譯 Android 系統(tǒng),Android SDK 以及相關(guān)文檔的一套框架。眾所周知,Android 是一個(gè)開源的操作系統(tǒng)。Android 的源碼中包含了許許多多的模塊。 不同產(chǎn)商的不同設(shè)備對(duì)于 Android 系統(tǒng)的定制都是不一樣的。如何將這些模塊統(tǒng)一管理起來(lái),如何能夠在不同的操作系統(tǒng)上進(jìn)行編譯,如何在編譯時(shí)能夠支持面向不同的硬件設(shè)備,不同的編譯類型,且還要提供面向各個(gè)產(chǎn)商的定制擴(kuò)展,是非常有難度的。 但 Android Build 系統(tǒng)很好的解決了這些問(wèn)題,這里面有很多值得我們開發(fā)人員學(xué)習(xí)的地方。對(duì)于 Android 平臺(tái)開發(fā)人員來(lái)說(shuō),本文可以幫助你熟悉你每天接觸到的構(gòu)建環(huán)境。對(duì)于其他開發(fā)人員來(lái)說(shuō),本文可以作為一個(gè) GNU Make 的使用案例,學(xué)習(xí)這些成功案例,可以提升我們的開發(fā)經(jīng)驗(yàn)。
前言
Android Build 系統(tǒng)是 Android 源碼的一部分。關(guān)于如何獲取 Android 源碼,請(qǐng)參照 Android Source 官方網(wǎng)站:
http://source.android.com/source/downloading.html。
Android Build 系統(tǒng)用來(lái)編譯 Android 系統(tǒng),Android SDK 以及相關(guān)文檔。該系統(tǒng)主要由 Make 文件,Shell 腳本以及 Python 腳本組成,其中最主要的是 Make 文件。
眾所周知,Android 是一個(gè)開源的操作系統(tǒng)。Android 的源碼中包含了大量的開源項(xiàng)目以及許多的模塊。不同產(chǎn)商的不同設(shè)備對(duì)于 Android 系統(tǒng)的定制都是不一樣的。
如何將這些項(xiàng)目和模塊的編譯統(tǒng)一管理起來(lái),如何能夠在不同的操作系統(tǒng)上進(jìn)行編譯,如何在編譯時(shí)能夠支持面向不同的硬件設(shè)備,不同的編譯類型,且還要提供面向各個(gè)產(chǎn)商的定制擴(kuò)展,是非常有難度的。
但 Android Build 系統(tǒng)很好的解決了這些問(wèn)題,這里面有很多值得我們開發(fā)人員學(xué)習(xí)的地方。
對(duì)于 Android 平臺(tái)開發(fā)人員來(lái)說(shuō),本文可以幫助你熟悉你每天接觸到的構(gòu)建環(huán)境。
對(duì)于其他開發(fā)人員來(lái)說(shuō),本文可以作為一個(gè) GNU Make 的使用案例,學(xué)習(xí)這些成功案例,可以提升我們的開發(fā)經(jīng)驗(yàn)。
回頁(yè)首
概述
Build 系統(tǒng)中最主要的處理邏輯都在 Make 文件中,而其他的腳本文件只是起到一些輔助作用,由于篇幅所限,本文只探討 Make 文件中的內(nèi)容。
整個(gè) Build 系統(tǒng)中的 Make 文件可以分為三類:
第一類是 Build 系統(tǒng)核心文件,此類文件定義了整個(gè) Build 系統(tǒng)的框架,而其他所有 Make 文件都是在這個(gè)框架的基礎(chǔ)上編寫出來(lái)的。
圖 1 是 Android 源碼樹的目錄結(jié)構(gòu),Build 系統(tǒng)核心文件全部位于 /build/core(本文所提到的所有路徑都是以 Android 源碼樹作為背景的,“/”指的是源碼樹的根目錄,與文件系統(tǒng)無(wú)關(guān))目錄下。
圖 1. Android 源碼樹的目錄結(jié)構(gòu)
第二類是針對(duì)某個(gè)產(chǎn)品(一個(gè)產(chǎn)品可能是某個(gè)型號(hào)的手機(jī)或者平板電腦)的 Make 文件,這些文件通常位于 device 目錄下,該目錄下又以公司名以及產(chǎn)品名分為兩級(jí)目錄,圖 2 是 device 目錄下子目錄的結(jié)構(gòu)。對(duì)于一個(gè)產(chǎn)品的定義通常需要一組文件,這些文件共同構(gòu)成了對(duì)于這個(gè)產(chǎn)品的定義。例如,/device/sony/it26 目錄下的文件共同構(gòu)成了對(duì)于 Sony LT26 型號(hào)手機(jī)的定義。
圖 2. device 目錄下子目錄的結(jié)構(gòu)
第三類是針對(duì)某個(gè)模塊(關(guān)于模塊后文會(huì)詳細(xì)討論)的 Make 文件。整個(gè)系統(tǒng)中,包含了大量的模塊,每個(gè)模塊都有一個(gè)專門的 Make 文件,這類文件的名稱統(tǒng)一為“Android.mk”,該文件中定義了如何編譯當(dāng)前模塊。Build 系統(tǒng)會(huì)在整個(gè)源碼樹中掃描名稱為“Android.mk”的文件并根據(jù)其中的內(nèi)容執(zhí)行模塊的編譯。
回頁(yè)首
編譯 Android 系統(tǒng)
執(zhí)行編譯
Android 系統(tǒng)的編譯環(huán)境目前只支持 Ubuntu 以及 Mac OS 兩種操作系統(tǒng)。關(guān)于編譯環(huán)境的構(gòu)建方法請(qǐng)參見(jiàn)以下路徑:http://source.android.com/source/initializing.html
在完成編譯環(huán)境的準(zhǔn)備工作以及獲取到完整的 Android 源碼之后,想要編譯出整個(gè) Android 系統(tǒng)非常的容易:
打開控制臺(tái)之后轉(zhuǎn)到 Android 源碼的根目錄,然后執(zhí)行如清單 1 所示的三條命令即可("$"是命令提示符,不是命令的一部分。):
完整的編譯時(shí)間依賴于編譯主機(jī)的配置,在筆者的 Macbook Pro(OS X 10.8.2, i7 2G CPU,8G RAM, 120G SSD)上使用 8 個(gè) Job 同時(shí)編譯共需要一個(gè)半小時(shí)左右的時(shí)間。
清單 1. 編譯 Android 系統(tǒng)
$ source build/envsetup.sh $ lunch full-eng $ make -j8這三行命令的說(shuō)明如下:
第一行命令“source build/envsetup.sh”引入了?build/envsetup.sh腳本。該腳本的作用是初始化編譯環(huán)境,并引入一些輔助的 Shell 函數(shù),這其中就包括第二步使用 lunch 函數(shù)。
除此之外,該文件中還定義了其他一些常用的函數(shù),它們?nèi)绫?1 所示:
表 1. build/envsetup.sh 中定義的常用函數(shù)
| croot | 切換到源碼樹的根目錄 |
| m | 在源碼樹的根目錄執(zhí)行 make |
| mm | Build 當(dāng)前目錄下的模塊 |
| mmm | Build 指定目錄下的模塊 |
| cgrep | 在所有 C/C++ 文件上執(zhí)行 grep |
| jgrep | 在所有 Java 文件上執(zhí)行 grep |
| resgrep | 在所有 res/*.xml 文件上執(zhí)行 grep |
| godir | 轉(zhuǎn)到包含某個(gè)文件的目錄路徑 |
| printconfig | 顯示當(dāng)前 Build 的配置信息 |
| add_lunch_combo | 在 lunch 函數(shù)的菜單中添加一個(gè)條目 |
第二行命令“l(fā)unch full-eng”是調(diào)用 lunch 函數(shù),并指定參數(shù)為“full-eng”。lunch 函數(shù)的參數(shù)用來(lái)指定此次編譯的目標(biāo)設(shè)備以及編譯類型。在這里,這兩個(gè)值分別是“full”和“eng”。“full”是 Android 源碼中已經(jīng)定義好的一種產(chǎn)品,是為模擬器而設(shè)置的。而編譯類型會(huì)影響最終系統(tǒng)中包含的模塊,關(guān)于編譯類型將在表 7 中詳細(xì)講解。
如果調(diào)用 lunch 函數(shù)的時(shí)候沒(méi)有指定參數(shù),那么該函數(shù)將輸出列表以供選擇,該列表類似圖 3 中的內(nèi)容(列表的內(nèi)容會(huì)根據(jù)當(dāng)前 Build 系統(tǒng)中包含的產(chǎn)品配置而不同,具體參見(jiàn)后文“添加新的產(chǎn)品”),此時(shí)可以通過(guò)輸入編號(hào)或者名稱進(jìn)行選擇。
圖 3. lunch 函數(shù)的輸出
第三行命令“make -j8”才真正開始執(zhí)行編譯。make 的參數(shù)“-j”指定了同時(shí)編譯的 Job 數(shù)量,這是個(gè)整數(shù),該值通常是編譯主機(jī) CPU 支持的并發(fā)線程總數(shù)的 1 倍或 2 倍(例如:在一個(gè) 4 核,每個(gè)核支持兩個(gè)線程的 CPU 上,可以使用 make -j8 或 make -j16)。在調(diào)用 make 命令時(shí),如果沒(méi)有指定任何目標(biāo),則將使用默認(rèn)的名稱為“droid”目標(biāo),該目標(biāo)會(huì)編譯出完整的 Android 系統(tǒng)鏡像。
Build 結(jié)果的目錄結(jié)構(gòu)
所有的編譯產(chǎn)物都將位于 /out 目錄下,該目錄下主要有以下幾個(gè)子目錄:
- /out/host/:該目錄下包含了針對(duì)主機(jī)的 Android 開發(fā)工具的產(chǎn)物。即 SDK 中的各種工具,例如:emulator,adb,aapt 等。
- /out/target/common/:該目錄下包含了針對(duì)設(shè)備的共通的編譯產(chǎn)物,主要是 Java 應(yīng)用代碼和 Java 庫(kù)。
- /out/target/product/<product_name>/:包含了針對(duì)特定設(shè)備的編譯結(jié)果以及平臺(tái)相關(guān)的 C/C++ 庫(kù)和二進(jìn)制文件。其中,<product_name>是具體目標(biāo)設(shè)備的名稱。
- /out/dist/:包含了為多種分發(fā)而準(zhǔn)備的包,通過(guò)“make disttarget”將文件拷貝到該目錄,默認(rèn)的編譯目標(biāo)不會(huì)產(chǎn)生該目錄。
Build 生成的鏡像文件
Build 的產(chǎn)物中最重要的是三個(gè)鏡像文件,它們都位于 /out/target/product/<product_name>/ 目錄下。
這三個(gè)文件是:
- system.img:包含了 Android OS 的系統(tǒng)文件,庫(kù),可執(zhí)行文件以及預(yù)置的應(yīng)用程序,將被掛載為根分區(qū)。
- ramdisk.img:在啟動(dòng)時(shí)將被 Linux 內(nèi)核掛載為只讀分區(qū),它包含了 /init 文件和一些配置文件。它用來(lái)掛載其他系統(tǒng)鏡像并啟動(dòng) init 進(jìn)程。
- userdata.img:將被掛載為 /data,包含了應(yīng)用程序相關(guān)的數(shù)據(jù)以及和用戶相關(guān)的數(shù)據(jù)。
回頁(yè)首
Make 文件說(shuō)明
整個(gè) Build 系統(tǒng)的入口文件是源碼樹根目錄下名稱為“Makefile”的文件,當(dāng)在源代碼根目錄上調(diào)用 make 命令時(shí),make 命令首先將讀取該文件。
Makefile 文件的內(nèi)容只有一行:“include build/core/main.mk”。該行代碼的作用很明顯:包含 build/core/main.mk 文件。在 main.mk 文件中又會(huì)包含其他的文件,其他文件中又會(huì)包含更多的文件,這樣就引入了整個(gè) Build 系統(tǒng)。
這些 Make 文件間的包含關(guān)系是相當(dāng)復(fù)雜的,圖 3 描述了這種關(guān)系,該圖中黃色標(biāo)記的文件(且除了?$開頭的文件)都位于 build/core/ 目錄下。
圖 4. 主要的 Make 文件及其包含關(guān)系
表 2 總結(jié)了圖 4 中提到的這些文件的作用:
表 2. 主要的 Make 文件的說(shuō)明
| main.mk | 最主要的 Make 文件,該文件中首先將對(duì)編譯環(huán)境進(jìn)行檢查,同時(shí)引入其他的 Make 文件。另外,該文件中還定義了幾個(gè)最主要的 Make 目標(biāo),例如 droid,sdk,等(參見(jiàn)后文“Make 目標(biāo)說(shuō)明”)。 |
| help.mk | 包含了名稱為 help 的 Make 目標(biāo)的定義,該目標(biāo)將列出主要的 Make 目標(biāo)及其說(shuō)明。 |
| pathmap.mk | 將許多頭文件的路徑通過(guò)名值對(duì)的方式定義為映射表,并提供 include-path-for 函數(shù)來(lái)獲取。例如,通過(guò)$(call include-path-for, frameworks-native)便可以獲取到 framework 本地代碼需要的頭文件路徑。 |
| envsetup.mk | 配置 Build 系統(tǒng)需要的環(huán)境變量,例如:TARGET_PRODUCT,TARGET_BUILD_VARIANT,HOST_OS,HOST_ARCH 等。 當(dāng)前編譯的主機(jī)平臺(tái)信息(例如操作系統(tǒng),CPU 類型等信息)就是在這個(gè)文件中確定的。 另外,該文件中還指定了各種編譯結(jié)果的輸出路徑。 |
| combo/select.mk | 根據(jù)當(dāng)前編譯器的平臺(tái)選擇平臺(tái)相關(guān)的 Make 文件。 |
| dumpvar.mk | 在 Build 開始之前,顯示此次 Build 的配置信息。 |
| config.mk | 整個(gè) Build 系統(tǒng)的配置文件,最重要的 Make 文件之一。該文件中主要包含以下內(nèi)容:
|
| definitions.mk | 最重要的 Make 文件之一,在其中定義了大量的函數(shù)。這些函數(shù)都是 Build 系統(tǒng)的其他文件將用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package 等,關(guān)于這些函數(shù)的說(shuō)明請(qǐng)參見(jiàn)每個(gè)函數(shù)的代碼注釋。 |
| distdir.mk | 針對(duì) dist 目標(biāo)的定義。dist 目標(biāo)用來(lái)拷貝文件到指定路徑。 |
| dex_preopt.mk | 針對(duì)啟動(dòng) jar 包的預(yù)先優(yōu)化。 |
| pdk_config.mk | 顧名思義,針對(duì) pdk(Platform Developement Kit)的配置文件。 |
| ${ONE_SHOT_MAKEFILE} | ONE_SHOT_MAKEFILE 是一個(gè)變量,當(dāng)使用“mm”編譯某個(gè)目錄下的模塊時(shí),此變量的值即為當(dāng)前指定路徑下的 Make 文件的路徑。 |
| ${subdir_makefiles} | 各個(gè)模塊的 Android.mk 文件的集合,這個(gè)集合是通過(guò) Python 腳本掃描得到的。 |
| post_clean.mk | 在前一次 Build 的基礎(chǔ)上檢查當(dāng)前 Build 的配置,并執(zhí)行必要清理工作。 |
| legacy_prebuilts.mk | 該文件中只定義了 GRANDFATHERED_ALL_PREBUILT 變量。 |
| Makefile | 被 main.mk 包含,該文件中的內(nèi)容是輔助 main.mk 的一些額外內(nèi)容。 |
Android 源碼中包含了許多的模塊,模塊的類型有很多種,例如:Java 庫(kù),C/C++ 庫(kù),APK 應(yīng)用,以及可執(zhí)行文件等 。并且,Java 或者 C/C++ 庫(kù)還可以分為靜態(tài)的或者動(dòng)態(tài)的,庫(kù)或可執(zhí)行文件既可能是針對(duì)設(shè)備(本文的“設(shè)備”指的是 Android 系統(tǒng)將被安裝的設(shè)備,例如某個(gè)型號(hào)的手機(jī)或平板)的也可能是針對(duì)主機(jī)(本文的“主機(jī)”指的是開發(fā) Android 系統(tǒng)的機(jī)器,例如裝有 Ubuntu 操作系統(tǒng)的 PC 機(jī)或裝有 MacOS 的 iMac 或 Macbook)的。不同類型的模塊的編譯步驟和方法是不一樣,為了能夠一致且方便的執(zhí)行各種類型模塊的編譯,在 config.mk 中定義了許多的常量,這其中的每個(gè)常量描述了一種類型模塊的編譯方式,這些常量有:
- 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
通過(guò)名稱大概就可以猜出每個(gè)變量所對(duì)應(yīng)的模塊類型。(在模塊的 Android.mk 文件中,只要包含進(jìn)這里對(duì)應(yīng)的常量便可以執(zhí)行相應(yīng)類型模塊的編譯。對(duì)于 Android.mk 文件的編寫請(qǐng)參見(jiàn)后文:“添加新的模塊”。)
這些常量的值都是另外一個(gè) Make 文件的路徑,詳細(xì)的編譯方式都是在對(duì)應(yīng)的 Make 文件中定義的。這些常量和 Make 文件的是一一對(duì)應(yīng)的,對(duì)應(yīng)規(guī)則也很簡(jiǎn)單:常量的名稱是 Make 文件的文件名除去后綴全部改為大寫然后加上“BUILD_”作為前綴。例如常量 BUILD_HOST_PREBUILT 的值對(duì)應(yīng)的文件就是 host_prebuilt.mk。
這些 Make 文件的說(shuō)明如表 3 所示:
表 3. 各種模塊的編譯方式的定義文件
| host_static_library.mk | 定義了如何編譯主機(jī)上的靜態(tài)庫(kù)。 |
| host_shared_library.mk | 定義了如何編譯主機(jī)上的共享庫(kù)。 |
| static_library.mk | 定義了如何編譯設(shè)備上的靜態(tài)庫(kù)。 |
| shared_library.mk | 定義了如何編譯設(shè)備上的共享庫(kù)。 |
| executable.mk | 定義了如何編譯設(shè)備上的可執(zhí)行文件。 |
| host_executable.mk | 定義了如何編譯主機(jī)上的可執(zhí)行文件。 |
| package.mk | 定義了如何編譯 APK 文件。 |
| prebuilt.mk | 定義了如何處理一個(gè)已經(jīng)編譯好的文件 ( 例如 Jar 包 )。 |
| multi_prebuilt.mk | 定義了如何處理一個(gè)或多個(gè)已編譯文件,該文件的實(shí)現(xiàn)依賴 prebuilt.mk。 |
| host_prebuilt.mk | 處理一個(gè)或多個(gè)主機(jī)上使用的已編譯文件,該文件的實(shí)現(xiàn)依賴 multi_prebuilt.mk。 |
| java_library.mk | 定義了如何編譯設(shè)備上的共享 Java 庫(kù)。 |
| static_java_library.mk | 定義了如何編譯設(shè)備上的靜態(tài) Java 庫(kù)。 |
| host_java_library.mk | 定義了如何編譯主機(jī)上的共享 Java 庫(kù)。 |
不同類型的模塊的編譯過(guò)程會(huì)有一些相同的步驟,例如:編譯一個(gè) Java 庫(kù)和編譯一個(gè) APK 文件都需要定義如何編譯 Java 文件。因此,表 3 中的這些 Make 文件的定義中會(huì)包含一些共同的代碼邏輯。為了減少代碼冗余,需要將共同的代碼復(fù)用起來(lái),復(fù)用的方式是將共同代碼放到專門的文件中,然后在其他文件中包含這些文件的方式來(lái)實(shí)現(xiàn)的。這些包含關(guān)系如圖 5 所示。由于篇幅關(guān)系,這里就不再對(duì)其他文件做詳細(xì)描述(其實(shí)這些文件從文件名稱中就可以大致猜出其作用)。
圖 5. 模塊的編譯方式定義文件的包含關(guān)系
回頁(yè)首
Make 目標(biāo)說(shuō)明
make /make droid
如果在源碼樹的根目錄直接調(diào)用“make”命令而不指定任何目標(biāo),則會(huì)選擇默認(rèn)目標(biāo):“droid”(在 main.mk 中定義)。因此,這和執(zhí)行“make droid”效果是一樣的。
droid 目標(biāo)將編譯出整個(gè)系統(tǒng)的鏡像。從源代碼到編譯出系統(tǒng)鏡像,整個(gè)編譯過(guò)程非常復(fù)雜。這個(gè)過(guò)程并不是在 droid 一個(gè)目標(biāo)中定義的,而是 droid 目標(biāo)會(huì)依賴許多其他的目標(biāo),這些目標(biāo)的互相配合導(dǎo)致了整個(gè)系統(tǒng)的編譯。
圖 6 描述了 droid 目標(biāo)所依賴的其他目標(biāo):
圖 6. droid 目標(biāo)所依賴的其他 Make 目標(biāo)
圖 6 中這些目標(biāo)的說(shuō)明如表 4 所示:
表 4. droid 所依賴的其他 Make 目標(biāo)的說(shuō)明
| apps_only | 該目標(biāo)將編譯出當(dāng)前配置下不包含 user,userdebug,eng 標(biāo)簽(關(guān)于標(biāo)簽,請(qǐng)參見(jiàn)后文“添加新的模塊”)的應(yīng)用程序。 |
| droidcore | 該目標(biāo)僅僅是所依賴的幾個(gè)目標(biāo)的組合,其本身不做更多的處理。 |
| dist_files | 該目標(biāo)用來(lái)拷貝文件到 /out/dist 目錄。 |
| files | 該目標(biāo)僅僅是所依賴的幾個(gè)目標(biāo)的組合,其本身不做更多的處理。 |
| prebuilt | 該目標(biāo)依賴于?$(ALL_PREBUILT),$(ALL_PREBUILT)的作用就是處理所有已編譯好的文件。 |
| $(modules_to_install) | modules_to_install 變量包含了當(dāng)前配置下所有會(huì)被安裝的模塊(一個(gè)模塊是否會(huì)被安裝依賴于該產(chǎn)品的配置文件,模塊的標(biāo)簽等信息),因此該目標(biāo)將導(dǎo)致所有會(huì)被安裝的模塊的編譯。 |
| $(modules_to_check) | 該目標(biāo)用來(lái)確保我們定義的構(gòu)建模塊是沒(méi)有冗余的。 |
| $(INSTALLED_ANDROID_INFO_TXT_TARGET) | 該目標(biāo)會(huì)生成一個(gè)關(guān)于當(dāng)前 Build 配置的設(shè)備信息的文件,該文件的生成路徑是:out/target/product/<product_name>/android-info.txt |
| systemimage | 生成 system.img。 |
| $(INSTALLED_BOOTIMAGE_TARGET) | 生成 boot.img。 |
| $(INSTALLED_RECOVERYIMAGE_TARGET) | 生成 recovery.img。 |
| $(INSTALLED_USERDATAIMAGE_TARGET) | 生成 userdata.img。 |
| $(INSTALLED_CACHEIMAGE_TARGET) | 生成 cache.img。 |
| $(INSTALLED_FILES_FILE) | 該目標(biāo)會(huì)生成 out/target/product/<product_name>/ installed-files.txt 文件,該文件中內(nèi)容是當(dāng)前系統(tǒng)鏡像中已經(jīng)安裝的文件列表。 |
其他目標(biāo)
Build 系統(tǒng)中包含的其他一些 Make 目標(biāo)說(shuō)明如表 5 所示:
表 5. 其他主要 Make 目標(biāo)
| make clean | 執(zhí)行清理,等同于:rm -rf out/。 |
| make sdk | 編譯出 Android 的 SDK。 |
| make clean-sdk | 清理 SDK 的編譯產(chǎn)物。 |
| make update-api | 更新 API。在 framework API 改動(dòng)之后,需要首先執(zhí)行該命令來(lái)更新 API,公開的 API 記錄在 frameworks/base/api 目錄下。 |
| make dist | 執(zhí)行 Build,并將 MAKECMDGOALS 變量定義的輸出文件拷貝到 /out/dist 目錄。 |
| make all | 編譯所有內(nèi)容,不管當(dāng)前產(chǎn)品的定義中是否會(huì)包含。 |
| make help | 幫助信息,顯示主要的 make 目標(biāo)。 |
| make snod | 從已經(jīng)編譯出的包快速重建系統(tǒng)鏡像。 |
| make libandroid_runtime | 編譯所有 JNI framework 內(nèi)容。 |
| makeframework | 編譯所有 Java framework 內(nèi)容。 |
| makeservices | 編譯系統(tǒng)服務(wù)和相關(guān)內(nèi)容。 |
| make <local_target> | 編譯一個(gè)指定的模塊,local_target 為模塊的名稱。 |
| make clean-<local_target> | 清理一個(gè)指定模塊的編譯結(jié)果。 |
| makedump-products | 顯示所有產(chǎn)品的編譯配置信息,例如:產(chǎn)品名,產(chǎn)品支持的地區(qū)語(yǔ)言,產(chǎn)品中會(huì)包含的模塊等信息。 |
| makePRODUCT-xxx-yyy | 編譯某個(gè)指定的產(chǎn)品。 |
| makebootimage | 生成 boot.img |
| makerecoveryimage | 生成 recovery.img |
| makeuserdataimage | 生成 userdata.img |
| makecacheimage | 生成 cache.img |
回頁(yè)首
在 Build 系統(tǒng)中添加新的內(nèi)容
添加新的產(chǎn)品
當(dāng)我們要開發(fā)一款新的 Android 產(chǎn)品的時(shí)候,我們首先就需要在 Build 系統(tǒng)中添加對(duì)于該產(chǎn)品的定義。
在 Android Build 系統(tǒng)中對(duì)產(chǎn)品定義的文件通常位于 device 目錄下(另外還有一個(gè)可以定義產(chǎn)品的目錄是 vender 目錄,這是個(gè)歷史遺留目錄,Google 已經(jīng)建議不要在該目錄中進(jìn)行定義,而應(yīng)當(dāng)選擇 device 目錄)。device 目錄下根據(jù)公司名以及產(chǎn)品名分為二級(jí)目錄,這一點(diǎn)我們?cè)诟攀鲋幸呀?jīng)提到過(guò)。
通常,對(duì)于一個(gè)產(chǎn)品的定義通常至少會(huì)包括四個(gè)文件:AndroidProducts.mk,產(chǎn)品版本定義文件,BoardConfig.mk 以及 verndorsetup.sh。下面我們來(lái)詳細(xì)說(shuō)明這幾個(gè)文件。
- AndroidProducts.mk:該文文件中的內(nèi)容很簡(jiǎn)單,其中只需要定義一個(gè)變量,名稱為“PRODUCT_MAKEFILES”,該變量的值為產(chǎn)品版本定義文件名的列表,例如:
- 產(chǎn)品版本定義文件:顧名思義,該文件中包含了對(duì)于特定產(chǎn)品版本的定義。該文件可能不只一個(gè),因?yàn)橥粋€(gè)產(chǎn)品可能會(huì)有多種版本(例如,面向中國(guó)地區(qū)一個(gè)版本,面向美國(guó)地區(qū)一個(gè)版本)。該文件中可以定義的變量以及含義說(shuō)明如表 6 所示:
表 6. 產(chǎn)品版本定義文件中的變量及其說(shuō)明
| PRODUCT_NAME | 最終用戶將看到的完整產(chǎn)品名,會(huì)出現(xiàn)在“關(guān)于手機(jī)”信息中。 |
| PRODUCT_MODEL | 產(chǎn)品的型號(hào),這也是最終用戶將看到的。 |
| PRODUCT_LOCALES | 該產(chǎn)品支持的地區(qū),以空格分格,例如:en_GB de_DE es_ES fr_CA。 |
| PRODUCT_PACKAGES | 該產(chǎn)品版本中包含的 APK 應(yīng)用程序,以空格分格,例如:Calendar Contacts。 |
| PRODUCT_DEVICE | 該產(chǎn)品的工業(yè)設(shè)計(jì)的名稱。 |
| PRODUCT_MANUFACTURER | 制造商的名稱。 |
| PRODUCT_BRAND | 該產(chǎn)品專門定義的商標(biāo)(如果有的話)。 |
| PRODUCT_PROPERTY_OVERRIDES | 對(duì)于商品屬性的定義。 |
| PRODUCT_COPY_FILES | 編譯該產(chǎn)品時(shí)需要拷貝的文件,以“源路徑 : 目標(biāo)路徑”的形式。 |
| PRODUCT_OTA_PUBLIC_KEYS | 對(duì)于該產(chǎn)品的 OTA 公開 key 的列表。 |
| PRODUCT_POLICY | 產(chǎn)品使用的策略。 |
| PRODUCT_PACKAGE_OVERLAYS | 指出是否要使用默認(rèn)的資源或添加產(chǎn)品特定定義來(lái)覆蓋。 |
| PRODUCT_CONTRIBUTORS_FILE | HTML 文件,其中包含項(xiàng)目的貢獻(xiàn)者。 |
| PRODUCT_TAGS | 該產(chǎn)品的標(biāo)簽,以空格分格。 |
通常情況下,我們并不需要定義所有這些變量。Build 系統(tǒng)的已經(jīng)預(yù)先定義好了一些組合,它們都位于 /build/target/product 下,每個(gè)文件定義了一個(gè)組合,我們只要繼承這些預(yù)置的定義,然后再覆蓋自己想要的變量定義即可。例如:
# 繼承 full_base.mk 文件中的定義$(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk) # 覆蓋其中已經(jīng)定義的一些變量PRODUCT_NAME := full_lt26 PRODUCT_DEVICE := lt26 PRODUCT_BRAND := Android PRODUCT_MODEL := Full Android on LT26- BoardConfig.mk:該文件用來(lái)配置硬件主板,它其中定義的都是設(shè)備底層的硬件特性。例如:該設(shè)備的主板相關(guān)信息,Wifi 相關(guān)信息,還有 bootloader,內(nèi)核,radioimage 等信息。對(duì)于該文件的示例,請(qǐng)參看 Android 源碼樹已經(jīng)有的文件。
- vendorsetup.sh:該文件中作用是通過(guò) add_lunch_combo 函數(shù)在 lunch 函數(shù)中添加一個(gè)菜單選項(xiàng)。該函數(shù)的參數(shù)是產(chǎn)品名稱加上編譯類型,中間以“-”連接,例如:add_lunch_combo full_lt26-userdebug。/build/envsetup.sh 會(huì)掃描所有 device 和 vender 二 級(jí)目 錄下的名稱 為"vendorsetup.sh"文件,并根據(jù)其中的內(nèi)容來(lái)確定 lunch 函數(shù)的 菜單選項(xiàng)。
在配置了以上的文件之后,便可以編譯出我們新添加的設(shè)備的系統(tǒng)鏡像了。
首先,調(diào)用“source build/envsetup.sh”該命令的輸出中會(huì)看到 Build 系統(tǒng)已經(jīng)引入了剛剛添加的 vendorsetup.sh 文件。
然后再調(diào)用“l(fā)unch”函數(shù),該函數(shù)輸出的列表中將包含新添加的 vendorsetup.sh 中添加的條目。然后通過(guò)編號(hào)或名稱選擇即可。
最后,調(diào)用“make -j8”來(lái)執(zhí)行編譯即可。
添加新的模塊
關(guān)于“模塊”的說(shuō)明在上文中已經(jīng)提到過(guò),這里不再贅述。
在源碼樹中,一個(gè)模塊的所有文件通常都位于同一個(gè)文件夾中。為了將當(dāng)前模塊添加到整個(gè) Build 系統(tǒng)中,每個(gè)模塊都需要一個(gè)專門的 Make 文件,該文件的名稱為“Android.mk”。Build 系統(tǒng)會(huì)掃描名稱為“Android.mk”的文件,并根據(jù)該文件中內(nèi)容編譯出相應(yīng)的產(chǎn)物。
需要注意的是:在 Android Build 系統(tǒng)中,編譯是以模塊(而不是文件)作為單位的,每個(gè)模塊都有一個(gè)唯一的名稱,一個(gè)模塊的依賴對(duì)象只能是另外一個(gè)模塊,而不能是其他類型的對(duì)象。對(duì)于已經(jīng)編譯好的二進(jìn)制庫(kù),如果要用來(lái)被當(dāng)作是依賴對(duì)象,那么應(yīng)當(dāng)將這些已經(jīng)編譯好的庫(kù)作為單獨(dú)的模塊。對(duì)于這些已經(jīng)編譯好的庫(kù)使用 BUILD_PREBUILT 或 BUILD_MULTI_PREBUILT。例如:當(dāng)編譯某個(gè) Java 庫(kù)需要依賴一些 Jar 包時(shí),并不能直接指定 Jar 包的路徑作為依賴,而必須首先將這些 Jar 包定義為一個(gè)模塊,然后在編譯 Java 庫(kù)的時(shí)候通過(guò)模塊的名稱來(lái)依賴這些 Jar 包。
下面,我們就來(lái)講解 Android.mk 文件的編寫:
Android.mk 文件通常以以下兩行代碼作為開頭:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)這兩行代碼的作用是:
為了方便模塊的編譯,Build 系統(tǒng)設(shè)置了很多的編譯環(huán)境變量。要編譯一個(gè)模塊,只要在編譯之前根據(jù)需要設(shè)置這些變量然后執(zhí)行編譯即可。它們包括:
- LOCAL_SRC_FILES:當(dāng)前模塊包含的所有源代碼文件。
- LOCAL_MODULE:當(dāng)前模塊的名稱,這個(gè)名稱應(yīng)當(dāng)是唯一的,模塊間的依賴關(guān)系就是通過(guò)這個(gè)名稱來(lái)引用的。
- LOCAL_C_INCLUDES:C 或 C++ 語(yǔ)言需要的頭文件的路徑。
- LOCAL_STATIC_LIBRARIES:當(dāng)前模塊在靜態(tài)鏈接時(shí)需要的庫(kù)的名稱。
- LOCAL_SHARED_LIBRARIES:當(dāng)前模塊在運(yùn)行時(shí)依賴的動(dòng)態(tài)庫(kù)的名稱。
- LOCAL_CFLAGS:提供給 C/C++ 編譯器的額外編譯參數(shù)。
- LOCAL_JAVA_LIBRARIES:當(dāng)前模塊依賴的 Java 共享庫(kù)。
- LOCAL_STATIC_JAVA_LIBRARIES:當(dāng)前模塊依賴的 Java 靜態(tài)庫(kù)。
- LOCAL_PACKAGE_NAME:當(dāng)前 APK 應(yīng)用的名稱。
- LOCAL_CERTIFICATE:簽署當(dāng)前應(yīng)用的證書名稱。
- LOCAL_MODULE_TAGS:當(dāng)前模塊所包含的標(biāo)簽,一個(gè)模塊可以包含多個(gè)標(biāo)簽。標(biāo)簽的值可能是 debug, eng, user,development 或者 optional。其中,optional 是默認(rèn)標(biāo)簽。標(biāo)簽是提供給編譯類型使用的。不同的編譯類型會(huì)安裝包含不同標(biāo)簽的模塊,關(guān)于編譯類型的說(shuō)明如表 7 所示:
表 7. 編譯類型的說(shuō)明
| eng | 默認(rèn)類型,該編譯類型適用于開發(fā)階段。 當(dāng)選擇這種類型時(shí),編譯結(jié)果將:
|
| user | 該編譯類型適合用于最終發(fā)布階段。 當(dāng)選擇這種類型時(shí),編譯結(jié)果將:
|
| userdebug | 該編譯類型適合用于 debug 階段。 該類型和 user 一樣,除了:
|
表 3 中的文件已經(jīng)定義好了各種類型模塊的編譯方式。所以要執(zhí)行編譯,只需要引入表 3 中對(duì)應(yīng)的 Make 文件即可(通過(guò)常量的方式)。例如,要編譯一個(gè) APK 文件,只需要在 Android.mk 文件中,加入“include $(BUILD_PACKAGE)
除此以外,Build 系統(tǒng)中還定義了一些便捷的函數(shù)以便在 Android.mk 中使用,包括:
- $(call my-dir):獲取當(dāng)前文件夾路徑。
- $(call all-java-files-under, <src>):獲取指定目錄下的所有 Java 文件。
- $(call all-c-files-under, <src>):獲取指定目錄下的所有 C 語(yǔ)言文件。
- $(call all-Iaidl-files-under, <src>)?:獲取指定目錄下的所有 AIDL 文件。
- $(call all-makefiles-under, <folder>):獲取指定目錄下的所有 Make 文件。
- $(call intermediates-dir-for, <class>, <app_name>, <host or target>, <common?> ):獲取 Build 輸出的目標(biāo)文件夾路徑。
清單 2 和清單 3 分別是編譯 APK 文件和編譯 Java 靜態(tài)庫(kù)的 Make 文件示例:
清單 2. 編譯一個(gè) APK 文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 獲取所有子目錄中的 Java 文件LOCAL_SRC_FILES := $(call all-subdir-java-files) # 當(dāng)前模塊依賴的靜態(tài) Java 庫(kù),如果有多個(gè)以空格分隔LOCAL_STATIC_JAVA_LIBRARIES := static-library # 當(dāng)前模塊的名稱LOCAL_PACKAGE_NAME := LocalPackage # 編譯 APK 文件include $(BUILD_PACKAGE)清單 3. 編譯一個(gè) Java 的靜態(tài)庫(kù)
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 獲取所有子目錄中的 Java 文件LOCAL_SRC_FILES := $(call all-subdir-java-files) # 當(dāng)前模塊依賴的動(dòng)態(tài) Java 庫(kù)名稱LOCAL_JAVA_LIBRARIES := android.test.runner # 當(dāng)前模塊的名稱LOCAL_MODULE := sample # 將當(dāng)前模塊編譯成一個(gè)靜態(tài)的 Java 庫(kù)include $(BUILD_STATIC_JAVA_LIBRARY)回頁(yè)首
結(jié)束語(yǔ)
整個(gè) Build 系統(tǒng)包含了非常多的內(nèi)容,由于篇幅所限,本文只能介紹其中最主要內(nèi)容。
由于 Build 系統(tǒng)本身也是在隨著 Android 平臺(tái)不斷的開發(fā)過(guò)程中,所以不同的版本其中的內(nèi)容和定義可能會(huì)發(fā)生變化。網(wǎng)絡(luò)上關(guān)于該部分的資料很零碎,并且很多資料中的一些內(nèi)容已經(jīng)過(guò)時(shí)不再適用,再加上缺少官方文檔,所以該部分的學(xué)習(xí)存在一定的難度。
這就要求我們要有很強(qiáng)的代碼閱讀能力,畢竟代碼是不會(huì)說(shuō)謊的。 要知道,對(duì)于我們這些開發(fā)人員來(lái)說(shuō),源代碼就是我們最忠實(shí)的朋友。 Use the Source,Luke!
參考資料
學(xué)習(xí)
- Android Open Source Project:Android Source 官方網(wǎng)站。
- Android Build System:Build 系統(tǒng)中包含的說(shuō)明文檔。
- GNU `make':GNU make 官方手冊(cè)。
- Android Device:大致介紹了 Build 系統(tǒng)中的一些文件。
- Build System:另一個(gè)關(guān)于 Build 系統(tǒng)的說(shuō)明資料。
- Add new target:該文檔描述了如何添加一個(gè)新的產(chǎn)品目標(biāo)。
總結(jié)
以上是生活随笔為你收集整理的理解 Android Build 系统的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Mtk Android 打包解包*.im
- 下一篇: Android OTA 升级之三:生成r