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阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容