天天看點

深入了解Android Build系統概述Android Build簡介編譯 Android 系統Make 檔案定制 Build 系統中内容

android build 系統是用來編譯 android 系統、android sdk 以及相關文檔的一套架構。在android系統中,android 的源碼中包含了許許多多的子產品。 不同産商的不同裝置對于 android 系統的定制都是不一樣的。如何将這些子產品統一管理起來,如何能夠在不同的作業系統上進行編譯,如何在編譯時能夠支援面向不同的硬體裝置,不同的編譯類型,且還要提供面向各個産商的定制擴充,android系統如何解決這些問題呢?這就是我們不得不談的android build 系統。

android源碼目錄結構:

深入了解Android Build系統概述Android Build簡介編譯 Android 系統Make 檔案定制 Build 系統中内容

在講解android編譯系統之前,我們首先需要了解linux系統的make指令。在linux系統中,我們可以通過make指令來編譯代碼。make指令在執行的時候,預設會在目前目錄找到一個makefile檔案,然後根據makefile檔案中的指令來對代碼進行編譯。如gcc,linux系統中的shell指令cp、rm等等。

看到這裡,有的小夥伴可能會說,在linux系統中,shell和make指令有什麼差別呢?

make指令事實也是通過shell指令來完成任務的,但是它的神奇之處是可以幫我們處理好檔案之間的依賴關系。例如有一個檔案t,它依賴于另外一個檔案d,要求隻有當檔案d的内容發生變化,才重新生成檔案t。

make指令是怎麼知道兩個檔案之間存在依賴關系,以及當被依賴檔案發生變化時如何處理目标檔案的呢?答案就在前面提到的makefile檔案。makefile檔案實際上是一個腳本檔案,就像普通的shell腳本檔案一樣,隻不過它遵循的是makefile文法。makefile檔案最基礎的功能就是描述檔案之間的依賴關系,以及怎麼處理這些依賴關系。

android build 系統是 android 系統的一部分,主要用來編譯 android 系統,android sdk 以及相關文檔。該系統主要由 make 檔案,shell 腳本以及 python 腳本組成。

android build分類:

build/core 目錄下的檔案,這是android build的系統架構核心;

device目錄下的檔案,存放的是具體的産品配置檔案;

各個子產品的編譯檔案:android.mk,位于子產品的原檔案目錄下。

android build系統核心在目錄build/core,這個目錄中有mk檔案、shell腳本和per腳本,他們構成android build系統的基礎和架構。

在核心的buil/core裡,系統主要幹了三件事情:

深入了解Android Build系統概述Android Build簡介編譯 Android 系統Make 檔案定制 Build 系統中内容

常用指令:

而在build/envsetup.sh中主要完成了三件事:

深入了解Android Build系統概述Android Build簡介編譯 Android 系統Make 檔案定制 Build 系統中内容

執行android系統的編譯,必須先執行envsetup.sh腳本,這個腳本會建立android的編譯環境。其具體執行的是建立shell指令以及調用add_lunch_combo指令,這個指令的将調用該指令的所傳遞的參數存放到一個全局的數組變量lunch_menu_choices中。

envsetup.sh腳本中定義的常用shell指令:

指令

說明

contact-button

指定目前編譯的産品

croot

快速切換到源碼的根目錄,友善開始編譯

m

編譯整個源碼,但不用将目前的目錄切換到源碼的根目錄

mm

編譯目前目錄下的所有子產品,但是不編譯他們的依賴項

cgrep

對系統中所有的c/c++檔案執行grep指令

sgrep

對系統中所有的源檔案執行grep指令

android 系統的編譯環境目前隻支援 ubuntu 以及 mac os 兩種作業系統。在編譯android系統之前我們需要先擷取完整的 android 源碼。打開控制台之後轉到 android 源碼的根目錄,然後執行如下命名:

關于這幾條指令的意思,我們上面提過。

第一步指令“source build/envsetup.sh”引入了 build/envsetup.sh腳本,該腳本的作用是初始化編譯環境,并引入一些輔助的 shell 函數;

第二步指令“lunch full-eng”是調用 lunch 函數,并指定參數為“full-eng”。lunch 函數的參數用來指定此次編譯的目标裝置以及編譯類型。

第三部指令“make -j8”才真正開始執行編譯。make 的參數“-j”指定了同時編譯的 job 數量,這是個整數,該值通常是編譯主機 cpu 支援的并發線程總數的 1 倍或 2 倍(例如:在一個 4 核,每個核支援兩個線程的 cpu 上,可以使用 make -j8 或 make -j16)。

完整的編譯時間依賴于編譯主機的配置。

所有的編譯産物都将位于 /out 目錄下,該目錄下主要包含:

/out/host/:該目錄下包含了針對主機的 android 開發工具的産物。即 sdk 中的各種工具,例如:emulator,adb,aapt 等。

/out/target/common/:該目錄下包含了針對裝置的共通的編譯産物,主要是 java 應用代碼和 java 庫。

/out/target/product//:包含了針對特定裝置的編譯結果以及平台相關的 c/c++ 庫和二進制檔案。其中,是具體目标裝置的名稱。

/out/dist/:包含了為多種分發而準備的包,通過“make disttarget”将檔案拷貝到該目錄,預設的編譯目标不會産生該目錄。

build 的産物中最重要的是三個鏡像檔案,它們都位于 /out/target/product// 目錄下:

system.img:包含了 android os 的系統檔案,庫,可執行檔案以及預置的應用程式,将被挂載為根分區。

ramdisk.img:在啟動時将被 linux 核心挂載為隻讀分區,它包含了 /init檔案和一些配置檔案。它用來挂載其他系統鏡像并啟動 init 程序。

userdata.img:将被挂載為 /data,包含了應用程式相關的資料以及和使用者相關的資料。

整個 build 系統的入口檔案是源碼樹根目錄下名稱為“makefile”的檔案,當在源代碼根目錄上調用 make 指令時,make 指令首先将讀取該檔案。

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

在整個build系統中,make 檔案間的關系是相當複雜的。看一張make檔案主要的關系圖:

深入了解Android Build系統概述Android Build簡介編譯 Android 系統Make 檔案定制 Build 系統中内容

make 常用檔案:

檔案名

main.mk

主要的 make 檔案,該檔案中首先将對編譯環境進行檢查,同時引入其他的 make 檔案。另外,該檔案中還定義了幾個最主要的 make 目标,例如 droid,sdk,等(參見後文“make 目标說明”)。

help.mk

含了名稱為 help 的 make 目标的定義,該目标将列出主要的 make 目标及其說明。

envsetup.mk

配置 build 系統需要的環境變量,例如:target_product,target_build_variant,host_os,host_arch 等。

目前編譯的主機平台資訊(例如作業系統,cpu 類型等資訊)就是在這個檔案中确定的。

另外,該檔案中還指定了各種編譯結果的輸出路徑。

pathmap.mk

将許多頭檔案的路徑通過名值對的方式定義為映射表,并提供 include-path-for 函數來擷取。例如,通過 $(call include-path-for, frameworks-native)便可以擷取到 framework 本地代碼需要的頭檔案路徑。

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)的配置檔案。

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

不同類型的子產品的編譯過程會有一些相同的步驟,例如:編譯一個 java 庫和編譯一個 apk 檔案都需要定義如何編譯 java 檔案。為了減少代碼備援,需要将共同的代碼複用起來,複用的方式是将共同代碼放到專門的檔案中,然後在其他檔案中包含這些檔案的方式來實作的。

子產品的編譯方式定義檔案的包含關系:

深入了解Android Build系統概述Android Build簡介編譯 Android 系統Make 檔案定制 Build 系統中内容

如果在源碼樹的根目錄直接調用“make”指令而不指定任何目标,則會選擇預設目标:“droid”(在 main.mk 中定義)。是以,這和執行“make droid”效果是一樣的。

droid 目标将編譯出整個系統的鏡像。從源代碼到編譯出系統鏡像,整個編譯過程非常複雜。這個過程并不是在 droid 一個目标中定義的,而是 droid 目标會依賴許多其他的目标,這些目标的互相配合導緻了整個系統的編譯。

那麼需要編譯出系統鏡像,需要哪些依賴呢?

深入了解Android Build系統概述Android Build簡介編譯 Android 系統Make 檔案定制 Build 系統中内容

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//android-info.txt

systemimage

生成 system.img。

build 系統中包含的其他一些 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 snod

從已經編譯出的包快速重建系統鏡像

make libandroid_runtime

編譯所有 jni framework 内容

makeframework

編譯所有 java framework 内容

makeservices

編譯系統服務和相關内容

make

編譯一個指定的子產品,local_target 為子產品的名稱

make clean-

清理一個指定子產品的編譯結果

makedump-products

顯示所有産品的編譯配置資訊,例如:産品名,産品支援的地區語言,産品中會包含的子產品等資訊

makeproduct-xxx-yyy

編譯某個指定的産品

makebootimage

生成 boot.img

當我們要開發一款新的 android 産品的時候,我們首先就需要在 build 系統中添加對于該産品的定義。在 android build 系統中對産品定義的檔案通常位于 device 目錄下,device 目錄下可以公司名以及産品名分為二級目錄,然後加入到系統中,如以前小米等基于android深度定制的系統。

通常,對于一個産品的定義通常至少會包括四個檔案:androidproducts.mk,産品版本定義檔案,boardconfig.mk 以及 verndorsetup.sh。

該檔案隻需要定義一個變量,名稱為“product_makefiles”。

該檔案中包含了對于特定産品版本的定義。該檔案可能不隻一個,因為同一個産品可能會有多種版本。

通常情況下,我們并不需要定義所有這些變量。build 系統的已經預先定義好了一些組合,它們都位于 /build/target/product 下,每個檔案定義了一個組合,我們隻要繼承這些預置的定義,然後再覆寫自己想要的變量定義即可。

該檔案用來配置硬體主機闆,它其中定義的都是裝置底層的硬體特性。例如:該裝置的主機闆相關資訊,wifi 相關資訊,還有 bootloader,核心,radioimage 等資訊。

該檔案中作用是通過 add_lunch_combo 函數在 lunch 函數中添加一個菜單選項。該函數的參數是産品名稱加上編譯類型,中間以“-”連接配接,例如:add_lunch_combo full_lt26-userdebug。/build/envsetup.sh 會掃描所有 device 和 vender 二 級目 錄下的名稱 為"vendorsetup.sh"檔案,并根據其中的内容來确定 lunch 函數的 菜單選項。

在配置了以上的檔案之後,便可以編譯出我們新添加的裝置的系統鏡像了。

我們可以使用指令:

來檢視build 系統已經引入了剛剛添加的 vendorsetup.sh 檔案。

在源碼樹中,一個子產品的所有檔案通常都位于同一個檔案夾中。為了将目前子產品添加到整個 build 系統中,每個子產品都需要一個專門的 make 檔案,該檔案的名稱為“android.mk”。build 系統會掃描名稱為“android.mk”的檔案,并根據該檔案中内容編譯出相應的産物。

注:

在 android build 系統中,編譯是以子產品(而不是檔案)作為機關的,每個子產品都有一個唯一的名稱,一個子產品的依賴對象隻能是另外一個子產品,而不能是其他類型的對象。對于已經編譯好的二進制庫,如果要用來被當作是依賴對象,那麼應當将這些已經編譯好的庫作為單獨的子產品。對于這些已經編譯好的庫使用 build_prebuilt 或 build_multi_prebuilt。例如:當編譯某個 java 庫需要依賴一些 jar 包時,并不能直接指定 jar 包的路徑作為依賴,而必須首先将這些 jar 包定義為一個子產品,然後在編譯 java 庫的時候通過子產品的名稱來依賴這些 jar 包。

那麼怎麼編寫android.mk 檔案呢?

android.mk 檔案通常以以下兩行代碼作為開頭:

為了友善子產品的編譯,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是預設标簽。标簽是提供給編譯類型使用的,不同的編譯類型會安裝包含不同标簽的子產品。

編譯類型說明:

eng

預設類型,該編譯類型适用于開發階段。

當選擇這種類型時,編譯結果将:

安裝包含 eng, debug, user,development 标簽的子產品

安裝所有沒有标簽的非 apk 子產品

安裝所有産品定義檔案中指定的 apk 子產品

user

該編譯類型适合用于最終釋出階段。

安裝所有帶有 user 标簽的子產品

安裝所有産品定義檔案中指定的 apk 子產品,apk 子產品的标簽将被忽略

userdebug

該編譯類型适合用于 debug 階段。

該類型和 user 一樣,除了:

會安裝包含 debug 标簽的子產品

編譯出的系統具有 root 通路權限

根據上表各種類型子產品的編譯方式,要執行編譯,隻需要引入表 3 中對應的 make 檔案即可。例如,要編譯一個 apk 檔案,隻需要在 android.mk 檔案中,加入“include $(build_package)。

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

$(call my-dir):擷取目前檔案夾路徑。

$(call all-java-files-under, ):擷取指定目錄下的所有 java 檔案。

$(call all-c-files-under, ):擷取指定目錄下的所有 c 語言檔案。

$(call all-iaidl-files-under, ) :擷取指定目錄下的所有 aidl 檔案。

$(call all-makefiles-under, ):擷取指定目錄下的所有 make 檔案。

$(call intermediates-dir-for, , , ,

):擷取 build 輸出的目标檔案夾路徑。

如:編譯一個 apk 檔案

編譯一個 java 的靜态庫: