當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > Android.mk分析
在Android的源碼中每個目錄下幾乎都有一個Android.mk的文件,這個文件就是用來管理當(dāng)前目錄或子目錄 下的文件進(jìn)行編譯的。我們打開 Android.mk文件,我們會發(fā)現(xiàn)它并沒有太多的Makefile語法,更多的是一些大寫的宏,例 如:LOCAL_PATH,LOCAL_MODULE等。此時我們就會在想,"Android源碼下的文件是如何編譯的呢?",實(shí)際上 Android源碼已經(jīng)有比較完善的編譯系統(tǒng),它是以模塊化的思想設(shè)計(jì)編譯系統(tǒng)的,Android源碼的每個目錄幾 乎都可以當(dāng)獨(dú)編譯,我們可以給予這個編譯系統(tǒng),編寫自己的Android.mk文件,將我們需要的文件進(jìn)行編 譯。
Android的編譯系統(tǒng)相關(guān)的文件存放在Android源碼目錄下的build/core目錄下,這里不介紹Android源碼的 編譯系統(tǒng),如果想了解Android編譯系統(tǒng)的讀者可以自己在網(wǎng)上查閱相關(guān)的資料。好了下面我們就來看看如何 編寫自己的Android.mk吧!
一、簡單的Android.mk
從編譯一個史上第一個程序開始吧,編寫編譯hello.c的Android.mk。
hello.c文件中的內(nèi)容我想你是會寫的,這里就不貼出來了,下面我們來編譯一下這個模塊。
設(shè)置好了編譯環(huán)境,下面我們就來編譯我們的模塊吧!我的模塊放在Android源碼目錄下的external/test/test1目 錄下。
嗯,我們很輕松的就完成了在Android源碼目錄下編譯自己的模塊,下面我們來詳細(xì)分析一下每個變量的具體 含義。
(1) LOCAL_PATH := $(call my-dir)
$(call my-dir)這種寫法是Makefile中調(diào)用一個自定義函數(shù)的寫法,也就是獲取my-dir這個自定義函數(shù)的結(jié)果。下 面我們來看看my-dir這個
函數(shù)具體是如何實(shí)現(xiàn)的?
我們重點(diǎn)關(guān)注紅顏標(biāo)注的地方,MAKEFILE_LIST是make解釋器內(nèi)部定義的變量,它的含義就是獲取它所尋找 到的Makefile文件的絕對路徑列表。當(dāng)我們在源碼目錄的頂層目錄進(jìn)行"mmm"進(jìn)行編譯的時候,Android的編 譯系統(tǒng)就會使用make工具找到相關(guān)的Makefile文件。我們的模塊里面的Makefile就是后一個Makefile文件 了。
LOCAL_MODULE_MAKEFILE := $$(lastword $$(MAKEFILE_LIST)) 獲取后一個Makefile文件絕對 路徑
$(dir $(LOCAL_MODULE_MAKEFILE)) 獲取目錄路徑,不包含文件 例如:$(dir /home/linux/Makefile) ->
/home/linux/
$(patsubst %/, %, /home/linux/) -> /home/linux
好了,我們總結(jié)一下 $(call my-dr)的終極含義:獲取當(dāng)前模塊的路徑
(2) include $(CLEAR_VARS)
CLEAR_VARS這個變量在Android源碼樹下的build/core/config.mk文件中定義:
include類似于C語言中的頭文件包含,嗯,它的含義就是在我們的Android.mk文件中包含編譯系統(tǒng)目錄下的 clear_vars.mk這個文件中的內(nèi)容。clear_vars.mk中就是將一些編譯的時候需要用到的一些變量清空。但是我可 以肯定它一定不會把LOCAL_PATH這個變量清空,想想為什么?
(3)LOCAL_MODULE
用來指定當(dāng)前模塊的名稱
(4)LOCAL_SRC_FILES
用來指定當(dāng)前模塊需要參與編譯的文件
(5)include $(BUILD_EXECUTABLE)
BUILD_EXECUTABLE這個變量在Android源碼樹下的build/core/config.mk文件中定義:
executable.mk文件中定義了如何編譯設(shè)備上的可執(zhí)行文件。
二、編譯模塊下的多個文件
上面的Android.mk中我們只編譯了一個文件,如果有多個文件需要編譯該如何做呢?在Android的編譯系統(tǒng) 中,我們有兩種方法讓多個文件
可以參與編譯。
(1)將需要編譯的文件名都指定在LOCAL_SRC_FILES變量 例如:在我們的test1目錄下還有兩個文件 add.c 和 sub.c ,這兩個文件中的內(nèi)容如下:
我們的Android.mk的內(nèi)容如下:
LOCAL_MODULE_PATH = $(LOCAL_PATH)/bin
表示將編譯好的模塊存放在模塊所在目錄的bin子目錄下
(2)調(diào)用Android編譯系統(tǒng)的函數(shù),獲取當(dāng)前模塊下所有需要編譯的文件
我們以獲取C語言為例,來看看函數(shù)的具體實(shí)現(xiàn):
(1)all-c-files-under
(2)all-subdir-c-files
嗯,對比一下兩者的區(qū)別:
(1)all-c-files-under 比較靈活,可以指定模塊下一個指定的子目錄下搜索所有的c語言文件 (2)all-subdir-c-files 是獲取模塊下所有的子目錄下的C語言文件 注意:在這里函數(shù)中,尋找Makefile文件的時候只會遞歸一級子目錄
好了,我們來看看我們修改后的Android.mk文件吧!
我們把所有的C語言文件存放在了src子目錄下,所以這里指定的是在src子目錄下搜索。
我們在hello.c中調(diào)用了add和sub函數(shù),沒有聲明,所以編譯的時候報(bào)了警告,下面我們自己定義一個hello.h的頭
文件,在這個頭文件中 我們聲明這兩個函數(shù)。
問題:如何在Android.mk文件中指定自己的頭文件搜索路徑?
三、編譯靜態(tài)庫和動態(tài)庫
前面我們通過Android.mk將我們模塊中的代碼編譯成了ELF格式的可執(zhí)行文件,下面我們來看看如何將自己的 模塊編譯成庫。
(1)BUILD_SHARED_LIBRARY
將模塊編譯成動態(tài)庫 , 例如:libadd_sub.so
(2)BUILD_STATIC_LIBRARY
將模塊編譯成靜態(tài)庫 , 例如:libadd_sub.a
四、鏈接庫
1、鏈接Android系統(tǒng)中自帶的庫
ALOGE是Android系統(tǒng)中用來輸出log信息的函數(shù),它在liblog.so中,所以我們在編譯我們的代碼時候,要告訴 編譯系統(tǒng),需要去連接liblog.so這個動態(tài)庫。
我們的Android.mk寫成如下形式:
(1)LOCAL_SHARED_LIBRARIES
告訴編譯系統(tǒng)需要鏈接的Android系統(tǒng)提供的動態(tài)庫
(2)LOCAL_STATIC_LIBRARIES
告訴編譯系統(tǒng)需要鏈接的Android系統(tǒng)提供的靜態(tài)庫
2、鏈接第三方庫 我們將add.c和sub.c編譯成libadd_sub.so,然后我們在test.c中調(diào)用add和sub這兩個函數(shù),此時Android.mk應(yīng)該寫 成如下形式:
LOCAL_LDFLAGS
指定鏈接參數(shù), -L 指定需要連接的庫所在的路徑, -l指定庫的名字
五、預(yù)置編譯
所謂的預(yù)置編譯指的是將一個編譯好的可執(zhí)行文件或APK以及他們依賴的庫、jar包拷貝到Android系統(tǒng)源 碼相關(guān)的存放路徑下,這樣我們在對Android 系統(tǒng)就行打包生成system.img鏡像時,這些東西就會被打包進(jìn) 去。哦,還有就是當(dāng)我們引入第三方庫或
問:為了不自己手動把這些東西拷貝到Android源碼對應(yīng)的目錄下,然后在打包呀? 答:麻煩,Android版本總是在升級,目錄結(jié)構(gòu)也總是在發(fā)生變化,使用預(yù)置編譯,所以的事情都由Android系 統(tǒng)自帶的編譯系統(tǒng)去做,這樣就何樂而不為呢?
下面我們以預(yù)置libadd_sub.so文件到Android系統(tǒng)中為例,來講解預(yù)置編譯的使用方法:
編譯效果如下:
解釋如下:
(1)LOCAL_MODULE
這里的含義和以前的含義不一樣,它表示文件(xx.apk/jar/so)預(yù)置到系統(tǒng)中之后的名字。例如:libadd_sub.so預(yù) 置之后的名字為libaddsub.so
(2)LOCAL_SRC_FILES
需要預(yù)置到系統(tǒng)中的文件
(3)LOCAL_MODULE_TAGS
這個變量可以賦值為user 、eng、tests、optional
user 指該模塊只在user版本下才編譯 eng 指該模塊只在eng版本下才編譯 tests 指該模塊只在tests版本下才編譯 optional 指該模塊在所有版本下都編譯
(4)LOCAL_MODULE_CLASS
這個變量用來指定文件類型,它可以賦的值有
APPS apk文件
SHARED_LIBRARIES 動態(tài)庫文件
JAVA_LIBRARIES dex歸檔文件 EXECUTABLES ELF格式文件
ETC 其他格式文件
(5)BUILD_PREBUILT 和 BUILD_MULTI_PREBUILT
不同點(diǎn):
<1>BUILD_PREBUILT 只能針對一個文件, BUILD_MUTI_PREBUILT針對多個文件
<2>BUILD_PREBUILT 在預(yù)置的時候可以通過LOCAL_MODULE_PATH將文件拷貝到自己指定的路徑下,而
BUILD_MULTI_PREBUILT只能將文件拷貝到Android系統(tǒng)指定的路徑下。
六、引入第三方j(luò)ar包
很多時候我們在做APP開發(fā)的時候都會用到第三方的jar包,那如何在Android系統(tǒng)中引入第三方j(luò)ar包,編譯
java代碼生成apk文件呢?
我們來看看Android源碼中packages/apps/Calculator目錄下Android.mk的寫法。
Calculator app應(yīng)用程序使用到了第三方的arity-2.1.2.jar文件,而arity-2.1.2.jar文件在Calculator當(dāng)前目錄下,為了 方便引用arity-2.1.2.jar文件這里先通過預(yù)置編譯將arity-2.1.2.jar拷貝到Android 系統(tǒng)的相應(yīng)目錄下,這樣開始 編譯Calculator應(yīng)用程序的時候才可以找到它依賴的jar包。
我們看看它的編譯流程:
make: Entering directory `/home/a/workdir/androidL'
target R.java/Manifest.java: Calculator (out/target/common/obj/APPS/Calculator_intermediates/src/R.stamp) target Prebuilt: Calculator (out/target/common/obj/JAVA_LIBRARIES/libarity_intermediates/classes.jar) target Prebuilt: Calculator (out/target/common/obj/JAVA_LIBRARIES/libarity_intermediates/javalib.jar) target Java: Calculator (out/target/common/obj/APPS/Calculator_intermediates/classes)
Copying: out/target/common/obj/APPS/Calculator_intermediates/classes-jarjar.jar
Copying: out/target/common/obj/APPS/Calculator_intermediates/emma_out/lib/classes-jarjar.jar Copying: out/target/common/obj/APPS/Calculator_intermediates/classes.jar
Proguard: out/target/common/obj/APPS/Calculator_intermediates/proguard.classes.jar ProGuard, version 4.10
Reading program jar [/home/a/workdir/androidL/out/target/common/obj/APPS/Calculator_intermediates/classes.jar] Reading library jar [/home/a/workdir/androidL/out/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/classes.ja r]
Preparing output jar [/home/a/workdir/androidL/out/target/common/obj/APPS/Calculator_intermediates/proguard.classes.jar] Copying resources from program jar [/home/a/workdir/androidL/out/target/common/obj/APPS/Calculator_intermediates/classes.jar]
target Dex: Calculator
Copying: out/target/common/obj/APPS/Calculator_intermediates/classes.dex
target Package: Calculator (out/target/product/fspad-733/obj/APPS/Calculator_intermediates/package.apk) Notice file: packages/apps/Calculator/NOTICE -- out/target/product/fspad- 733/obj/NOTICE_FILES/src//system/app/Calculator/Calculator.apk.txt
Install: out/target/product/fspad-733/system/app/Calculator/Calculator.apk target Prebuilt: libarity (out/target/product/fspad- 733/obj/JAVA_LIBRARIES/libarity_intermediates/javalib.jar)
make: Leaving directory `/home/a/workdir/androidL'
好了,下面我們來看看這個Android.mk中我們前面沒有用過的變量。
(1)LOCAL_STATIC_JAVA_LIBRARIES
指定當(dāng)前模塊依賴的jar包
(2)LOCAL_SDK_VERSION
指定當(dāng)前SDK的版本為Android源碼中的SDK版本
(3)LOCAL_PACKAGE_NAME
指定生成的apk文件的名字
(4)LOCAL_CERTIFICATE
指定apk文件的簽名?梢灾付ǖ闹涤:testkey、media、platform、shared這四種,可以在源碼 build/target/product/security里面看到對應(yīng)的秘鑰,其中shared.pk8代表私鑰,shared.x509.perm代表公鑰,一定 是成對出現(xiàn)的。
其中testkey是作為android編譯的時候默認(rèn)的簽名key,如果系統(tǒng)中的apk的Android.mk中沒有設(shè)置 LOCAL_CERTIFICATE的值,就默認(rèn)使用testkey。而如果設(shè)置成LOCAL_CERTIFICATE :=platform就代表使用 platform來簽名,這樣的話這個apk就擁有了和system相同的簽名,因?yàn)橄到y(tǒng)級別的簽名也是使用platform來簽 名的。
(5)BUILD_PACKAGE
將模塊編譯成APK文件
(6)LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES
指定prebuilt jar庫的規(guī)則,格式 別名 : jar文件路徑。注意:別名一定要與 LOCAL_STATIC_JAVA_LIBRARIES里所取的別名一致,且不含jar;jar文件路徑一定要是真實(shí)的存放第 三方j(luò)ar包的路徑。編譯用BUILD_MULTI_PREBUILT。