理解 Android Build 系統

源自:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-build/html

前言

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

http://source.android.com/source/downloading.htmlandroid

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

衆所周知,Android 是一個開源的操做系統。Android 的源碼中包含了大量的開源項目以及許多的模塊。不一樣產商的不一樣設備對於 Android 系統的定製都是不同的。併發

如何將這些項目和模塊的編譯統一管理起來,如何可以在不一樣的操做系統上進行編譯,如何在編譯時可以支持面向不一樣的硬件設備,不一樣的編譯類型,且還要提供面向各個產商的定製擴展,是很是有難度的。app

但 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 系統的編譯環境目前只支持 Ubuntu 以及 Mac OS 兩種操做系統。關於編譯環境的構建方法請參見如下路徑:http://source.android.com/source/initializing.html

在完成編譯環境的準備工做以及獲取到完整的 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 庫。
host_java_library.mk 定義瞭如何編譯主機上的共享 Java 庫。

 

不一樣類型的模塊的編譯過程會有一些相同的步驟,例如:編譯一個 Java 庫和編譯一個 APK 文件都須要定義如何編譯 Java 文件。所以,表 3 中的這些 Make 文件的定義中會包含一些共同的代碼邏輯。爲了減小代碼冗餘,須要將共同的代碼複用起來,複用的方式是將共同代碼放到專門的文件中,而後在其餘文件中包含這些文件的方式來實現的。這些包含關係如圖 5 所示。因爲篇幅關係,這裏就再也不對其餘文件作詳細描述(其實這些文件從文件名稱中就能夠大體猜出其做用)。

圖 5. 模塊的編譯方式定義文件的包含關係

圖 5. 模塊的編譯方式定義文件的包含關係

 

Make 目標說明

make /make droid

若是在源碼樹的根目錄直接調用「make」命令而不指定任何目標,則會選擇默認目標:「droid」(在 main.mk 中定義)。所以,這和執行「make droid」效果是同樣的。

droid 目標將編譯出整個系統的鏡像。從源代碼到編譯出系統鏡像,整個編譯過程很是複雜。這個過程並非在 droid 一個目標中定義的,而是 droid 目標會依賴許多其餘的目標,這些目標的互相配合致使了整個系統的編譯。

圖 6 描述了 droid 目標所依賴的其餘目標:

圖 6. droid 目標所依賴的其餘 Make 目標

圖 6. droid 目標所依賴的其餘 Make 目標

圖 6 中這些目標的說明如表 4 所示:

表 4. droid 所依賴的其餘 Make 目標的說明
名稱 說明
apps_only 該目標將編譯出當前配置下不包含 user,userdebug,eng 標籤(關於標籤,請參見後文「添加新的模塊」)的應用程序。
droidcore 該目標僅僅是所依賴的幾個目標的組合,其自己不作更多的處理。
dist_files 該目標用來拷貝文件到 /out/dist 目錄。
files 該目標僅僅是所依賴的幾個目標的組合,其自己不作更多的處理。
prebuilt 該目標依賴於 $(ALL_PREBUILT)$(ALL_PREBUILT)的做用就是處理全部已編譯好的文件。
$(modules_to_install) modules_to_install 變量包含了當前配置下全部會被安裝的模塊(一個模塊是否會被安裝依賴於該產品的配置文件,模塊的標籤等信息),所以該目標將致使全部會被安裝的模塊的編譯。
$(modules_to_check) 該目標用來確保咱們定義的構建模塊是沒有冗餘的。
$(INSTALLED_ANDROID_INFO_TXT_TARGET) 該目標會生成一個關於當前 Build 配置的設備信息的文件,該文件的生成路徑是: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) 該目標會生成 out/target/product/<product_name>/ installed-files.txt 文件,該文件中內容是當前系統鏡像中已經安裝的文件列表。

 

其餘目標

Build 系統中包含的其餘一些 Make 目標說明如表 5 所示:

表 5. 其餘主要 Make 目標
Make 目標 說明
make clean 執行清理,等同於:rm -rf out/。
make sdk 編譯出 Android 的 SDK。
make clean-sdk 清理 SDK 的編譯產物。
make update-api 更新 API。在 framework API 改動以後,須要首先執行該命令來更新 API,公開的 API 記錄在 frameworks/base/api 目錄下。
make dist 執行 Build,並將 MAKECMDGOALS 變量定義的輸出文件拷貝到 /out/dist 目錄。
make all 編譯全部內容,無論當前產品的定義中是否會包含。
make help 幫助信息,顯示主要的 make 目標。
make snod 從已經編譯出的包快速重建系統鏡像。
make libandroid_runtime 編譯全部 JNI framework 內容。
makeframework 編譯全部 Java framework 內容。
makeservices 編譯系統服務和相關內容。
make <local_target> 編譯一個指定的模塊,local_target 爲模塊的名稱。
make clean-<local_target> 清理一個指定模塊的編譯結果。
makedump-products 顯示全部產品的編譯配置信息,例如:產品名,產品支持的地區語言,產品中會包含的模塊等信息。
makePRODUCT-xxx-yyy 編譯某個指定的產品。
makebootimage 生成 boot.img
makerecoveryimage 生成 recovery.img
makeuserdataimage 生成 userdata.img
makecacheimage 生成 cache.img

 

 

在 Build 系統中添加新的內容

添加新的產品

當咱們要開發一款新的 Android 產品的時候,咱們首先就須要在 Build 系統中添加對於該產品的定義。

在 Android Build 系統中對產品定義的文件一般位於 device 目錄下(另外還有一個能夠定義產品的目錄是 vender 目錄,這是個歷史遺留目錄,Google 已經建議不要在該目錄中進行定義,而應當選擇 device 目錄)。device 目錄下根據公司名以及產品名分爲二級目錄,這一點咱們在概述中已經提到過。

一般,對於一個產品的定義一般至少會包括四個文件:AndroidProducts.mk,產品版本定義文件,BoardConfig.mk 以及 verndorsetup.sh。下面咱們來詳細說明這幾個文件。

  • AndroidProducts.mk:該文文件中的內容很簡單,其中只須要定義一個變量,名稱爲「PRODUCT_MAKEFILES」,該變量的值爲產品版本定義文件名的列表,例如:
 PRODUCT_MAKEFILES := \ 
 $(LOCAL_DIR)/full_stingray.mk \ 
 $(LOCAL_DIR)/stingray_emu.mk \ 
 $(LOCAL_DIR)/generic_stingray.mk
  • 產品版本定義文件:顧名思義,該文件中包含了對於特定產品版本的定義。該文件可能不僅一個,由於同一個產品可能會有多種版本(例如,面向中國地區一個版本,面向美國地區一個版本)。該文件中能夠定義的變量以及含義說明如表 6 所示:
表 6. 產品版本定義文件中的變量及其說明
常量 說明
PRODUCT_NAME 最終用戶將看到的完整產品名,會出如今「關於手機」信息中。
PRODUCT_MODEL 產品的型號,這也是最終用戶將看到的。
PRODUCT_LOCALES 該產品支持的地區,以空格分格,例如:en_GB de_DE es_ES fr_CA。
PRODUCT_PACKAGES 該產品版本中包含的 APK 應用程序,以空格分格,例如:Calendar Contacts。
PRODUCT_DEVICE 該產品的工業設計的名稱。
PRODUCT_MANUFACTURER 製造商的名稱。
PRODUCT_BRAND 該產品專門定義的商標(若是有的話)。
PRODUCT_PROPERTY_OVERRIDES 對於商品屬性的定義。
PRODUCT_COPY_FILES 編譯該產品時須要拷貝的文件,以「源路徑 : 目標路徑」的形式。
PRODUCT_OTA_PUBLIC_KEYS 對於該產品的 OTA 公開 key 的列表。
PRODUCT_POLICY 產品使用的策略。
PRODUCT_PACKAGE_OVERLAYS 指出是否要使用默認的資源或添加產品特定定義來覆蓋。
PRODUCT_CONTRIBUTORS_FILE HTML 文件,其中包含項目的貢獻者。
PRODUCT_TAGS 該產品的標籤,以空格分格。

 

一般狀況下,咱們並不須要定義全部這些變量。Build 系統的已經預先定義好了一些組合,它們都位於 /build/target/product 下,每一個文件定義了一個組合,咱們只要繼承這些預置的定義,而後再覆蓋本身想要的變量定義便可。例如:

 # 繼承 full_base.mk 文件中的定義
 $(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk) 
 # 覆蓋其中已經定義的一些變量
 PRODUCT_NAME := full_lt26 
 PRODUCT_DEVICE := lt26 
 PRODUCT_BRAND := Android 
 PRODUCT_MODEL := Full Android on LT26
  • BoardConfig.mk:該文件用來配置硬件主板,它其中定義的都是設備底層的硬件特性。例如:該設備的主板相關信息,Wifi 相關信息,還有 bootloader,內核,radioimage 等信息。對於該文件的示例,請參看 Android 源碼樹已經有的文件。
  • vendorsetup.sh:該文件中做用是經過 add_lunch_combo 函數在 lunch 函數中添加一個菜單選項。該函數的參數是產品名稱加上編譯類型,中間以「-」鏈接,例如:add_lunch_combo full_lt26-userdebug。/build/envsetup.sh 會掃描全部 device 和 vender 二 級目 錄下的名稱 爲"vendorsetup.sh"文件,並根據其中的內容來肯定 lunch 函數的 菜單選項。

在配置了以上的文件以後,即可以編譯出咱們新添加的設備的系統鏡像了。

首先,調用「source build/envsetup.sh」該命令的輸出中會看到 Build 系統已經引入了剛剛添加的 vendorsetup.sh 文件。

而後再調用「lunch」函數,該函數輸出的列表中將包含新添加的 vendorsetup.sh 中添加的條目。而後經過編號或名稱選擇便可。

最後,調用「make -j8」來執行編譯便可。

添加新的模塊

關於「模塊」的說明在上文中已經提到過,這裏再也不贅述。

在源碼樹中,一個模塊的全部文件一般都位於同一個文件夾中。爲了將當前模塊添加到整個 Build 系統中,每一個模塊都須要一個專門的 Make 文件,該文件的名稱爲「Android.mk」。Build 系統會掃描名稱爲「Android.mk」的文件,並根據該文件中內容編譯出相應的產物。

須要注意的是:在 Android Build 系統中,編譯是以模塊(而不是文件)做爲單位的,每一個模塊都有一個惟一的名稱,一個模塊的依賴對象只能是另一個模塊,而不能是其餘類型的對象。對於已經編譯好的二進制庫,若是要用來被看成是依賴對象,那麼應當將這些已經編譯好的庫做爲單獨的模塊。對於這些已經編譯好的庫使用 BUILD_PREBUILT 或 BUILD_MULTI_PREBUILT。例如:當編譯某個 Java 庫須要依賴一些 Jar 包時,並不能直接指定 Jar 包的路徑做爲依賴,而必須首先將這些 Jar 包定義爲一個模塊,而後在編譯 Java 庫的時候經過模塊的名稱來依賴這些 Jar 包。

下面,咱們就來說解 Android.mk 文件的編寫:

Android.mk 文件一般以如下兩行代碼做爲開頭:

 LOCAL_PATH := $(call my-dir) 
 include $(CLEAR_VARS)

這兩行代碼的做用是:

  1. 設置當前模塊的編譯路徑爲當前文件夾路徑。
  2. 清理(可能由其餘模塊設置過的)編譯環境中用到的變量。

爲了方便模塊的編譯,Build 系統設置了不少的編譯環境變量。要編譯一個模塊,只要在編譯以前根據須要設置這些變量而後執行編譯便可。它們包括:

  • LOCAL_SRC_FILES:當前模塊包含的全部源代碼文件。
  • LOCAL_MODULE:當前模塊的名稱,這個名稱應當是惟一的,模塊間的依賴關係就是經過這個名稱來引用的。
  • LOCAL_C_INCLUDES:C 或 C++ 語言須要的頭文件的路徑。
  • LOCAL_STATIC_LIBRARIES:當前模塊在靜態連接時須要的庫的名稱。
  • LOCAL_SHARED_LIBRARIES:當前模塊在運行時依賴的動態庫的名稱。
  • LOCAL_CFLAGS:提供給 C/C++ 編譯器的額外編譯參數。
  • LOCAL_JAVA_LIBRARIES:當前模塊依賴的 Java 共享庫。
  • LOCAL_STATIC_JAVA_LIBRARIES:當前模塊依賴的 Java 靜態庫。
  • LOCAL_PACKAGE_NAME:當前 APK 應用的名稱。
  • LOCAL_CERTIFICATE:簽署當前應用的證書名稱。
  • LOCAL_MODULE_TAGS:當前模塊所包含的標籤,一個模塊能夠包含多個標籤。標籤的值多是 debug, eng, user,development 或者 optional。其中,optional 是默認標籤。標籤是提供給編譯類型使用的。不一樣的編譯類型會安裝包含不一樣標籤的模塊,關於編譯類型的說明如表 7 所示:
表 7. 編譯類型的說明
名稱 說明
eng 默認類型,該編譯類型適用於開發階段。
當選擇這種類型時,編譯結果將:
  • 安裝包含 eng, debug, user,development 標籤的模塊
  • 安裝全部沒有標籤的非 APK 模塊
  • 安裝全部產品定義文件中指定的 APK 模塊
user 該編譯類型適合用於最終發佈階段。
當選擇這種類型時,編譯結果將:
  • 安裝全部帶有 user 標籤的模塊
  • 安裝全部沒有標籤的非 APK 模塊
  • 安裝全部產品定義文件中指定的 APK 模塊,APK 模塊的標籤將被忽略
userdebug 該編譯類型適合用於 debug 階段。
該類型和 user 同樣,除了:
  • 會安裝包含 debug 標籤的模塊
  • 編譯出的系統具備 root 訪問權限

 

表 3 中的文件已經定義好了各類類型模塊的編譯方式。因此要執行編譯,只須要引入表 3 中對應的 Make 文件便可(經過常量的方式)。例如,要編譯一個 APK 文件,只須要在 Android.mk 文件中,加入「include $(BUILD_PACKAGE)

除此之外,Build 系統中還定義了一些便捷的函數以便在 Android.mk 中使用,包括:

  • $(call my-dir):獲取當前文件夾路徑。
  • $(call all-java-files-under, <src>):獲取指定目錄下的全部 Java 文件。
  • $(call all-c-files-under, <src>):獲取指定目錄下的全部 C 語言文件。
  • $(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 輸出的目標文件夾路徑。

清單 2 和清單 3 分別是編譯 APK 文件和編譯 Java 靜態庫的 Make 文件示例:

清單 2. 編譯一個 APK 文件
  LOCAL_PATH := $(call my-dir) 
  include $(CLEAR_VARS) 
  # 獲取全部子目錄中的 Java 文件
  LOCAL_SRC_FILES := $(call all-subdir-java-files) 			
  # 當前模塊依賴的靜態 Java 庫,若是有多個以空格分隔
  LOCAL_STATIC_JAVA_LIBRARIES := static-library 
  # 當前模塊的名稱
  LOCAL_PACKAGE_NAME := LocalPackage 
  # 編譯 APK 文件
  include $(BUILD_PACKAGE)
清單 3. 編譯一個 Java 的靜態庫
  LOCAL_PATH := $(call my-dir) 
  include $(CLEAR_VARS) 
   
  # 獲取全部子目錄中的 Java 文件
  LOCAL_SRC_FILES := $(call all-subdir-java-files) 
   
  # 當前模塊依賴的動態 Java 庫名稱
  LOCAL_JAVA_LIBRARIES := android.test.runner 
   
  # 當前模塊的名稱
  LOCAL_MODULE := sample 
   
  # 將當前模塊編譯成一個靜態的 Java 庫
  include $(BUILD_STATIC_JAVA_LIBRARY)
相關文章
相關標籤/搜索