編輯:關於Android編程
本文的目的是用比較容易理解的方式,介紹一下整個Android項目的編譯。至少知道大概的編譯流程是怎麼樣的,項目裡面的Android.mk文件包含些什麼內容。
makefile文件用來描述文件之間的依賴關系,並描述文件的編譯規則。我們知道從源代碼到可執行程序,中間要經歷編譯生成中間文件(windows裡面的obj文件,Linux裡面的.o文件),鏈接這些中間文件生成可執行文件的過程。
一個最簡單的makefile文件為:
main.o : main.c defs.h cc -c main.c
main.o依賴main.c和defs.h兩個文件,使用命令cc來編譯main.c最終生成main.o這個中間文件。
當然我們在Android中極少看到這種形式的makefile文件。我想講的重點是,Android中的mk文件會定義一些全局變量,描述模塊編譯的先後順序,聲明模塊編譯依賴的其他模塊(包括一些三方庫)。整個Android項目是由很多模塊組成,項目的編譯涉及到譬如Java源碼的編譯,C源碼的編譯,python源碼的編譯等等。在make文件中同樣也定義了這些編譯工具的路徑,這樣我們就可以調用這些工具來編譯不同種類的源碼。
我們一般編譯項目會執行如下命令:
source build/envsetup.sh lunch 3 #或者lunch full_eng 取決於你當前的項目配置情況 make -j8
第一條命令引入了shell腳本envsetup.sh,在這個文件頭的注釋裡面就描述清楚了該文件的作用。我簡單翻譯如下:
將下面這些命令引入到編譯環境中: - lunch: lunch- ,指定編譯哪個目標設備。 - tapas: tapas [ ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user] - croot: 切換工作目錄到項目根目錄. - m: 在源碼根目錄執行make命令. - mm: 僅編譯當前目錄下面的模塊,但是不包括他們的依賴(比如某個模塊依賴的模塊再另一個地址,所依賴的模塊就不會被編譯). - mmm: 同mm,不過是編譯該命令後面制定的目錄下的所有模塊,同樣不包含模塊的依賴。 To limit the modules being built use the syntax: mmm dir/:target1,target2. - mma: 編譯當前目錄下所有的模塊和它們的依賴。 - mmma: 編譯制定目錄下所有的模塊和它們的依賴。 - cgrep: 在所有的C/C++文件中搜索,用法同grep。 - ggrep: 在所有的gradle文件中搜索。 - jgrep: 在所有的Java文件中搜索。 - resgrep: 在所有的res/*.xml資源文件中搜索。 - mangrep: 在所有的AndroidManifest.xml資源文件中搜索。 - sepgrep: 在所有的sepolicy文件(後綴為te的文件)中搜索。 - sgrep: Greps on all local source files. - godir: Go to the directory containing a file. - carrier: for open cmcc cu and others
上面的命令在執行完第一條命令之後都是可以使用的,在平常使用中可以節省大量的時間,特別是m/mm/mmm/mma/mmma編譯命令和grep的變種命令。
lunch執行載入某個項目的編譯環境和設置,最後執行make命令即可開始編譯。-j8的意思是開啟8個工作線程(j代表job),8這個數值可以根據自己的cpu內核來決定,一般一個內核可以開雙線程,所以如果是4核cpu,那麼就可以用8。執行make命令後,Android編譯系統會根據當前的編譯環境設置,以根目錄下面的Makefile文件內容作為編譯啟動入口,執行編譯指令。包括編譯系統核心源碼,編譯廠商自己添加的模塊源碼,編譯應用模塊的源碼(Android.mk),再將編譯結果鏈接在一起輸出,就是我們最後燒機的img文件了。
編譯結果輸出到源碼根目錄下面的out/文件夾。
/out/host/:在Android編譯過程中要用到的各種主機工具,包括各種SDK tools:emulator,adb,aapt等。所謂主機,是相對你的目標工程機來說的,也就是你用來編譯這套代碼的電腦。
/out/target/common/:針對目標設備的編譯結果,主要是JAVA應用代碼和庫文件。
/out/target/product//:特定產品的編譯結果,裡面包含具體平台的代碼,比如C/C++庫和二進制文件。其實就是庫文件和鏡像文件。
/out/dist/。這個比較少看到就不說了。參考文獻裡面有詳細的說明。
如果太深入細節去看,我們就很容易暈頭,所以要分模塊和分種類。譬如說建房子,我們從一塊塊磚頭來復述建房子過程,那鐵定是痛苦和漫長的過程。但是如果我們先搭建鋼筋混凝土框架,再把磚頭往框架裡面塞。這樣理解起來是不是很快。
上面講到Android編譯系統中make的三種類型,核心代碼make,廠商項目特定make,應用模塊源碼Android.mk。
前文講到make的入口是根目錄的Makefile文件,內容如下:
### DO NOT EDIT THIS FILE ### include build/core/main.mk ### DO NOT EDIT THIS FILE ###
其實就是跳轉到了build/core/目錄下面的main.mk,這個目錄就是整個系統核心源碼編譯的所有make文件都在這個目錄了。包括廠商項目特定make和應用模塊的make都會在編譯過程中被覆蓋到。這個文件是頂重要的,描述了編譯的整個流程,串聯起了所有的make文件。
subdir_makefiles := \ $(shell build/tools/findleaves.py $(FIND_LEAVES_EXCLUDES) $(subdirs) Android.mk)
上面的代碼摘自main.mk,可以看到其實所有的Android.mk文件是使用python腳本來搜索的。而且main.mk裡面include了很多make文件,設置了一些編譯變量,在分析Android.mk的時候,我們會分析其中一部分變量。
注意:所有make文件裡面定義的變量都是在同一個命名空間裡面,所以要考慮到重命名和變量在使用前清空。這個就是為什麼Android.mk裡都包含include $(CLEAR_VARS)這個語句。同樣CLEAR_VARS這個變量對應的是”build/core/”文件夾下面的clear_vars.mk文件。很多我們在Android.mk裡面看到的變量都是在main.mk裡面引用的make文件裡面定義了。
下面是從參考文獻裡面引入的一張main.mk調用圖
像一棵樹一張,串聯起了整個項目的編譯過程。這些mk文件的作用,請仔細閱讀參考文獻2.
Android源碼包含的模塊也是分類的,比如Java庫,APK應用,主機庫,目標機器共享庫,可執行文件等等。對於同一種類型的模塊,不適用的編譯方法也是一樣的,所以Android將不同模塊的編譯方法抽取出來,定義了下面的變量:
BUILD_HOST_STATIC_LIBRARY/BUILD_HOST_SHARED_LIBRARY:編譯主機靜態/共享庫
BUILD_STATIC_LIBRARY/BUILD_SHARED_LIBRARY:編譯目標機器靜態共享庫
BUILD_EXECUTABLE/BUILD_HOST_EXECUTABLE:編譯目標機器可執行文件/編譯主機可執行文件
BUILD_PACKAGE:編譯APK文件
BUILD_PREBUILT/BUILD_MULTI_PREBUILT:如何處理一個或多個已經編譯好的文件(例如一個jar包)
BUILD_HOST_PREBUILT:同上,針對主機。
BUILD_JAVA_LIBRARY/ BUILD_STATIC_JAVA_LIBRARY:如何編譯設備上的Java庫/如何編譯設備上的靜態Java庫
BUILD_HOST_JAVA_LIBRARY:如何編譯主機上的Java庫
這些變量都是在同目錄下面的config.mk文件裡面定義的,這個mk文件同樣在main.mk裡面被include了。以BUILD_PACKAGE為例,定義如下:
#config.mk BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
其實引入的是package.mk模塊。這個跟我們上面說的針對不同模塊,Android抽取了不同的編譯方法。所以針對APK包的編譯,Android使用的是package.mk進行處理,這個也是一種復用,防止出現冗余的編譯代碼。
一般在同一套代碼下面,不同的廠商會定義自家的多個產品。通常這些產品差異是硬件配置,而不同的硬件對應不同的驅動。所以不同的產品都需要有自己的make文件來告訴編譯系統應該編譯哪些產品本身的文件。一般都會在”/device“目錄下面定義廠商的文件夾,而這個文件夾下面會定義具體產品相關的文件夾,這個文件夾下面就放置的是具體產品相關的make文件。device目錄結構如下:
在這些mk文件裡面,最重要的是AndroidProducts.mk和BoardConfig.mk文件。一般產品只是有部分配置改變,所以其實這些make文件可以繼承某個make文件,僅僅修改差異的部分。AndroidProducts.mk其實定義了如下語句:
$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
所以可以看到這個make文件其實是繼承了core_64_bit.mk文件的,這個也簡化了我們建立新項目的步驟。
Android編譯系統會將源碼分成不同的模塊。大家經常看到Android.mk文件,這個文件一般放在模塊的根目錄下面,描述的是該模塊如何編譯,依賴哪些模塊,最後以什麼形式輸出。我們以Settings模塊為例,介紹Android.mk裡面的元素和用法:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES :=\ javalib.jar:libs/javalib.jar include $(BUILD_MULTI_PREBUILT) include $(CLEAR_VARS) LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt telephony-common ims-common audiopostprocess LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v13 common_zxing jsr305 letv-domain-common LOCAL_MODULE_TAGS := optional res_dirs := cus_res res LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs)) LOCAL_SRC_FILES := \ $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := Settings LOCAL_CERTIFICATE := platform LOCAL_PRIVILEGED_MODULE := true LOCAL_PROGUARD_FLAG_FILES := proguard.flags ifneq ($(INCREMENTAL_BUILDS),) LOCAL_PROGUARD_ENABLED := disabled endif include frameworks/base/packages/SettingsLib/common.mk include $(BUILD_PACKAGE)
一般開頭都會有兩行代碼:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS)
作用是:
將當前模塊的根目錄賦值為一個變量LOCAL_PATH,方便後面引用。
我們前面說過,所有的mk文件都是被編譯系統集成在一起的,所以所有的變量都是在同一個命名空間裡面。CLEAR_VARS用於清楚一些變量的值,防止影響到本模塊的編譯。從include語法看,這個變量應該指向的是某個mk文件。
剩下的大部分都是變量定義,我們看看這些變量含義:
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 是默認標簽。標簽是提供給編譯類型使用的。
這些都不是最關鍵的,僅僅只是變量賦值罷了,點睛之筆是最後的
include $(BUILD_PACKAGE)
這個變量我們上面提到過
BUILD_PACKAGE:編譯APK文件 #config.mk BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
所以這個地方是引入package.mk文件,而這個文件會使用在這個Android.mk文件裡面賦值的各種變量,編譯生成制定的目標。
Chromium的Extension由Page和Content Script組成。如果將Extension看作是一個App,那麼Page和Content Script就是
SQLite是一款開源的、嵌入式關系型數據庫,第一個版本Alpha發布於2000年。SQLite在便攜性、易用性、緊湊性、高效性和可靠性方面有著突出的表現。在Androi
概述首先,應該了解的幾個問題:1)Android平台網絡相關API接口a) java.net.*(標准Java接口) java.net.*提供與聯網有關的類,包
靠譜助手安卓模擬器黑屏/閃退的最有可能的原因在於顯卡驅動。顯卡驅動的兼容性對於安卓模擬器向來都是個大難題,各大安卓模擬器也在努力解決。但是顯卡型