轉載聲明
原文地址 作者 發布時間 作者主頁 Android 編譯系統 (一)
Android 編譯系統 (二)haicom 2012-03-13 22:02:38 haicom的博客 注:原作內容為2012年所發布,與最新版本的aosp有些區別
主要是沒有一個完整的Android Build System
中文版,所以寫了一個也可以以后作為參考。
Makefile & Android build system
在進行講述Android編譯系統之前,應該先了解一下編譯時所使用的Makefile,或者說復習下這方面的知識,這樣才能更好的了解Android build system
的原理。
Makefile
Makefile的規則
首先介紹Makefile的規則:
target ... : prerequisites ...
command
...
target也就是一個目標文件,可以是Object File,也可以是執行文件。還可以是一個標簽(Label)。prerequisites就是要生成那個target所需要(依賴)的文件或是目標。
command也就是make需要執行的命令(任意的Shell命令)。這是一個文件的依賴關系,也就是說,target這一個或多個的目標文件依賴于prerequisites中的文件,其生成規則定義在command中。就是說,prerequisites
中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。
這就是Makefile
的規則。也就是Makefile
中最核心的內容,Android編譯系統符合GNU make
的標準,當然這也是Android
編譯系統最核心的內容。
編譯helloworld
下面用一個最簡單的例子來說明這個規則,當然就是我們最喜歡寫的helloworld,創建一個簡單的helloworld.c
,下面就是編譯helloworld
的makefile:
helloworld : helloworld.o
cc -o helloworld helloworld .o
helloworld.o : helloworld.c
cc -c main.c
clean:
rm helloworld helloworl.o
執行make就可以編譯helloworld.c
了,執行make clean
就可以清除編譯結果了(其實就是刪除helloworld
helloworl.o
)。
Android build ystem
Makefile
文件用來告訴make
命令需要怎么樣的去編譯和鏈接程序。在編譯時,需要根據編譯環境和編譯目標選擇編譯工具,編譯參數,以及選擇編譯安裝哪些模塊。同時Makefile指定了構建目標所需的依賴性以及生成規則。
在Android中,主要的Makefile文件存在于build/core/
目錄下,它的表現形式為多個后綴為*.mk
的文件組成,也稱為build system
。Android build system
主要有兩大部分構成:配置部分和目標構建部分。
Build system
的主流程文件為build/core/main.mk
文件,有它以及所需要的其它*.mk
文件共同完成一次Build
的任務。
下面以表格的形式概要描述幾個重要的*.mk
的文件的作用:
文件名 | 作用 |
---|---|
Android.mk |
module/package 的設置文件,每個module/package 的目錄下都會有一個Android.mk
|
AndroidProducts.mk |
即為Android build system 提供給廠商的接口文件,通過此文件即可定義所需編譯和安裝的packages,默認為generic
|
base_rules.mk |
對一些Makefile的變量規則化 |
BoardConfig.mk |
是為product 主板做設定,例如driver 選擇設定,選擇CPU 架構等等 |
Binary.mk |
控制如何生成目標文件 |
buildspec.mk |
位于根目錄下,可在此選擇要產生的product 、平臺、額外的module/package 等。build/buildspec.mk.default 是樣板 |
Clear_vars.mk |
清除編譯系統中用到的臨時變量 |
Copy_headers.mk |
將頭文件拷貝到指定目錄 |
config.mk |
定義了編譯目標程序所需的工具鏈及編譯參數 |
definations.mk |
定義了很多編譯系統中用到的宏,相當于函數庫 |
envsetup.mk |
初始化編譯環境,定義一些實用的shell 函數,方便編譯 |
main.mk |
實際的主控Makefile ,例如找到TOP 目錄下所有Android.mk 文件 |
Makefile |
輔助main.mk 主要控制生成 system.img ,ramdisk.img ,userdata.img 等 |
build/envsetup.sh |
提供了幾個有用的命令,如:執行. build/envsetup.sh
|
Combo/linux-arm.mk |
控制如何生成linux-arm 二進制文件,包括ARM 相關的編譯器,編譯參數等的設置 |
Android.mk
Android.mk是Android編譯系統中最重要的一個文件,下面將詳細介紹:
概述
Android.mk在編譯中起著至關重要的作用,這其實就是Android編譯環境中的makefile。Android.mk文件是為了向生成系統描述你的源代碼。更明確的說:這個文件實際上是GNU Make文件的一小片段,它會被生成系統解析一次或多次。因此,你應該在Android.mk里盡量少地聲明變量。
下面是在NDK中Android.mk網頁中引用的一段:
An Android.mk file is written to describe your sources to thebuild system. More specifically:
The file is really a tiny GNU Makefile fragment that will be parsed one or more times by the build system. As such, you should try to minimize the variables you declare there and do not assume that anything is not defined during parsing.
The file syntax is designed to allow you to group your sources into 'modules'. A module is one of the following:
a static library
a shared library
Only shared libraries will be installed/copied to your application package. Static libraries can be used to generate shared libraries though.You can define one or more modules in each Android.mk file, and you can use the same source file in several modules.
- The build system handles many details for you. For example, you don't need to list header files or explicit dependencies between generated files in your Android.mk. The NDK build system will compute these automatically for you. This also means that, when updating to newer releases of the NDK, you should be able to benefit from new toolchain/platform support without having to touch your Android.mk files.
詳細說明
首先,對這些變量的命名做一說明:
LOCAL_XXX
變量:在每個module中都要設置以LOCAL_
開頭的變量。它們會被include $(CLEAR_VARS)
命令來清除,你會在你的很多module中使用這些LOCAL_開頭的變量。
PRIVATE_XXX
變量:這些變量是make-target-specific
(編譯具體目標)的變量。
INTERNAL_XXX
變量:這些變量是編譯系統所使用的變量,所以你最好不要將你的變量用此來命名,而且最好也不要在你的makefile中見到這些變量。
HOST_XXX
變量和TARGET_XXX
變量:這些變量包含或者定義了一些特定的host
或者target
編譯。所以不要在你的makefile中設置以開頭HOST_
和TARGET_
的變量。
BUILD_XXX
變量和CLEAR_VARS
:這些變量都包含在那些清晰的makefile模板中了,比如CLEAR_VARS
和BUILD_HOST_PACKAGE
。
當然,你還可以在你的Android.mk文件中使用其它你所需要的命名變量。但是,要記住的是Android是一個非遞歸的編譯系統,所以很有可能,你的變量可能會被其它的Android.mk改變,當你的module執行命令是,這些變量可能已經不同了。
說明:為保證可參考性,也將英語原文記錄如下。
But first, a note on variable naming:
LOCAL_ These variables are set per-module. They are cleared by the include $(CLEAR_VARS) line, so you can rely on them being empty after including that file. Most of the variables you'll use in most modules are LOCAL_ variables.
PRIVATE_ These variables are make-target-specific variables. That means they're only usable within the commands for that module. It also means that they're unlikely to change behind your back from modules that are included after yours. This link to the make documentation describes more about target-specific variables. Please note that there are a couple of these laying around the tree that aren't prefixed with PRIVATE_. It is safe, and they will be fixed as they are discovered. Sorry for the confusion.
INTERNAL_ These variables are critical to functioning of the build system, so you shouldn't create variables named like this, and you probably shouldn't be messing with these variables in your makefiles.
HOST_ and TARGET_ These contain the directories and definitions that are specific to either the host or the target builds. Do not set variables that start with HOST_ or TARGET_ in your makefiles.
BUILD_ and CLEAR_VARS These contain the names of well-defined template makefiles to include. Some examples are CLEAR_VARS and BUILD_HOST_PACKAGE.
Any other name is fair-game for you to use in your Android.mk. However, remember that this is a non-recursive build system, so it is possible that your variable will be changed by another Android.mk included later, and be different when the commands for your rule / module are executed
下面對Android.mk文件中涉及到的變量做詳細說明:
LOCAL_XXX
變量
LOCAL_XXX
變量用于向編譯系統描述你的模塊中所使用的變量,你應該在include $(CLEAR_VARS)
和include $(BUILD_XXXXX)
語句之間來定義你想要使用的變量。
下面來詳細說明以LOCAL_
開頭的變量:
-
LOCAL_ASSET_FILES
LOCAL_ASSET_FILES在Android.mk文件中編譯應用程序(BUILD_PACKAGE)時設置此變量,表示引用資源文件,通常會定義成:LOCAL_ASSET_FILES += $(call find-subdir-assets)
說明:為保證可參考性,也將英語原文記錄如下(下同,不再說明)。
In Android.mk files that include $(BUILD_PACKAGE) set this to the set of files you want built into your app. Usually:
LOCAL_ASSET_FILES += $(call find-subdir-assets)
This will probably change when we switch to ant for the apps' build system.
-
LOCAL_CC
如果你想在你的module中使用不同的C編譯器,可以設置這個變量。如果LOCAL_CC是空的,它就使用默認的編譯器。If you want to use a different C compiler for this module, set LOCAL_CC to the path to the compiler. If LOCAL_CC is blank, the appropriate default compiler is used.
-
LOCAL_CXX
如果你想在你的module中使用不同的C++編譯器,可以設置這個變量。如果LOCAL_CXX是空的,它就使用默認的編譯器。If you want to use a different C++ compiler for this module, set LOCAL_CXX to the path to the compiler. If LOCAL_CXX is blank, the appropriate default compiler is used.
-
LOCAL_CFLAGS
LOCAL_CFLAGS變量為C/C++編譯器定義額外的標志,當編譯C/C++源文件時傳遞一個可選的編譯器標志,這對于指定額外的宏定義或編譯選項很有用。例如:LOCAL_CFLAGS += -DLIBUTILS_NATIVE=1
If you have additional flags to pass into the C or C++ compiler, add them here. For example:
LOCAL_CFLAGS += -DLIBUTILS_NATIVE=1
重要提示:
盡量不要改變Android.mk
中的優化/調試級別,這個可以通過在Application.mk
中設置相應的信息來自動為你處理,并且會會讓NDK生成在調試過程中使用的有用的數據文件。注意:在Android-ndk-1.5_r1
中,只使用于C源文件,而不適用于C++源文件。在匹配所有Android build system
的行為已經得到了糾正。(現在你可以為C++源文件使用LOCAL_CPPFLAGS
來指定標志)它可以用LOCAL_CFLAGS += -I<path>
來指定額外的包含路徑,然而,如果使用LOCAL_C_INCLUDES
會更好,因為用ndk-gdk
進行本地調試的時候,那些路徑依然是需要使用的。 -
LOCAL_CPPFLAGS
LOCAL_CPPFLAGS
變量和LOCAL_CFLAGS
變量類似,如果你只想要增加一些標記(flag)在你的C++編譯器中,使用LOCAL_CPPFLAGS
變量來增加它們,例如:LOCAL_CPPFLAGS += -ffriend-injection
LOCAL_CPPFLAGS必須在LOCAL_CFLAGS變量命令行的后面使用,所以你可以用它來重寫你在LOCAL_CFLAGS定義的標記。
If you have additional flags to pass into only the C++ compiler, add them here. For example:
LOCAL_CPPFLAGS += -ffriend-injection
LOCAL_CPPFLAGS is guaranteed to be after LOCAL_CFLAGS on the compile line, so you can use it to override flags listed in LOCAL_CFLAGS.
注意:
在Android NDK-1.5_r1版本中,相應的標志可以應用于C或C++源文件上。在配合完整的Android build system的時候,這已經得到了糾正(你可以使用LOCAL_CFLAGS去指定C或C++源文件)。 -
LOCAL_CPP_EXTENSION
如果你的C++文件不是以cpp為文件后綴,通過LOCAL_CPP_EXTENSION指定C++文件后綴名例如:LOCAL_CPP_EXTENSION := .cc
需要注意的是在module中給出的所有的C++文件必須具有相同的擴展名,它是不允許混合使用不同擴展名的。
If your C++ files end in something other than ".cpp", you can specify the custom extension here. For example:
LOCAL_CPP_EXTENSION := .cc
Note that all C++ files for a given module must have the same extension; it is not currently possible to mix different extensions.
注意:統一模塊中C++文件后綴必須保持一致。
-
LOCAL_C_INCLUDES
LOCAL_C_INCLUDES變量可以指定額外的目錄來指引C/C++編譯器來尋找頭文件。這些路徑必須是最頂端的目錄路徑,使用LOCAL_PATH來包含你的子目錄路徑下的文件,例如:LOCAL_C_INCLUDES += extlibs/zlib-1.2.3 LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
注意:
如果你在代碼(main.c)中引用了一些頭文件,而在編譯時如果找不到這些頭文件,就會報如下圖的錯誤:
所以要確保你所包含的路徑目錄下,有你所需要的頭文件,以避免編譯時出現錯誤。Additional directories to instruct the C/C++ compilers to look for header files in. These paths are rooted at the top of the tree. Use LOCAL_PATH if you have subdirectories of your own that you want in the include paths. For example:
LOCAL_C_INCLUDES += extlibs/zlib-1.2.3 LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
You should not add subdirectories of include to LOCAL_C_INCLUDES, instead you should reference those files in the #include statement with their subdirectories. For example:
#include <utils/KeyedVector.h> not #include <KeyedVector.h>
There are some components that are doing this wrong, and should be cleaned up.
補充1:
如果在你的頭文件目錄include下有以下*.h文件:/include/ utils/ KeyedVector.h log.h
你不應該在LOCAL_C_INCLUDES變量中包含子目錄,相反,你應該在使用#include來聲明這些文件的引用,以及它們的子目錄路徑。例如:
#include <utils/KeyedVector.h>
而不是:
#include <KeyedVector.h>
如果你想引用log.h文件,那么你應該這么寫:
#include <log.h>
補充2:
如果你在編譯JNI時,在你的JNI代碼中要引用jni.h時,既當你的代碼(helloneon.c)中寫道:#include <jni.h>
如果你沒有在該目錄下的Android.mk中定義:
# Also need the JNI headers LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
那么就會在編譯時報如下圖所示的錯誤:
如果你能預先定義該變量的值,那么就不會出現上述的錯誤。
-
LOCAL_MODULE_TAGS
可以用空白的空格來分開一些標簽,可以設置LOCAL_MODULE_TAGS
,如果這個標簽列表是空的或者包含有droid,這個module就會當作droid來編譯,否則,它只能用make <your-module>
來編譯和安裝你的module,或者使用make all
Set
LOCAL_MODULE_TAGS
to any number of whitespace-separated tags. If the tag list is empty or contains droid, the module will get installed as part of a make droid. Otherwise, it will only get installed by runningmake <your-module>
or with themake all
pseudotarget.補充:
LOCAL_MODULE_TAGS
:模塊標記,一般的取值范圍為debug
、eng
、test
、optional
,如果不定義則默認為optional
。對這幾個模式的解釋為:user
:指該模塊只在user版本下才編譯;eng
:指該模塊只在eng
版本下才編譯;tests
:指該模塊只在tests
版本下才編譯;optional
:指該模塊在所有版本下都編譯。 -
LOCAL_REQUIRED_MODULES
可以用空格來分開不同的module的名字,以用來設置LOCAL_REQUIRED_MODULES
,像libblah
或者Email
。如果安裝了這個module,那么同時也會安裝這個module所必需的模塊。確保所需要的共享庫和必須已經安裝好,當所給的程序安裝時。Set
LOCAL_REQUIRED_MODULES
to any number of whitespace-separated module names, like "libblah" or "Email". If this module is installed, all of the modules that it requires will be installed as well. This can be used to, e.g., ensure that necessary shared libraries or providers are installed when a given app is installed. -
LOCAL_FORCE_STATIC_EXECUTABLE
如果編譯的可執行程序要進行靜態鏈接(執行時不依賴于任何動態庫),則設置:LOCAL_FORCE_STATIC_EXECUTABLE:=true
目前只有libc有靜態庫形式,這個只有文件系統中/sbin目錄下的應用程序會用到,這個目錄下的應用程序在運行時通常文件系統的其它部分還沒有加載,所以必須進行靜態鏈接。
If your executable should be linked statically, set
LOCAL_FORCE_STATIC_EXECUTABLE:=true.
There is a very short list of libraries that we have in static form (currently only libc). This is really only used for executables in /sbin on the root filesystem.
-
LOCAL_JAVA_LIBRARIES
LOCAL_JAVA_LIBRARIES
編譯java應用程序和庫的時候指定包含的java類庫,目前有core和framework兩種情況下定義成:
注意:LOCAL_JAVA_LIBRARIES
不是必須的,而且編譯APK時不允許定義(系統會自動添加)When linking Java apps and libraries,
LOCAL_JAVA_LIBRARIES
specifies which sets of java classes to include. Currently there are two of these: core and framework. In most cases, it will look like this:LOCAL_JAVA_LIBRARIES := core framework
Note that setting
LOCAL_JAVA_LIBRARIES
is not necessary (and is not allowed) when building an APK withinclude $(BUILD_PACKAGE)
. The appropriate libraries will be included automatically. -
LOCAL_LDFLAGS
你可以通過設置LOCAL_LDFLAGS
來增加額外的標記傳遞給連接器。不過要記住,這些參數對ld來說是非常重要的,所以你最好在所有的平臺都測試下。You can pass additional flags to the linker by setting
LOCAL_LDFLAGS
. Keep in mind that the order of parameters is very important to ld, so test whatever you do on all platforms. -
LOCAL_LDLIBS
LOCAL_LDLIBS
允許你在你編譯你的可執行程序或者庫的時候,添加一些指定的額外的庫。用lxxx的格式來指定你要引用的庫,它們會被連接命令行直接解析。然而,要知道它不會為這些庫生成附屬。這在使用模擬器編譯而又想使用庫預編譯在主機上時是非常有用的。例如:LOCAL_LDLIBS += -lcurses -lpthread LOCAL_LDLIBS += -Wl,-z,origin
LOCAL_LDLIBS allows you to specify additional libraries that are not part of the build for your executable or library. Specify the libraries you want in -lxxx format; they're passed directly to the link line. However, keep in mind that there will be no dependency generated for these libraries. It's most useful in simulator builds where you want to use a library preinstalled on the host. The linker (ld) is a particularly fussy beast, so it's sometimes necessary to pass other flags here if you're doing something sneaky. Some examples:
LOCAL_LDLIBS += -lcurses -lpthread LOCAL_LDLIBS += -Wl,-z,origin
補充1:
LOCAL_LDLIBS
:生成你的模塊時用到的額外的連接器標記(linkerflags
)的名單,在傳遞有“-l”前綴的特殊系統庫的名稱時很有用。例如:LOCAL_LDLIBS := -labc
上面的語句會告訴連接器在load time時生成連接到/system/lib/目錄下名字叫做libabc.so的動態庫。
補充2:
如果在你的Android.mk代碼中定義了LOCAL_LDLIBS變量,例如:LOCAL_LDLIBS := -lGLESv1_CM -ldl -llog
但是,如果在編譯的過程中找不到這個庫,或者說這個庫并沒有存在與你編譯環境下,那么在編譯的時候就會出現如下圖所示的錯誤:
所以,在對
LOCAL_LDLIBS
變量賦值時,要確保其正確性以及存在性,避免出現上圖的編譯錯誤。 -
LOCAL_NO_MANIFEST
如果你的Package沒有Manifest(AndroidManifest.xml),你可以設置LOCAL_NO_MANIFEST:=true.
If your package doesn't have a manifest (AndroidManifest.xml), then set
LOCAL_NO_MANIFEST:=true.
The common resources package does this.
-
LOCAL_PACKAGE_NAME
LOCAL_PACKAGE_NAME
變量是一個App的名字,例如:Dialer、Contacts等等。它可能在我們使用ant編譯系統編譯App時會發生改變。LOCAL_PACKAGE_NAME
is the name of an app. For example, Dialer, Contacts, etc. This will probably change or go away when we switch to an ant-based build system for the apps. -
LOCAL_PATH
LOCAL_PATH := $(call my-dir):每個Android.mk文件都必須以定義LOCAL_PATH變量開始,其目的是為了定位源文件的位置。例如:LOCAL_PATH := $(my-dir)
my-dir宏函數使用的是
MAKEFILE_LIST
變量,你必須在include其它任何makefile之前來調用它。另外,考慮到當你include任何子目錄時都要重新設置LOCAL_PATH
,你必須在include它們之前設置它。The directory your Android.mk file is in. You can set it by putting the following as the first line in your Android.mk:
LOCAL_PATH := $(my-dir)
The my-dir macro uses the
MAKEFILE_LIST
variable, so you must call it before you include any other makefiles. Also, consider that any subdirectories you inlcude might resetLOCAL_PATH
, so do your own stuff before you include them. This also means that if you try to write several include lines that referenceLOCAL_PATH
, it won't work, because those included makefiles might resetLOCAL_PATH
. -
LOCAL_PREBUILT_EXECUTABLES
LOCAL_PREBUILT_EXECUTABLES
預編譯including $(BUILD_PREBUILT)
或者$(BUILD_HOST_PREBUILT)
時所用,指定需要復制的可執行文件。When including
$(BUILD_PREBUILT)
or$(BUILD_HOST_PREBUILT)
, set these to executables that you want copied. They're located automatically into the right bin directory. -
LOCAL_PREBUILT_LIBS
LOCAL_PREBUILT_LIBS
變量是在預編譯including $(BUILD_PREBUILT)
或者$(BUILD_HOST_PREBUILT)
時所用, 指定需要復制的庫.When including
$(BUILD_PREBUILT)
or$(BUILD_HOST_PREBUILT)
, set these to libraries that you want copied. They're located automatically into the right lib directory. -
LOCAL_SHARED_LIBRARIES
LOCAL_SHARED_LIBRARIES
變量用來列出模塊所需的共享庫的列表,不需要加上.so
后綴。例如:LOCAL_SHARED_LIBRARIES := / libutils / libui / libaudio / libexpat / libsgl
These are the libraries you directly link against. You don't need to pass transitively included libraries. Specify the name without the suffix:
LOCAL_SHARED_LIBRARIES := \ libutils \ libui \ libaudio \ libexpat \ libsgl
-
LOCAL_SRC_FILES
LOCAL_SRC_FILES
變量必須包含一系列將被構建和組合成模塊的C/C++源文件。
注意:不需要列出頭文件或include文件,因為生成系統會為你自動計算出源文件的依賴關系。默認的C++源文件的擴展名是.cpp,但你可以通過定義LOCAL_DEFAULT_EXTENSION來指定一個擴展名。The build system looks at LOCAL_SRC_FILES to know what source files to compile -- .cpp .c .y .l .java. For lex and yacc files, it knows how to correctly do the intermediate .h and .c/.cpp files automatically. If the files are in a subdirectory of the one containing the Android.mk, prefix them with the directory name:
LOCAL_SRC_FILES := \ file1.cpp \ dir/file2.cpp
-
LOCAL_STATIC_LIBRARIES
LOCAL_STATIC_LIBRARIES
變量和LOCAL_SHARED_LIBRARIES
類似,用來列出你的模塊中所需的靜態庫的列表,你可以在你的module中包含一些想使用的靜態庫,通常我們使用共享庫,但是有些地方,像在sbin下的可執行程序和主機上的可執行程序我們要使用靜態庫。例如:LOCAL_STATIC_LIBRARIES := / libutils / libtinyxml
These are the static libraries that you want to include in your module. Mostly, we use shared libraries, but there are a couple of places, like executables in sbin and host executables where we use static libraries instead.
LOCAL_STATIC_LIBRARIES := \ libutils \ libtinyxml
-
LOCAL_MODULE
LOCAL_MODULE
變量必須定義,用來標識在Android.mk
文件中描述的每個模塊。名稱必須是唯一的,而且不包含任何空格。如果有其它moudle中已經定義了該名稱,那么你在編譯時就會報類似這樣的錯誤:libgl2jni already defined by frameworks/base/opengl/tests/gl2_jni/jni. Stop.
下面就是該錯誤的截圖:
接下來就是修改你的module的名字了,或者找到跟你重名的module把它干掉,但不建議你那么做,因為有可能會帶來未知的錯誤(你修改了別人的module的名字,而別人不一定知道,當他再編譯或者做其它時,就會出錯)。
LOCAL_MODULE
is the name of what's supposed to be generated from yourAndroid.mk
. For exmample, for libkjs, theLOCAL_MODULE
is "libkjs" (the build system adds the appropriate suffix--
.so
.dylib
.dll
).注意:編譯系統會自動產生合適的前綴和后綴,例如:
LOCAL_MODULE := screenshot
一個被命名為“screenshot”的共享庫模塊,將會生成“libscreenshot.so”文件。
補充1:變量命名的規范性
如果LOCAL_MODULE
變量定義的值可能會被其它module調用時,就要考慮為其變量命名的規范性了。特別是在使用JNI時,既在LOCAL_JNI_SHARED_LIBRARIES
變量中定義的值,最好要和LOCAL_MODULE
變量定義的值保存一致(具體請參考LOCAL_JNI_SHARED_LIBRARIES
變量的使用說明)。
這時的LOCAL_MODULE
變量的命名最好以lib開頭,既libxxx
,例如:LOCAL_MODULE := libscreenshot
-
LOCAL_MODULE_PATH
通知編譯系統將module放到其它地方而不是它通常的類型。如果你重寫這個變量,確保你還要再設置LOCAL_UNSTRIPPED_PATH
變量的值。如果你忘了設置LOCAL_UNSTRIPPED_PATH
變量的值的話,就會報錯。Instructs the build system to put the module somewhere other than what's normal for its type. If you override this, make sure you also set
LOCAL_UNSTRIPPED_PATH
if it's an executable or a shared library so the unstripped binary has somewhere to go. An error will occur if you forget to. -
LOCAL_WHOLE_STATIC_LIBRARIES
LOCAL_WHOLE_STATIC_LIBRARIES
指定模塊所需要載入的完整靜態庫(這些靜態庫在鏈接是不允許鏈接器刪除其中無用的代碼)。通常這在你想往共享庫中增加一個靜態庫時是非常有用的,共享庫就會接受到靜態庫暴露出的content,例如:LOCAL_WHOLE_STATIC_LIBRARIES := / libsqlite3_android
These are the static libraries that you want to include in your module without allowing the linker to remove dead code from them. This is mostly useful if you want to add a static library to a shared library and have the static library's content exposed from the shared library.
LOCAL_WHOLE_STATIC_LIBRARIES := \ libsqlite3_android
-
LOCAL_REQUIRED_MODULES
LOCAL_REQUIRED_MODULES
指定模塊運行所依賴的模塊(模塊安裝時將會同步安裝它所依賴的模塊)Set
LOCAL_REQUIRED_MODULES
to any number of whitespace-separated module names, like "libblah
" or "Email
". If this module is installed, all of the modules that it requires will be installed as well. This can be used to, e.g., ensure that necessary shared libraries or providers are installed when a given app is installed. -
LOCAL_PRELINK_MODULE
LOCAL_PRELINK_MODULE
變量用來規定是否需要預連接處理(默認需要,用來做動態庫優化)。LOCAL_PRELINK_MODULE
只有在編譯.so
的時候才會有的選項,主要是通過預鏈接的方式來加快程序啟動和執行的速度,如果在你的代碼(/jni/Android.mk
)中有下面一條語句:LOCAL_PRELINK_MODULE := true
那么你要在
build/core/prelink-linux-arm.map
中定義你的庫所需要使用的空間,如果不定義或者空間不夠的話,在編譯的時候就會報錯。如下圖所示:
當在build/core/prelink-linux-arm.map
中定義了我們這里使用的libhello-jni.so
庫的空間之后,既在該文件中加入一條語句:libhello-jni.so 0x99E00000
注意:在
prelink-linux-arm.map
文件的開頭部分有明確的規定,指定的內存取值范圍分配給不同的部分使用,而我們的App的庫也給指定了一個范圍:0x90000000 - 0x9FFFFFFF Prelinked App Libraries
重新編譯,就不會再報錯了,下面的截圖中很清晰地看到已經將libhello-jni.so庫預編譯成功了:
注意:
在給我們的應用庫分配地址空間時,最好以1M為邊界,地址空間大小按照由大到小的降序進行排序。下面是對于Prelink的說明:
Prelink
利用事先鏈接代替運行時鏈接的方法來加速共享庫的加載,它不僅可以加快起動速度,還可以減少部分內存開銷。程序運行時的動態鏈接尤其是重定位(relocation
)的開銷對于大型系統來說是很大的。動態鏈接和加載的過程開銷很大,并且在大多數的系統上,函數庫并不會常常被更動,每次程序被執行時所進行的鏈接動作都是完全相同的,對于嵌入式系統來說尤其如此。因此,這一過程可以改在運行時之前就可以預先處理好,即花一些時間利用Prelink工具對動態共享庫和可執行文件進行處理,修改這些二進制文件并加入相應的重定位等信息,節約了本來在程序啟動時的比較耗時的查詢函數地址等工作,這樣可以減少程序啟動的時間,同時也減少了內存的耗用。Prelink
的這種做法當然也有代價的,每次更新動態共享庫時,相關的可執行文件都需要重新執行一遍Prelink
才能保證有效,因為新的共享庫中的符號信息、地址等很可能與原來的已經不同了,這就是為什么android framework
代碼一改動,這時候就會導致相關的應用程序重新被編譯。 -
LOCAL_EXPORT_CFLAGS
LOCAL_JNI_SHARED_LIBRARIES
變量主要是用在JNI
的編譯中,如果你要在你的Java
代碼中引用JNI
中的共享庫*.so
,此變量就是共享庫的名字。
那么你要注意的一點是:在你的Project根目錄下的Android.mk
中要定義此變量用來引用你要使用的JNI中的共享庫*.so
。例如:$(Project)/Android.mk LOCAL_JNI_SHARED_LIBRARIES := libsanangeles
而在你的jni目錄下的
Android.mk
中則要定義LOCAL_MODULE
變量的值,一定要讓這兩個變量的值相同。假如你沒有這么做,而是像這樣:$(Project)/jni/Android.mk LOCAL_MODULE := sanangeles
那么,在編譯的時候就會出現下圖的錯誤:
這說明在編譯libsanangeles.so
找不到其規則,因為在上面的代碼中定義的是sanangeles
。重新修改LOCAL_MODULE
變量的值:$(Project)/jni/Android.mk LOCAL_MODULE := libsanangeles
即可正常編譯。
-
LOCAL_EXPORT_CPPFLAGS
定義這個變量用來記錄C/C++
編譯器標志集合,并且會被添加到其他任何以LOCAL_STATIC_LIBRARIES
和LOCAL_SHARED_LIBRARIES
的模塊的LOCAL_CFLAGS
定義中。例如:這樣定義"foo
"模塊:# foo/Android.mk include $(CLEAR_VARS) LOCAL_MODULE :=foo LOCAL_SRC_FILES :=foo/foo.c LOCAL_EXPORT_CFLAGS :=-DFOO=1 include $(BUILD_STATIC_LIBRARY)
另一個模塊,叫做"
bar
",并且依賴于上面的模塊:# bar/Android.mk include $(CLEAR_VARS) LOCAL_MODULE :=bar LOCAL_SRC_FILES :=bar.c LOCAL_CFLAGS:=-DBAR=2 LOCAL_STATIC_LIBRARIES:=foo include $(BUILD_SHARED_LIBRARY)
然后,當編譯bar.c的時候,標志"
-DFOO=1 -DBAR=2
"將被傳遞到編譯器。輸出的標志被添加到模塊的LOCAL_CFLAGS
上,所以你可以很容易重寫它們。它們也有傳遞性:如果"zoo
"依賴"bar
",“bar
”依賴"foo
",那么"zoo
"也將繼承"foo
"輸出的所有標志。
最后,當編譯模塊輸出標志的時候,這些標志并不會被使用。在上面的例子中,當編譯foo/foo.c
時,-DFOO=1
將不會被傳遞給編譯器。 LOCAL_EXPORT_C_INCLUDES
類似LOCAL_EXPORT_CFLAGS
,但適用于C++
標志。
具體請參考LOCAL_EXPORT_CFLAGS
條目。-
LOCAL_EXPORT_LDLIBS
類似于LOCAL_EXPORT_CFLAGS
,但是只用于鏈接標志。注意,引入的鏈接標志將會被追加到模塊的LOCAL_LDLIBS
,這是由UNIX
連接器的工作方式決定的。
當模塊foo
是一個靜態庫的時候并且代碼依賴于系統庫時會很有用的。LOCAL_EXPORT_LDLIBS
可以用于輸出依賴,例如:# Frist build the static library libfoo.a include $(CLEAR_VARS) LOCAL_MODULE := foo LOCAL_SRC_FILES := foo/foo.c LOCAL_EXPORT_LDLIBS := -llog include $(BUILD_STATIC_LIBRARY) # Then build the shared library libbar.so include $(CLEAR_VARS) LOCAL_MODULE := bar LOCAL_SRC_FILES := bar.c LOCAL_STATIC_LIBRARIES := foo include $(BUILD_SHARED_LIBRARY)
這里,在連接器命令最后,
libbar.so
將以”-llog
”參數進行編譯來表明它依賴于系統日志庫,因為它依賴于foo
。 LOCAL_ALLOW_UNDEFINED_SYMBOLS
默認情況下,當試圖編譯一個共享庫的時候遇到任何未定義的引用都可能導致"未定義符號"(undefined symbol
)的錯誤。這在你的源代碼中捕獲bug
會很有用。
然而,但是由于某些原因,你需要禁用此檢查的話,設置變量為"true
"即可。需要注意的是,相應的共享庫在運行時可能加載失敗。-
LOCAL_ARM_MODE
LOCAL_ARM_MODE
變量主要是應用與嵌入式產品的編譯系統中,可以指定為arm
模式。例如:LOCAL_ARM_MODE := arm
注意:你需要執行編譯系統為在ARM模式下通過文件的名字增加后綴的方式編譯指定的源文件。例如:
LOCAL_SRC_FILES :=foo.c bar.c.arm
這會告訴編譯系統一直以ARM模式編譯"
bar.c
",并且通過LOCAL_ARM_MODE
的值編譯foo.c。
BUILD_XXX
變量
-
BUILD_SHARED_LIBRARY
BUILD_SHARED_LIBRARY
:指明要編譯生成動態共享庫。指向一個生成腳本,這個腳本通過LOCAL_XXX
變量收集關于組件的信息,并決定如何根據你列出來的源文件生成目標共享庫。
注意:在include
這個腳本文件之前你必須至少已經定義了LOCAL_MODULE
和LOCAL_SRC_FILES
。例如:include $(BUILD_SHARED_LIBRARY)
注意:這會生成一個名為
lib$(LOCAL_MODULE).so
的動態庫。 -
BUILD_STATIC_LIBRARY
BUILD_STATIC_LIBRARY
與BUILD_SHARED_LIBRARY
類似,但用來生成目標靜態庫。靜態庫不會被拷貝至你的project/packages
文件夾下,但可用來生成共享庫。
例如:include $(BUILD_STATIC_LIBRARY)
注意:這會生成一個靜態庫,名叫
lib$(LOCAL_MODULE).a
的靜態庫。 -
BUILD_PACKAGE
BUILD_PACKAGE
變量用于在最好編譯時生成*.apk
,例如:include $(BUILD_STATIC_LIBRARY)
注意:這會生成一個
apk
安裝包,名字就叫$(LOCAL_MODULE).apk
的安裝包。
其它變量
CLEAR_VARS
CLEAR_VARS
變量是生成系統提供的,它指向一個特殊的GNU Makefile
,它將會為你自動清除許多名為LOCAL_XXX
的變量(比如:LOCAL_MODULE
、LOCAL_SRC_FILES
、LOCAL_STATIC_LIBRARIES
等),但LOCAL_PATH
是例外,它不會被清除。
注意:這些變量的清除是必須的,因為所有的控制文件是在單一的Makefile
,執行環境中解析的,在這里所有的變量都是全局的。TARGET_PLATFORM
TARGET_PLATFORM
:當解析該Android.mk
文件時用它來指定Andoid
目標平臺的名稱。例如:android-3
與Android 1.5
相對應。
NDK提供的宏函數
下面是GNU Make的宏函數,必須通過這樣的形式調用:
$(call <function>)
-
my-dir
my-dir
:返回放置當前Android.mk
的文件夾相對于NDK
生成系統根目錄的路徑。可用來在Android.mk
的開始處定義LOCAL_PATH
的值:LOCAL_PATH := $(call my-dir)
-
all-subdir-makefiles
all-subdir-makefiles
:返回my-dir
子目錄下的所有Android.mk
。例如:
代碼的結構如下:sources/foo/Android.mk sources/foo/lib1/Android.mk sources/foo/lib2/Android.mk
如果
sources/foo/Android.mk
里有這樣一行:include $(call all-subdir-makefiles)
那么,它將會自動地包含
sources/foo/lib1/Android.mk
和sources/foo/lib2/Android.mk
。這個函數能將深層嵌套的代碼文件夾提供給生成系統。
注意:默認情況下,NDK
僅在source/*/Android.mk
里尋找文件。 this-makefile
this-makefile
:返回當前Makefile
所在目錄的路徑。parent-makefile
parent-makefile
:返回父Makefile
所在目錄makefile
的路徑。-
import-module
一個允許你通過名字找到并包含另一個模塊的的Android.mk
的功能,例如:$(call import-module,<name>)
這將會找到通過
NDK_MODULE_PATH
環境變量引用的模塊<name
>的目錄列表,并且將其自動包含到Android.mk
中。
Application.mk
作用
Application.mk
目的是描述在你的應用程序中所需要的模塊(即靜態庫或動態庫)。
Application.mk
文件通常被放置在$PROJECT/jni/Application.mk
下,$PROJECT
指的是您的項目。另一種方法是將其放在頂層的子目錄下,既$NDK/apps
目錄下,例如:
$NDK/apps/<myapp>/Application.mk
<myapp
>是一個簡稱,用于描述你的NDK
編譯系統的應用程序(這個名字不會生成共享庫或者最終的包),這個方法是Android NDK r4
以前的,現在仍然兼容。但是我們強烈建議你使用第一種方法,因為它更簡單并且不用修改NDK
安裝樹的目錄。
詳細說明
下面是Application.mk中定義的幾個變量:
APP_MODULES
APP_MODULES
變量是強制性的,并且會列出所有你所需要的模塊。它不允許用一個空格來分隔其模塊列表,這個模塊名字被定義在Android.mk
文件中的LOCAL_MODULE
中。-
APP_PROJECT_PATH
APP_PROJECT_PATH
變量也是強制性的,并且會給出應用程序工程的根目錄一個絕對路徑。這是用來復制或者安裝一個沒有任何版本限制的JNI庫,從而給 APK 生成工具一個詳細的路徑。例如:# \HelloNDK\Application.mk APP_PROJECT_PATH := $(call my-dir)/project APP_MODULES := HelloNdk
這里定義了工程路徑為
$(call my-dir)/project
,而要編譯的模塊則是HelloNdk
,這樣編譯系統才會找到我們要編譯的庫和源文件。 APP_CFLAGS
APP_CFLAGS
則是當要編譯模塊中有任何C
文件或者C++
文件的時候,C
編譯器的信號就會被發出。這里可以在你的應用中需要這些模塊時,進行編譯的調整,這樣就不許要直接更改Android.mk
為文件本身了。APP_OPTIM
這個變量是可選的,可以定義成兩個值release
或者debug
,用于修改編譯程序模塊時的優化層級。release
模式是默認的,會產生高優化的文件,debug
模式會生成不優化的文件,使得調試更容易進行。
注意:調試release
和debug
文件都是可能的,但是release
版在調試節提高的信息很少,一些變量被優化輸出,無法檢查,代碼被重排序,使得跟蹤代碼很困難,堆棧追蹤也不可靠,等等。APP_CPPFLAGS
當編譯的只有C++
源文件的時候,可以通過這個C++
編譯器來設置。
注意:在Android NDK-1.5_r1
中,這個標志可以應用于C
和C++
源文件中。并且得到了糾正,以建立完整的與系統相匹配的Android編譯系統。你先可也可以使用APP_CFLAGS
來應用于C
或者C++
源文件中。建議使用APP_CFLAGS
。
補充
兩種不同級別的應用apk
目前我所理解是在Android
開發中我們會遇到兩種不同級別的應用apk
:系統級應用apk
和普通級應用apk
。
下面分別描述兩種apk
:
編譯系統級應用apk
將應用程序的代碼放到武當源代碼目錄路徑下,然后進行編譯。將編譯生成的*.apk
通過adb
或者其它方式放到/system/app
目錄下即可。編譯普通級應用apk
應用程序的代碼并沒有放到平臺的源代碼目錄下,然后通過編譯生成的*.apk
通過adb install
的方式放到/data/app
目錄下,就是普通級的apk
。
參考資料
Android ndk r7b for linux/ Android ndk r6b for windows
-
Android NDK 概覽:
$(NDK)/doc/OVERVIEW.html
-
NDK使用方法:
$(NDK)/doc/HOWTO.html
-
Android.mk 文件:
$(NDK)/doc/ANDROID-MK.html
-
Application.mk 文件:
$(NDK)/doc/APPLICATION-MK.html
- Android Building System 總結
-
build-system.html
: Android 源碼下platform/build/core/build-system.html