從對Makefile一無所知開始,折騰了一個多星期,終於對Android.mk有了一個全面些的瞭解。瞭解了標準的Makefile後,發現 Android.mk其實是把真正的Makefile封裝起來,做成了一個對使用者來說很簡單的東西。使用它來編譯器時,不管是動態庫、可執行檔二進位檔案,還是Jar庫、APK包,只要沿著一個簡單的思路來做三大步就可以了:清除舊變數,設定新變數,調用編譯函數。
明白了以後,發現Makefile文法不是問題,有很多教程和高手。編譯模組時如何清除變數、調用編譯函數等也不是問題,源碼當中無處不在這樣的例子。而對初學者來說,更需要明白的可能是,Android如何讓使用指令碼的人從Makefile文法當中解放出來,簡單地按照上面的三大步就可以編譯出任何模組。
拿AlarmClock來做例子的話:
//清除舊變數
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
//設定新變數
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := AlarmClock
//調用編譯函數
include $(BUILD_PACKAGE)
下面簡單解釋一下這三步:
1、清除舊變數,是因為Android.mk中所有的變數都是全域的,編譯函數在編譯時間會調用這些變數。為了防止編譯函數使用了編譯其它模組時設定的變數,每次開始編譯一個新的模組時清除所有的變數是個好習慣。
2、設定新變數就是把本次編譯時間用到的源碼地址,包名等設定好。
3、調用編譯函數其實就是include一個固定的mk檔案,這個mk檔案會根據設定的變數提取出編譯模組需要的target,Command等資訊並執行固定的編譯命令。
看明白Android.mk後,最深的感受還是Android的這種封裝思想,讓我想起了很多以前沒有思考過的東西。呵。。。
我會一些個人的理解記錄Android一步步地演化出這種封裝思想。真誠地歡迎批評指正。
Makefile這個東西,往最簡單處說,就是這樣的模式:
目標:信賴檔案
執行命令
這裡暫不考慮它的文法細節,舉個不完整的例子,就是這樣的:
AlarmClock.apk:AlarmClock.java
javac AlarmClock.java
java AlarmClock.class
要產生AlarmClock.apk,就需要AlarmClock.java這個信賴檔案,然後執行後面兩行的兩個命令。假如我們簡單粗暴地用這種寫法去編譯Android系統的話,會相當累。Android.mk就很巧妙地把它們進行了抽象。我們現在來類比一下:
定義三個變數target,source,command,分別代表編譯目標、信賴檔案和編譯命令,就成了這樣:
$(target):$(source)
$(command)
這三個變數的值分別是:
target := AlarmClock.apk
source := AlarmClock.java
commnd := /
javac AlarmClock.java/
java AlarmClock.class
這樣的話,在編譯每個APK時,只要分別給target、source、command賦值,然後再這樣寫就可以了:
$(target):$(source)
$(command)
嗯,還是有點兒麻煩,那再抽象一次。定義一個變數name,讓它存放要編譯的模組的名字:
name := AlarmClock
然後再更改一下前面的三個變數:
target := $(name).apk
source := $(name).java
commnd := /
javac $(name).java/
java $(name).class
現在要編譯一個模組Contacts需要做些什麼呢?
name := Contacts
$(target):$(source)
$(command)
三行代碼就可以了,很簡單,對吧?但後面兩行還是每次都要寫,那再抽象。把後面兩行放到一個build_apk.mk檔案當中。然後定義一個變數:
BUILD_PACKAGES := build_apk.mk
全部完了,這回編譯一個模組Contacts時只要這樣做就可以了:
name := Contacts
include $(BUILD_PACKAGES)
一個不夠複雜,那就多點兒:
name := Contacts
include $(BUILD_PACKAGES)
name := Phone
include $(BUILD_PACKAGES)
name := Email
include $(BUILD_PACKAGES)
//================強壯的分隔線=========
現在我們來完善一下上面的過程,把它系統化。
定義一個專門用來清除變數的clear_vars.mk裡面的內容如下:
name :=
target :=
source :=
command :=
再定義一個變數CLEAR_VARS := clear_vars.mk。上面的編譯指令碼就變成了這樣:
include $(CLEAR_VARS)
name := Contacts
include $(BUILD_PACKAGES)
include $(CLEAR_VARS)
name := Phone
include $(BUILD_PACKAGES)
include $(CLEAR_VARS)
name := Email
include $(BUILD_PACKAGES)
這回,清除舊變數、設定新變數、調用編譯函數三大步全都有了。
好了,現在我們進入到Android源碼的build/core/目錄下。先看其中的main.mk,config.mk這兩個檔案。
main.mk裡面是具體的指令碼,在這裡控制編譯模組。
config.mk中定義了下面這樣的檔案調用:
CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
編譯模組時,每一個include其實都是一次功能調用,或者對mk檔案的調用,或者對變數和函數的調用。做為單獨的功能模組抽象出來的mk檔案都在build/core/目錄下,而函數的定義全部在definitions.mk中。
萬事開頭難,有了上面的思路,再看這些指令碼就很簡單了。一路順風,呵。。。
轉自:http://blog.csdn.net/a345017062/article/details/6130264