深入浅出
第四章、Android編譯系統與定制Android平臺系統
4.1Android編譯系統
Android的源碼由幾十萬個文件構成,這些文件之間有的相互依賴,有的又相互獨立,它們按功能或類型又被放到不同目錄下,對于這個大的一個工程,Android通過自己的編譯系統完成編譯過程。
4.1.1 Android編譯系統介紹
Android和Linux一樣,他們的編譯系統都是通過Makefile工具來組織編譯源碼的。Makefile工具用來解釋和執行Makefile文件,在Makefile文件里定義好工程源碼的編譯規則,通過make命令即可以完成對整個工程的自動編譯。因此分析makefile文件是理解編譯系統的關鍵。
在Android中,下面幾個主要的makefile文件構成了Android編譯系統。
圖x-x Android編譯系統組成
①Makefile:編譯系統的入口Makefile文件,它只有一行代碼,包含build/core/main.mk
②build/core/main.mk:主要Makefile,定義了Android編譯系統的主線
③build/core/config.mk:根據用戶輸入的編譯選項導出配置變量,影響編譯目標
④build/core/envsetup.mk:定義大量全局變量,用戶編譯配置
⑤build/core/product_config.mk:根據用戶選擇的目標產品,定義編譯結果輸出目錄
⑥device/*/$(TARGET_DEVICE)/BoardConfig.mk:根據用戶選擇的目標產品找到對應的設備TARGET_DEVICE,加載設備的板級配置
⑦build/core/definitions.mk:定義編譯過程中用到的大量變量和宏,是編譯系統的函數庫
⑧MODULES_DIR/Android.mk :每個模塊的規則定義文件,它出現在每個要編譯的目錄下,如圖x-x所示,我們可以自己向Android系統中添加自己的模塊,來達到定制系統的目的。
圖x-x 模塊中的Android.mk文件
⑨build/core/Makefile:Android編譯目標規則定義文件,最終編譯結果在該文件中定義,如system.img、ramdisk.img、boot.img、userdata.img等
4.1.2 Android.mk文件
在Android源碼中,大量的源碼按照功能通過目錄來分類,同一功能的代碼通常被編譯成一個目標文件,目標文件不僅僅包含可執行C/C++應用程序,還包含動態庫、靜態庫、Java類庫、Android應用程序等,在Android編譯系統中,每個被編譯的目標文件被稱為一個模塊(module),在每個模塊的源碼目錄中必須創建一個Android.mk文件作為編譯規則,這些Android.mk文件在編譯時被編譯系統中的findleaves.py腳本包含進去。
@build/core/main.mk
489 subdir_makefiles :=
490$(shell build/tools/findleaves.py --prune=out --prune=.repo --prune=.git$(subdirs) Android.mk)
491
492 include $(subdir_makefiles)
注:findleaves.py由Python語言編譯的腳本,Python是一種執行效率比較高的面向對象的腳本,上述腳本意思是返回subdirs目錄下的Android.mk文件,但是會跳過out、.reop、.git目錄。
通常編譯一個模塊時編譯器需要知道以下內容:
Ø編譯什么文件?(指定源碼目錄和源碼文件)
Ø編譯器需要哪些編譯參數?
Ø編譯時需要哪些庫或頭文件?
Ø如何編譯?(編譯成動態庫、靜態庫、二進制程序、Android應用還是Java庫?)
Ø編譯目標
Android.mk的語法不同于Makefile,Android.mk語法更簡潔,用戶只需在Android.mk中定義出一些編譯變量,Android的編譯系統會根據Android.mk文件中變量的值進行編譯。
比如Zygote進程app_process模塊中的Android.mk如下面代碼所示:
@ frameworks/base/cmds/app_process/Android.mk
1LOCAL_PATH:= $(call my-dir) #指定源碼目錄
2include $(CLEAR_VARS) #包含清除編譯變量的mk文件,防止影響本次編譯
3
4LOCAL_SRC_FILES:= #指定被編譯源碼
5 app_main.cpp
6
7LOCAL_SHARED_LIBRARIES := #指定編譯Zygote時用到的其它動態庫
8 libcutils
9 libutils
10 libbinder
11 libandroid_runtime
12
13LOCAL_MODULE:= app_process #指定被編譯模塊的名字
14
15include $(BUILD_EXECUTABLE) #指定編譯方式,編譯成可執行程序
再比如Camera應用程序中的Android.mk:
@ packages/apps/Camera/Android.mk
1LOCAL_PATH:= $(call my-dir) #指定源碼目錄
2include $(CLEAR_VARS) #包含清除編譯變量的mk文件,防止影響本次編譯
3
4LOCAL_MODULE_TAGS := optional #指定應用程序標簽
5
6LOCAL_SRC_FILES := $(call all-java-files-under, src) #指定被編譯源碼
7
8LOCAL_PACKAGE_NAME := Camera #指定Android應用程序名
9LOCAL_SDK_VERSION := current #指定該應用程序依賴的SDK版本
10
11LOCAL_PROGUARD_FLAG_FILES := proguard.flags #指定混淆編譯配置文件
12
13include $(BUILD_PACKAGE) #指定模塊編譯方式,這兒編譯成Android應用程序
14
15 # Usethe following include to make our test apk.
16include $(call all-makefiles-under,$(LOCAL_PATH)) # 包含當前目錄下子目錄中的Android.mk文件,向下編譯
通過上面兩個例子可以看出來,Android.mk文件結構很簡單,每個模塊的Android.mk文件必須完成以下操作:
Ø指定當前模塊的目錄
通過調用$(call my-dir)命令包(一些Makefile的集合),來獲得當前模塊目錄。
Ø清除所有的LOCAL_XX變量
通過include命令包含clear_vars.mk文件來清除所有的LOCAL_XX變量,防止影響本次編譯結果,clear_vars.mk文件由變量CLEAR_VARS來定義
Ø指定源碼文件
通過LOCAL_SRC_FILES變量指定源碼文件,對于C/C++文件,要將它們全部列出來賦值給LOCAL_SRC_FILES(見上面程序代碼),對于Java源碼,可以通過調用命令包$(callall-java-files-under, src)來實現,它會在src目錄下查找所有的Java文件,將其羅列出來。
Ø指定編譯細節
在編譯時可能需要修改編譯器參數、需要鏈接其它的庫、需要其它路徑下的頭文件等編譯細節。
Ø指定目標模塊名
如果是C/C++庫、可執行程序或Java類庫,通過LOCAL_MODULE指定最終編譯出來的模塊名,如果是Android應用程序,通過LOCAL_PACKAGE_NAME變量來指定。
Ø指定目標模塊類型
模塊最終都要進行編譯,通過include 命令包含一些預定義好的變量來指定模塊最終的類型,這些變量分別對應一個makefile文件,包含了模塊類型的編譯過程。主要的預定義編譯變量如下:
|
編譯變量 |
功能 |
|
BUILD_SHARED_LIBRARY |
將模塊編譯成共享庫 |
|
BUILD_STATIC_LIBRARY |
將模塊編譯成靜態庫 |
|
BUILD_EXECUTABLE |
將模塊編譯成可執行文件 |
|
BUILD_JAVA_LIBRARY |
將模塊編譯成Java類庫 |
|
BUILD_PACKAGE |
將模塊編譯成Android應用程序包 |
注:上述編譯變量的定義在build/core/definitions.mk中。
在Android.mk中,主要編譯變量如下表所示:
|
編譯變量 |
功能 |
|
LOCAL_PATH |
指定編譯路徑 |
|
LOCAL_MODULE |
指定編譯模塊名 |
|
LOCAL_SRC_FILES |
指定編譯源碼列表 |
|
LOCAL_SHARED_LIBRARIES |
指定使用的C/C++共享庫列表 |
|
LOCAL_STATIC_LIBRARIES |
指定使用的C/C++靜態庫列表 |
|
LOCAL_STATIC_JAVA_LIBRARIES |
指定使用的Java庫列表 |
|
LOCAL_CFLAGS |
指定編譯器參數 |
|
LOCAL_C_INCLUDES |
指定C/C++頭文件路徑 |
|
LOCAL_PACKAGE_NAME |
指定Android應用程序名 |
|
LOCAL_CERTIFICATE |
指定簽名認證 |
|
LOCAL_JAVA_LIBRARIES |
指定使用的Java庫列表 |
|
LOCAL_SDK_VERSION |
指定編譯Android應用程序時的SDK版本 |
注:其它的編譯變量見附錄。
4.1.3實驗:編譯HelloWorld應用程序
【實驗內容】
在Ubuntu系統中使用eclipse開發環境編寫簡單的HelloWorld應用程序,然后使用Android編譯系統進行編譯,最終將HelloWorld應用程序作為系統應用集成到Android系統中。
【實驗目的】
通過實驗,學員掌握在Android源碼的編譯系統中編譯Android應用程序、庫、可執行程序,了解Android系統應用程序的定制過程,最終在Android模擬器中,運行自己通過編譯系統編譯的Android應用程序。
【實驗平臺】
擁有Android源碼的Ubuntu操作系統(可以在Windows系統中虛擬Ubuntu系統)。
【實驗步驟】
1.打開eclipse開發環境,創建一個Android應用程序:HelloWorld:
$ cd ~/android/eclipse
$./eclipse &
2.將新創建的HelloWorld工程拷貝到源碼目錄中的packages/apps目錄下:
$ cp -rf HelloWorld/~/android/android_source/packages/apps
在HelloWorld工程目錄下刪除由eclipse開發環境自動生成的文件和目錄,僅保留如圖x-x所示工程目錄結構:
3.編譯HelloWorld工程的Android.mk文件,我們可以仿照Android里自帶的應用程序的Android.mk文件,例如Camera工程中的Android.mk文件:
將Camera工程中的Android.mk文件拷貝到HelloWorld工程中:
$ cp ../Camera/Android.mk./
修改Android.mk文件,刪除沒必要的編譯變量:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_PACKAGE_NAME :=HelloWorld
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
4.編譯HelloWorld工程:
Ø切換到Android源碼目錄下:
$ cd ~/android/android_source/
Ø加載編譯函數:
$ source build/envsetup.sh
Ø選擇編譯目標項:
$ lunch generic-eng
Ø通過mmm命令編譯HelloWorld工程:
$ mmm packages/apps/HelloWorld/
Ø編譯生成模擬器映像system.img:
$ make snod
注:我們也可以直接通過make命令來編譯HelloWorld工程并生成system.img映像文件,但是這種方式耗時比較長,所以我們使用上面的編譯方式,能節省實驗時間,關于Android源碼編譯的細節,請查看2.3.2編譯Android章節。
5.啟動模擬器,查看HelloWorld應用程序運行效果:
$ ./run_emulator.sh
注:run_emulator.sh是快速運行模擬器的腳本,詳細說明請查看2.5定制Android模擬器章節。
總結
- 上一篇: CSDN 迷你博客错误
- 下一篇: convert 批量文件的格式转换