- Android核心原理與系統級應用高效開發
- 韓超等
- 2943字
- 2018-12-30 05:22:12
2.2 各種部件的構建
一般情況下,在使用了envsetup進行環境設置后,可以通過mmm進行編譯一個工程,如下所示:
$ mmm {project_path}
↘ 2.2.1 Android.mk的語法
Android.mk是Android工程的管理文件,其作用基本等同于Linux環境中的Makefile。在語法上,Android.mk和普通的Makefile略有不同,主要區別是Android.mk包含一些Android編譯系統公共的宏。
Android.mk中選項參考以下文件路徑:
build/core/config.mk
各個選項的默認值在以下文件中定義:
build/core/base_rules.mk
Android.mk文件只處理從根目錄開始找到的第一個Android.mk文件,如果需要遞歸,需要在當前目錄的Android.mk文件中做如下處理:
# Android.mk文件的最后 include $(call all-makefiles-under,$(LOCAL_PATH))
增加以上依然內容后,如果當前目錄的子目錄中還有Android.mk,也會對其做出處理。
在一個Android.mk中也可以生成多個目標:可執行程序、動態庫、靜態庫或者Android應用程序包。Android.mk文件可以處理多個內容:
include $(CLEAR_VARS) # 處理第一個內容 include $(CLEAR_VARS) # 處理第二個內容
↘ 2.2.2 各種部件的構建方式
1.可執行程序
可執行程序是Linux標準的ELF格式文件的一種,具有main入口,可以直接作為一個進程執行。在Android.mk中編譯一個可執行程序的模板如下所示:
LOCAL_PATH := $(my-dir) # Test Exe include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_SRC_FILES:= \ main.c LOCAL_MODULE:= test_exe LOCAL_C_INCLUDES := LOCAL_STATIC_LIBRARIES := LOCAL_SHARED_LIBRARIES := libc include $(BUILD_EXECUTABLE)
編譯一個可執行程序,需要在LOCAL_SRC_FILES中加入源文件路徑(相對于當前的Android.mk目錄的路徑),在LOCAL_C_INCLUDES中加入所需要包含的頭文件路徑;在LOCAL_STATIC_LIBRARIES中加入所需要連接的靜態庫(*.a)的名稱;在LOCAL_SHARED_LIBRARIES加入所需要連接的動態庫(*.so)的名稱。
LOCAL_MODULE表示模塊最終的名稱。最后使用include $(BUILD_EXECUTABLE)表示以一個可執行程序的方式進行編譯。在本例中,LOCAL_MODULE被定義為test_exe,因此最終生成可執行程序的名稱是test_exe。
一個可執行程序編譯后生成獨立的目標目錄在out/target/product/中,路徑如下所示:
<TARGET_PRODUCT>/obj/EXECUTABLES/< LOCAL_MODULE >
2.靜態庫
靜態庫,也稱之為歸檔文件,在Linux中擴展名通常為.a。在Android.mk中編譯一個靜態庫(歸檔文件)的模板如下所示:
LOCAL_PATH := $(my-dir) # Test static lib include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_SRC_FILES:= \ hello.c LOCAL_MODULE:= libtest_static LOCAL_C_INCLUDES := LOCAL_STATIC_LIBRARIES := LOCAL_SHARED_LIBRARIES := libc include $(BUILD_STATIC_LIBRARY)
編譯一個靜態庫,基本的內容和編譯可執行程序相似,區別在于使用include$(BUILD_STATIC_LIBRARY) 表示編譯靜態庫。在本例中,LOCAL_MODULE被定義為libtest_static,因此最終生成靜態庫的名稱是libtest_static.a。
一個靜態庫編譯后生成的獨立的目標目錄在out/target/product/中,路徑如下所示:
<TARGET_PRODUCT>/obj/STATIC_LIBRARIES/< LOCAL_MODULE >
3.動態庫
動態庫,也稱之為共享庫,是Linux標準的ELF格式文件的一種,在Linux中擴展名通常為.so。在Android.mk中編譯一個動態庫(共享庫)的模板如下所示:
LOCAL_PATH := $(my-dir) # Test shared lib include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_SRC_FILES:= \ hello.c LOCAL_MODULE:= libtest_shared TARGET_PRELINK_MODULE := false LOCAL_C_INCLUDES := LOCAL_STATIC_LIBRARIES := LOCAL_SHARED_LIBRARIES := libc include $(BUILD_SHARED_LIBRARY)
編譯一個動態庫,基本的內容和編譯可執行程序、靜態庫相似,區別在于使用include$(BUILD_SHARED_LIBRARY) 表示編譯動態庫。在本例中,LOCAL_MODULE被定義為libtest_shared,因此最終生成動態庫的名稱是libtest_shared.so。
一個動態庫編譯后生成的獨立的目標目錄在out/target/product/中,路徑如下所示:
<TARGET_PRODUCT>/obj/STATIC_LIBRARIES/< LOCAL_MODULE >
4.Android應用程序包
Android應用程序包是一種特殊的文件,通常以apk為擴展名。在Android.mk中編譯一個應用程序包的模板如下所示:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := TestApplication include $(BUILD_PACKAGE)
這里使用BUILD_PACKAGE宏表示編譯apk,而LOCAL_SRC_FILES使用自動查找的方法,將找到所有的Java文件進行編譯。
在源代碼環境中編譯和在SDK中編譯應用程序包略有不同,涉及的目錄主要有以下兩個。
● out/target/common/obj/APPS:通用Java字節碼目錄。
● out/target/product/<TARGET_PRODUCT>/obj/APPS:Android應用包目錄。
每個包在這兩個目錄中均具有名為{LOCAL_PACKAGE_NAME}_intermediates/的獨立目錄。
例如,對于SkeletonApp包的編譯,公共目錄的生成結構如下所示:
out/target/common/obj/APPS/SkeletonApp_intermediates/ |-- classes-full-debug.jar |-- classes.jar |-- classes-jarjar.jar |-- emma_out | `-- lib | `-- classes-jarjar.jar |-- noproguard.classes.dex |-- noproguard.classes.jar |-- noproguard.classes-with-local.dex |-- proguard_options |-- public_resources.xml `-- src [自動生成的Java文件目錄] |-- com | `-- example | `-- android | `-- skeletonapp | `-- R.java `-- R.stamp
一般都會根據資源目錄res自動生成R.java文件,在具有aidl時還會生成由它自動生成的java源代碼文件。
SkeletonApp的目標目錄out/target/product/<TARGET_PRODUCT>/obj/APPS中為SkeletonApp_intermediates。其中包含3個apk包,如下所示。
● package.apk.unsigned:第一步生成的沒有簽名的apk包。
● package.apk.unaligned:第二步生成具有簽名,但是沒有對齊的apk包。
● package.apk:第三步生成最終的apk包。
在編譯應用程序包的時候,有一個額外的宏可以控制編譯的行為,如下所示:
WITH_DEXPREOPT := true
如果WITH_DEXPREOPT被定義為true,一個應用程序包將由兩個部分組成:一個是不包含Java字節碼(classes.dex)的apk文件,一個是名稱為classes.odex的字節碼文件。兩個文件同時預置在系統中依然可以構成能運行的應用程序。但是,這種生成的結果就不能再進行動態的安裝了。
● 提示:WITH_DEXPREOPT可以在每個應用程序包的Android.mk文件中定義,也可以全局定義。Android 4.x之前的版本此宏的全局默認沒有打開,Android 4.x中則將此宏全局默認定義為true。
5.各部分內容的生成路徑和安裝
可執行程序、靜態庫(*.a)、動態庫(*.so)和Android應用程序包(*.apk)生成的編譯結果分別放在以下的目錄中。
● out/target/product/<TARGET_PRODUCT>/obj/EXECUTABLES/。
● out/target/product/<TARGET_PRODUCT>/obj/STATIC_LIBRARIES/。
● out/target/product/<TARGET_PRODUCT>/obj/SHARED_LIBRARIES/。
● out/target/product/<TARGET_PRODUCT>/obj/APPS/。
幾種模塊的目標的目錄分別為如下所示。
● 可執行程序:<LOCAL_MODULE>__intermediates。
● 靜態庫:<LOCAL_MODULE>_static_intermediates。
● 動態庫:<LOCAL_MODULE>_shared_intermediates。
● Android應用程序包:<LOCAL_PACKAGE_NAME>_intermediates。
其中<LOCAL_MODULE>和<LOCAL_PACKAGE_NAME>將決定每一個工程私有目錄的名稱。對于可執行程序和動態庫,一般在LINK子目錄中是帶有符號的庫(沒有經過符號剝離)。
編寫Android.mk文件的過程中,有以下兩個注意點。
(1)Android的編譯過程根據擴展名自動識別文件類型,統一加入LOCAL_SRC_FILES中即可。
(2)在編譯過程中LOCAL_SRC_FILES盡量不要使用../source.cpp類型的文件路徑,因為各個源代碼的編譯結果也會放置到相對路徑中,使用這種路徑的文件將會被放置在編譯目錄的上級目錄中。
在編譯過程中,可以編譯目標機的內容,也可以編譯主機的內容。以上的例子是編譯目標機的內容,可執行程序、靜態庫、動態庫和Android應用程序包使用的編譯宏分別如下所示:
include $(BUILD_EXECUTABLE) # 可執行程序 include $(BUILD_STATIC_LIBRARY) # 靜態庫 include $(BUILD_SHARED_LIBRARY) # 動態庫 include $(BUILD_PACKAGE) # Android應用程序包
編譯主機的內容時,可執行程序、靜態庫、動態庫使用宏分別如下所示:
include $(BUILD_HOST_EXECUTABLE) # 主機可執行程序 include $(BUILD_HOST_STATIC_LIBRARY) # 主機靜態庫 include $(BUILD_HOST_SHARED_LIBRARY) # 主機動態庫
幾種模塊默認的安裝路徑如下所示。
● Android應用程序包:TARGET_OUT/app。
● 動態庫:TARGET_OUT/lib。
● 可執行程序:TARGET_OUT/bin。
● 靜態庫不需要安裝,因此在目標文件系統中沒有靜態庫。
在Android.mk文件中,可以指定最后的目標安裝路徑,使用LOCAL_MODULE_PATH宏來指定模塊的安裝路徑。
增加可以安裝到不同文件系統路徑的方式如下所示:
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED)
不同的文件系統路徑使用以下的宏進行選擇。
TARGET_ROOT_OUT表示根文件系統,路徑為
out/target/product/<TARGET_PRODUCT>/root
TARGET_OUT表示system文件系統,路徑為
out/target/product/<TARGET_PRODUCT>/system
TARGET_OUT_DATA表示data文件系統,路徑為
out/target/product/<TARGET_PRODUCT>/data
如果需要進一步指定子目錄,可以使用這3個宏加上其中子目錄的方式。如果不指定LOCAL_MODULE_PATH,安裝到默認的目錄中。
↘ 2.2.3 預編譯內容的安裝
在各個部分的構建過程中,除了編譯各種內容外,有時還需要向目標文件系統復制一些文件,例如運行時配置腳本、資源文件、預制的程序和庫等,也有時需要在目標文件系統中創建子目錄。預編譯內容的安裝有兩種方法,一種是使用命令向文件系統中復制,另一種是使用Android的預編譯模板。
1.使用命令安裝
使用命令的安裝方式,實際上就是在Android.mk中調用主機命令行的程序,執行向文件系統中復制等工作。
在Android.mk中,使用命令行進行目錄創建和安裝的方法如下所示:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) copy_from := \ A.txt \ B.txt copy_to := $(addprefix $(TARGET_OUT)/txt/,$(copy_from)) $(copy_to) : PRIVATE_MODULE := txt $(copy_to) : $(TARGET_OUT)/txt/% : $(LOCAL_PATH)/% | $(ACP) $(transform-prebuilt-to-target) ALL_PREBUILT = $(copy_to) DIRS := $(addprefix $(TARGET_OUT)/, \ txt \ $(DIRS): @echo Directory: $@ @mkdir -p $@
其中執行的echo和mkdir都是標準的Shell命令,$(ACP)是Android中對cp命令的一個封裝(Android cp),執行的也是復制工作。此時,Android.mk將會進行如下的工作。
(1)在system文件系統(TARGET_OUT)中創建目錄txt。
(2)將當前路徑下的A.txt和B.txt文件復制到system/txt目錄中。
2.使用預編譯模板的安裝
Android具有一個特殊的預編譯模板:BUILD_PREBUILT,使用這個模板可以將內容復制到目標系統中,也可以自動建立子目錄。
在Android.mk中,使用預編譯模板進行目錄創建和安裝的方法如下所示:
LOCAL_PATH := $(my-dir) include $(CLEAR_VARS) LOCAL_MODULE := target.txt LOCAL_MODULE_CLASS := TEXT LOCAL_MODULE_TAGS := eng LOCAL_MODULE_PATH := $(TARGET_OUT)/text LOCAL_SRC_FILES := source.txt include $(BUILD_PREBUILT)
此時,LOCAL_SRC_FILES和LOCAL_MODULE兩個變量被賦予了不同的含義,前者表示需要復制的源,后者表示要復制的目標命令。LOCAL_MODULE_PATH依然用于指定目標的路徑。LOCAL_MODULE_CLASS表示模塊的類型,其中可以使用自定義的字符串。
此時,Android.mk將會進行如下的動作:
(1)在system文件系統(TARGET_OUT)中創建目錄txt。
(2)將當前路徑下的source.txt復制為system/txt目錄中的target.txt。
在進行處理的過程中,也會生成模塊的中間目錄,路徑為:
out/target/product/<TARGET_PRODUCT>/obj/text/target.txt_intermediates/
其中將具有target.txt文件,text來自于LOCAL_MODULE_CLASS的定義,實際上表示的是一個在obj目錄中的新目標的類型。