在Android的源碼中每個目錄下幾乎都有一個Android.mk的文件,這個文件就是用來管理當前目錄或子目錄 下的文件進行編譯的。我們打開 Android.mk文件,我們會發現它并沒有太多的Makefile語法,更多的是一些大寫的宏,例 如:LOCAL_PATH,LOCAL_MODULE等。此時我們就會在想,"Android源碼下的文件是如何編譯的呢?",實際上 Android源碼已經有比較完善的編譯系統,它是以模塊化的思想設計編譯系統的,Android源碼的每個目錄幾 乎都可以當獨編譯,我們可以給予這個編譯系統,編寫自己的Android.mk文件,將我們需要的文件進行編 譯。
Android的編譯系統相關的文件存放在Android源碼目錄下的build/core目錄下,這里不介紹Android源碼的 編譯系統,如果想了解Android編譯系統的讀者可以自己在網上查閱相關的資料。好了下面我們就來看看如何 編寫自己的Android.mk吧!
一、簡單的Android.mk
從編譯一個史上第一個程序開始吧,編寫編譯hello.c的Android.mk。
hello.c文件中的內容我想你是會寫的,這里就不貼出來了,下面我們來編譯一下這個模塊。
設置好了編譯環境,下面我們就來編譯我們的模塊吧!我的模塊放在Android源碼目錄下的external/test/test1目 錄下。
嗯,我們很輕松的就完成了在Android源碼目錄下編譯自己的模塊,下面我們來詳細分析一下每個變量的具體 含義。
(1) LOCAL_PATH := $(call my-dir)
$(call my-dir)這種寫法是Makefile中調用一個自定義函數的寫法,也就是獲取my-dir這個自定義函數的結果。下 面我們來看看my-dir這個
函數具體是如何實現的?
我們重點關注紅顏標注的地方,MAKEFILE_LIST是make解釋器內部定義的變量,它的含義就是獲取它所尋找 到的Makefile文件的絕對路徑列表。當我們在源碼目錄的頂層目錄進行"mmm"進行編譯的時候,Android的編 譯系統就會使用make工具找到相關的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
好了,我們總結一下 $(call my-dr)的終極含義:獲取當前模塊的路徑
(2) include $(CLEAR_VARS)
CLEAR_VARS這個變量在Android源碼樹下的build/core/config.mk文件中定義:
include類似于C語言中的頭文件包含,嗯,它的含義就是在我們的Android.mk文件中包含編譯系統目錄下的 clear_vars.mk這個文件中的內容。clear_vars.mk中就是將一些編譯的時候需要用到的一些變量清空。但是我可 以肯定它一定不會把LOCAL_PATH這個變量清空,想想為什么?
(3)LOCAL_MODULE
用來指定當前模塊的名稱
(4)LOCAL_SRC_FILES
用來指定當前模塊需要參與編譯的文件
(5)include $(BUILD_EXECUTABLE)
BUILD_EXECUTABLE這個變量在Android源碼樹下的build/core/config.mk文件中定義:
executable.mk文件中定義了如何編譯設備上的可執行文件。
二、編譯模塊下的多個文件
上面的Android.mk中我們只編譯了一個文件,如果有多個文件需要編譯該如何做呢?在Android的編譯系統 中,我們有兩種方法讓多個文件
可以參與編譯。
(1)將需要編譯的文件名都指定在LOCAL_SRC_FILES變量 例如:在我們的test1目錄下還有兩個文件 add.c 和 sub.c ,這兩個文件中的內容如下:
我們的Android.mk的內容如下:
LOCAL_MODULE_PATH = $(LOCAL_PATH)/bin
表示將編譯好的模塊存放在模塊所在目錄的bin子目錄下
(2)調用Android編譯系統的函數,獲取當前模塊下所有需要編譯的文件
我們以獲取C語言為例,來看看函數的具體實現:
(1)all-c-files-under
(2)all-subdir-c-files
嗯,對比一下兩者的區別:
(1)all-c-files-under 比較靈活,可以指定模塊下一個指定的子目錄下搜索所有的c語言文件 (2)all-subdir-c-files 是獲取模塊下所有的子目錄下的C語言文件 注意:在這里函數中,尋找Makefile文件的時候只會遞歸一級子目錄
好了,我們來看看我們修改后的Android.mk文件吧!
我們把所有的C語言文件存放在了src子目錄下,所以這里指定的是在src子目錄下搜索。
我們在hello.c中調用了add和sub函數,沒有聲明,所以編譯的時候報了警告,下面我們自己定義一個hello.h的頭
文件,在這個頭文件中 我們聲明這兩個函數。
問題:如何在Android.mk文件中指定自己的頭文件搜索路徑?
三、編譯靜態庫和動態庫
前面我們通過Android.mk將我們模塊中的代碼編譯成了ELF格式的可執行文件,下面我們來看看如何將自己的 模塊編譯成庫。
(1)BUILD_SHARED_LIBRARY
將模塊編譯成動態庫 , 例如:libadd_sub.so
(2)BUILD_STATIC_LIBRARY
將模塊編譯成靜態庫 , 例如:libadd_sub.a
四、鏈接庫
1、鏈接Android系統中自帶的庫
ALOGE是Android系統中用來輸出log信息的函數,它在liblog.so中,所以我們在編譯我們的代碼時候,要告訴 編譯系統,需要去連接liblog.so這個動態庫。
我們的Android.mk寫成如下形式:
(1)LOCAL_SHARED_LIBRARIES
告訴編譯系統需要鏈接的Android系統提供的動態庫
(2)LOCAL_STATIC_LIBRARIES
告訴編譯系統需要鏈接的Android系統提供的靜態庫
2、鏈接第三方庫 我們將add.c和sub.c編譯成libadd_sub.so,然后我們在test.c中調用add和sub這兩個函數,此時Android.mk應該寫 成如下形式:
LOCAL_LDFLAGS
指定鏈接參數, -L 指定需要連接的庫所在的路徑, -l指定庫的名字
五、預置編譯
所謂的預置編譯指的是將一個編譯好的可執行文件或APK以及他們依賴的庫、jar包拷貝到Android系統源 碼相關的存放路徑下,這樣我們在對Android 系統就行打包生成system.img鏡像時,這些東西就會被打包進 去。哦,還有就是當我們引入第三方庫或
問:為了不自己手動把這些東西拷貝到Android源碼對應的目錄下,然后在打包呀? 答:麻煩,Android版本總是在升級,目錄結構也總是在發生變化,使用預置編譯,所以的事情都由Android系 統自帶的編譯系統去做,這樣就何樂而不為呢?
下面我們以預置libadd_sub.so文件到Android系統中為例,來講解預置編譯的使用方法:
編譯效果如下:
解釋如下:
(1)LOCAL_MODULE
這里的含義和以前的含義不一樣,它表示文件(xx.apk/jar/so)預置到系統中之后的名字。例如:libadd_sub.so預 置之后的名字為libaddsub.so
(2)LOCAL_SRC_FILES
需要預置到系統中的文件
(3)LOCAL_MODULE_TAGS
這個變量可以賦值為user 、eng、tests、optional
user 指該模塊只在user版本下才編譯 eng 指該模塊只在eng版本下才編譯 tests 指該模塊只在tests版本下才編譯 optional 指該模塊在所有版本下都編譯
(4)LOCAL_MODULE_CLASS
這個變量用來指定文件類型,它可以賦的值有
APPS apk文件
SHARED_LIBRARIES 動態庫文件
JAVA_LIBRARIES dex歸檔文件 EXECUTABLES ELF格式文件
ETC 其他格式文件
(5)BUILD_PREBUILT 和 BUILD_MULTI_PREBUILT
不同點:
<1>BUILD_PREBUILT 只能針對一個文件, BUILD_MUTI_PREBUILT針對多個文件
<2>BUILD_PREBUILT 在預置的時候可以通過LOCAL_MODULE_PATH將文件拷貝到自己指定的路徑下,而
BUILD_MULTI_PREBUILT只能將文件拷貝到Android系統指定的路徑下。
六、引入第三方jar包
很多時候我們在做APP開發的時候都會用到第三方的jar包,那如何在Android系統中引入第三方jar包,編譯
java代碼生成apk文件呢?
我們來看看Android源碼中packages/apps/Calculator目錄下Android.mk的寫法。
Calculator app應用程序使用到了第三方的arity-2.1.2.jar文件,而arity-2.1.2.jar文件在Calculator當前目錄下,為了 方便引用arity-2.1.2.jar文件這里先通過預置編譯將arity-2.1.2.jar拷貝到Android 系統的相應目錄下,這樣開始 編譯Calculator應用程序的時候才可以找到它依賴的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
指定當前模塊依賴的jar包
(2)LOCAL_SDK_VERSION
指定當前SDK的版本為Android源碼中的SDK版本
(3)LOCAL_PACKAGE_NAME
指定生成的apk文件的名字
(4)LOCAL_CERTIFICATE
指定apk文件的簽名。可以指定的值有:testkey、media、platform、shared這四種,可以在源碼 build/target/product/security里面看到對應的秘鑰,其中shared.pk8代表私鑰,shared.x509.perm代表公鑰,一定 是成對出現的。
其中testkey是作為android編譯的時候默認的簽名key,如果系統中的apk的Android.mk中沒有設置 LOCAL_CERTIFICATE的值,就默認使用testkey。而如果設置成LOCAL_CERTIFICATE :=platform就代表使用 platform來簽名,這樣的話這個apk就擁有了和system相同的簽名,因為系統級別的簽名也是使用platform來簽 名的。
(5)BUILD_PACKAGE
將模塊編譯成APK文件
(6)LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES
指定prebuilt jar庫的規則,格式 別名 : jar文件路徑。注意:別名一定要與 LOCAL_STATIC_JAVA_LIBRARIES里所取的別名一致,且不含jar;jar文件路徑一定要是真實的存放第 三方jar包的路徑。編譯用BUILD_MULTI_PREBUILT。