Android編譯系統(Android.mk文件詳解-僅供參考)

轉載聲明

原文地址 作者 發布時間 作者主頁
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 systemAndroid 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:

  1. 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.

  2. 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.

  1. 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_VARSBUILD_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_開頭的變量:

  1. 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.

  2. 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.

  3. 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.

  4. 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進行本地調試的時候,那些路徑依然是需要使用的。

  5. 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++源文件)。

  6. 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++文件后綴必須保持一致。

  7. 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)
    

    那么就會在編譯時報如下圖所示的錯誤:



    如果你能預先定義該變量的值,那么就不會出現上述的錯誤。

  8. 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 running make <your-module>or with the make all pseudotarget.

    補充:
    LOCAL_MODULE_TAGS:模塊標記,一般的取值范圍為debugengtestoptional,如果不定義則默認為optional。對這幾個模式的解釋為:user:指該模塊只在user版本下才編譯;eng:指該模塊只在eng版本下才編譯;tests:指該模塊只在tests版本下才編譯;optional:指該模塊在所有版本下都編譯。

  9. 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.

  10. 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.

  11. 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 with include $(BUILD_PACKAGE). The appropriate libraries will be included automatically.

  12. 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.

  13. 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變量賦值時,要確保其正確性以及存在性,避免出現上圖的編譯錯誤。

  14. 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.

  15. 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.

  16. 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 reset LOCAL_PATH, so do your own stuff before you include them. This also means that if you try to write several include lines that reference LOCAL_PATH, it won't work, because those included makefiles might reset LOCAL_PATH.

  17. 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.

  18. 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.

  19. 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
    
  20. 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
    
  21. 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
    
  22. 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 your Android.mk. For exmample, for libkjs, the LOCAL_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
    
  23. 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.

  24. 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
    
  25. 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.

  26. 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代碼一改動,這時候就會導致相關的應用程序重新被編譯。

  27. 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
    

    即可正常編譯。

  28. LOCAL_EXPORT_CPPFLAGS
    定義這個變量用來記錄C/C++編譯器標志集合,并且會被添加到其他任何以LOCAL_STATIC_LIBRARIESLOCAL_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將不會被傳遞給編譯器。

  29. LOCAL_EXPORT_C_INCLUDES
    類似LOCAL_EXPORT_CFLAGS,但適用于C++標志。
    具體請參考LOCAL_EXPORT_CFLAGS條目。

  30. 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

  31. LOCAL_ALLOW_UNDEFINED_SYMBOLS
    默認情況下,當試圖編譯一個共享庫的時候遇到任何未定義的引用都可能導致"未定義符號"(undefined symbol)的錯誤。這在你的源代碼中捕獲bug會很有用。
    然而,但是由于某些原因,你需要禁用此檢查的話,設置變量為"true"即可。需要注意的是,相應的共享庫在運行時可能加載失敗。

  32. 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變量

  1. BUILD_SHARED_LIBRARY
    BUILD_SHARED_LIBRARY:指明要編譯生成動態共享庫。指向一個生成腳本,這個腳本通過LOCAL_XXX變量收集關于組件的信息,并決定如何根據你列出來的源文件生成目標共享庫。
    注意:在include這個腳本文件之前你必須至少已經定義了LOCAL_MODULELOCAL_SRC_FILES。例如:

    include $(BUILD_SHARED_LIBRARY)
    

    注意:這會生成一個名為lib$(LOCAL_MODULE).so的動態庫。

  2. BUILD_STATIC_LIBRARY
    BUILD_STATIC_LIBRARYBUILD_SHARED_LIBRARY類似,但用來生成目標靜態庫。靜態庫不會被拷貝至你的project/packages文件夾下,但可用來生成共享庫。
    例如:

    include $(BUILD_STATIC_LIBRARY)
    

    注意:這會生成一個靜態庫,名叫lib$(LOCAL_MODULE).a的靜態庫。

  3. BUILD_PACKAGE
    BUILD_PACKAGE變量用于在最好編譯時生成*.apk,例如:

    include $(BUILD_STATIC_LIBRARY)
    

    注意:這會生成一個apk安裝包,名字就叫$(LOCAL_MODULE).apk的安裝包。

其它變量

  1. CLEAR_VARS
    CLEAR_VARS變量是生成系統提供的,它指向一個特殊的GNU Makefile,它將會為你自動清除許多名為LOCAL_XXX的變量(比如:LOCAL_MODULELOCAL_SRC_FILESLOCAL_STATIC_LIBRARIES等),但LOCAL_PATH是例外,它不會被清除。
    注意:這些變量的清除是必須的,因為所有的控制文件是在單一的Makefile,執行環境中解析的,在這里所有的變量都是全局的。

  2. TARGET_PLATFORM
    TARGET_PLATFORM:當解析該Android.mk文件時用它來指定Andoid目標平臺的名稱。例如:android-3Android 1.5相對應。

NDK提供的宏函數

下面是GNU Make的宏函數,必須通過這樣的形式調用:

$(call <function>)
  1. my-dir
    my-dir:返回放置當前Android.mk的文件夾相對于NDK生成系統根目錄的路徑。可用來在Android.mk的開始處定義LOCAL_PATH的值:

    LOCAL_PATH := $(call my-dir)
    
  2. 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.mksources/foo/lib2/Android.mk。這個函數能將深層嵌套的代碼文件夾提供給生成系統。
    注意:默認情況下,NDK僅在source/*/Android.mk里尋找文件。

  3. this-makefile
    this-makefile:返回當前Makefile所在目錄的路徑。

  4. parent-makefile
    parent-makefile:返回父Makefile所在目錄makefile的路徑。

  5. 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中定義的幾個變量:

  1. APP_MODULES
    APP_MODULES 變量是強制性的,并且會列出所有你所需要的模塊。它不允許用一個空格來分隔其模塊列表,這個模塊名字被定義在Android.mk文件中的LOCAL_MODULE中。

  2. 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,這樣編譯系統才會找到我們要編譯的庫和源文件。

  3. APP_CFLAGS
    APP_CFLAGS則是當要編譯模塊中有任何C文件或者C++文件的時候,C編譯器的信號就會被發出。這里可以在你的應用中需要這些模塊時,進行編譯的調整,這樣就不許要直接更改Android.mk為文件本身了。

  4. APP_OPTIM
    這個變量是可選的,可以定義成兩個值release或者debug,用于修改編譯程序模塊時的優化層級。release模式是默認的,會產生高優化的文件,debug模式會生成不優化的文件,使得調試更容易進行。
    注意:調試releasedebug文件都是可能的,但是release版在調試節提高的信息很少,一些變量被優化輸出,無法檢查,代碼被重排序,使得跟蹤代碼很困難,堆棧追蹤也不可靠,等等。

  5. APP_CPPFLAGS
    當編譯的只有C++源文件的時候,可以通過這個C++編譯器來設置。
    注意:在Android NDK-1.5_r1中,這個標志可以應用于CC++源文件中。并且得到了糾正,以建立完整的與系統相匹配的Android編譯系統。你先可也可以使用APP_CFLAGS來應用于C或者C++源文件中。建議使用APP_CFLAGS

補充

兩種不同級別的應用apk

目前我所理解是在Android開發中我們會遇到兩種不同級別的應用apk:系統級應用apk和普通級應用apk
下面分別描述兩種apk

  1. 編譯系統級應用apk
    將應用程序的代碼放到武當源代碼目錄路徑下,然后進行編譯。將編譯生成的*.apk通過adb或者其它方式放到/system/app目錄下即可。

  2. 編譯普通級應用apk
    應用程序的代碼并沒有放到平臺的源代碼目錄下,然后通過編譯生成的*.apk通過adb install的方式放到/data/app目錄下,就是普通級的apk

參考資料

  1. Android ndk r7b for linux/ Android ndk r6b for windows
  2. Android NDK 概覽: $(NDK)/doc/OVERVIEW.html
  3. NDK使用方法: $(NDK)/doc/HOWTO.html
  4. Android.mk 文件: $(NDK)/doc/ANDROID-MK.html
  5. Application.mk 文件: $(NDK)/doc/APPLICATION-MK.html
  6. Android Building System 總結
  7. build-system.html: Android 源碼下platform/build/core/build-system.html
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容