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目录中的新目标的类型。