- Android核心原理与系统级应用高效开发
- 韩超等
- 2943字
- 2020-08-28 23:36:13
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目录中的新目标的类型。