Android系统开发--7.添加新模块

在 Android 编译系统中,编译依赖是以模块作为单位的,每个模块都有一个唯一的名称,一个模块的依赖对象只能是另外一个模块,而不能是其他类型的对象。一个模块的所有文件通常都位于同一个文件夹中。为了将当前模块添加到整个编译系统中,每个模块都需要一个专门的 Makefile 文件,该文件的名称为 Android.mk。 编译系统会扫描名称为 Android.mk 的文件,并根据该文件中内容编译出相应的目标。下面,我们就来讲解 Android.mk 文件的编写规则。

1. Android.mk 的基本元素

Android.mk 文件通常以以下两行代码作为开头:

1
2
LOCAL_PATH := $(call my-dir) 
include $(CLEAR_VARS)

这两行代码的作用是:

  • 设置当前模块的编译路径为当前文件夹路径。
  • 清理(可能由其他模块设置过的)编译环境中用到的变量。具体来说是重置除了 LOCAL_PATH 之外的所有以 LOCAL_ 开头的编译环境变量。

Android 源码是一个非常庞大的系统,包含了成千上万个模块,各模块之间的依赖关系也很复杂。为了方便模块的编译, Android 开发团队在 Makefile 的基础上,开发了一套编译系统,在这套编译系统下,我们要定义一个模块变得非常简单,只需要定义一系列的编译环境变量就行了,根本不用关心具体的编译细节。下面举例解析一下部分重要的编译环境变量:

  • LOCAL_MODULE:当前模块的名称,这个名称在系统中应当是唯一的,模块间的依赖关系就是通过这个名称来引用的。编译系统会自动添加适当的后缀。例如,libfoo,要产生动态库,则生成libfoo.so。
  • LOCAL_SRC_FILES:当前模块包含的所有源代码文件。
  • LOCAL_C_INCLUDES:C 或 C++ 语言需要的头文件的路径。
  • LOCAL_STATIC_LIBRARIES:当前模块依赖的静态库。
  • LOCAL_SHARED_LIBRARIES:当前模块依赖的动态库。
  • LOCAL_CFLAGS:提供给 C/C++ 编译器的额外编译参数。
  • LOCAL_JAVA_LIBRARIES:当前模块依赖的 Java 共享库。
  • LOCAL_STATIC_JAVA_LIBRARIES:当前模块依赖的 Java 静态库。
  • LOCAL_CERTIFICATE: APK 签名类型。
  • LOCAL_MODULE_TAGS:当前模块所包含的标签,一个模块可以包含多个标签。标签的值可能是 eng, debug, user, optional。其中,optional 是默认标签。标签是提供给编译类型使用的。

不同的编译类型会安装包含不同标签的模块,关于编译类型的说明如下:

  • eng
    • 安装包含 eng, debug, user 标签的模块
    • 安装所有没有标签的非 APK 模块
    • 安装所有产品定义文件中指定的 APK 模块
  • userdebug
    • 安装所有带有 debug, user 标签的模块
    • 安装所有没有标签的非 APK 模块
    • 安装所有产品定义文件中指定的 APK 模块
  • user
    • 安装所有带有 user 标签的模块
    • 安装所有没有标签的非 APK 模块
    • 安装所有产品定义文件中指定的 APK 模块

除此以外,Build 系统中还定义了一些便捷的函数以便在 Android.mk 中使用,包括:

  • $(call all-java-files-under, ):获取指定目录下的所有 Java 文件。
  • $(call all-c-files-under, ):获取指定目录下的所有 C 语言文件。
  • $(call all-Iaidl-files-under, ) :获取指定目录下的所有 AIDL 文件。
  • $(call all-makefiles-under, ):获取指定目录下的所有 Make 文件。

定义完 LOCAL_XXX 变量之后, 最后一步是 include $(BUILD_XXX)
其中 BUILD_XXX 的可选项在 build/make/core/config.mk 文件中有定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk
BUILD_HEADER_LIBRARY:= $(BUILD_SYSTEM)/header_library.mk
BUILD_AUX_STATIC_LIBRARY:= $(BUILD_SYSTEM)/aux_static_library.mk
BUILD_AUX_EXECUTABLE:= $(BUILD_SYSTEM)/aux_executable.mk
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
BUILD_HOST_EXECUTABLE:= $(BUILD_SYSTEM)/host_executable.mk
BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
BUILD_PHONY_PACKAGE:= $(BUILD_SYSTEM)/phony_package.mk
BUILD_RRO_PACKAGE:= $(BUILD_SYSTEM)/build_rro_package.mk
BUILD_HOST_PREBUILT:= $(BUILD_SYSTEM)/host_prebuilt.mk
BUILD_PREBUILT:= $(BUILD_SYSTEM)/prebuilt.mk
BUILD_MULTI_PREBUILT:= $(BUILD_SYSTEM)/multi_prebuilt.mk
BUILD_JAVA_LIBRARY:= $(BUILD_SYSTEM)/java_library.mk
BUILD_STATIC_JAVA_LIBRARY:= $(BUILD_SYSTEM)/static_java_library.mk
BUILD_HOST_JAVA_LIBRARY:= $(BUILD_SYSTEM)/host_java_library.mk
...

可见 BUILD_XXX 变量都是一个 mk 文件路径,每一个 BUILD_XXX 定义了一种编译规则。
当我们 include $(BUILD_XXX) ,编译系统就根据前面所定义的 LOCAL_XXX 变量,来定义模块的目标, 依赖关系,及编译命令,编译参数等。
需要注意的是,这个时候只是定义了模块的目标而已,并没有开始编译。

2. 各种类型的模块定义模板

一个模块的基本格式如下:

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)  
include $(CLEAR_VARS)
................
LOCAL_MODULE := hello
LOCAL_SRC_FILES := hello.cpp
LOCAL_xxx := xxx
................
include $(BUILD_xxx)

2.1. 静态库

1
2
3
4
5
6
7
8
9
10
LOCAL_PATH := $(call my-dir)    
include $(CLEAR_VARS)
LOCAL_MODULE = libhellos
LOCAL_CFLAGS = $(L_CFLAGS)
LOCAL_SRC_FILES = hellos.c
LOCAL_C_INCLUDES = $(INCLUDES)
LOCAL_SHARED_LIBRARIES := libcutils
LOCAL_COPY_HEADERS_TO := libhellos
LOCAL_COPY_HEADERS := hellos.h
include $(BUILD_STATIC_LIBRARY)

2.2 动态库

1
2
3
4
5
6
7
8
9
10
LOCAL_PATH := $(call my-dir)    
include $(CLEAR_VARS)
LOCAL_MODULE = libhellod
LOCAL_CFLAGS = $(L_CFLAGS)
LOCAL_SRC_FILES = hellod.c
LOCAL_C_INCLUDES = $(INCLUDES)
LOCAL_SHARED_LIBRARIES := libcutils
LOCAL_COPY_HEADERS_TO := libhellod
LOCAL_COPY_HEADERS := hellod.h
include $(BUILD_SHARED_LIBRARY)

2.3 板子上的可执行文件

1
2
3
4
5
6
7
8
9
10
11
12
#使用动态库    
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hellod
LOCAL_MODULE_TAGS := debug
LOCAL_SRC_FILES := maind.c
LOCAL_C_INCLUDES := $(INCLUDES)
LOCAL_STATIC_LIBRARIES := libhellos
LOCAL_SHARED_LIBRARIES := libc libcutils
LOCAL_LDLIBS += -ldl
LOCAL_CFLAGS := $(L_CFLAGS)
include $(BUILD_EXECUTABLE)

2.4 预编译文件

1
2
3
4
5
6
7
8
9
LOCAL_PATH := $(call my-dir)  
include $(CLEAR_VARS)
#the data or lib you want to copy
LOCAL_MODULE := libxxx.so
LOCAL_SRC_FILES := lib/$(LOCAL_MODULE)
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
LOCAL_MODULE_PATH := $(ANDROID_OUT_SHARED_LIBRARIES)
OVERRIDE_BUILD_MODULE_PATH := $(TARGET_OUT_INTERMEDIATE_LIBRARIES)
include $(BUILD_PREBUILT)

2.5 java 静态库

1
2
3
4
5
6
LOCAL_PATH := $(call my-dir) 
include $(CLEAR_VARS)
LOCAL_MODULE := sample
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := android.test.runner
include $(BUILD_STATIC_JAVA_LIBRARY)

2.6 APK

1
2
3
4
5
6
LOCAL_PATH := $(call my-dir) 
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := LocalPackage
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_STATIC_JAVA_LIBRARIES := static-library
include $(BUILD_PACKAGE)

3. 添加一个 hello 模块

前面讲这么多理论知识,这里我们来实践一下,在源码里面添加一个 hello 模块,模块类型为可执行文件,并把这个模块打包到系统里面。

3.1 给模块一个容身之处

1
mkdir -p device/qiushao/common/hello

3.2 编写代码实现

在 hello 目录下新建一个 hello.c 文件:

1
2
3
4
5
#include<stdio.h>
int main() {
printf("hello android\n");
return 0;
}

3.3 编写 Android.mk

在 hello 目录下新建一个 Android.mk 文件:

1
2
3
4
5
6
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_MODULE_TAGS := eng
LOCAL_SRC_FILES := hello.c
include $(BUILD_EXECUTABLE)

3.4 添加到 product

由于我们设置的 LOCAL_MODULE_TAGS := eng , 且编译类型为 eng,根据第一小节中的解析,这个模块是会自动编译打包到系统中的。
当然如果不确定的话,我们也可以修改 device/qiushao/generic_pure/generic_pure.mk, 加上:

1
PRODUCT_PACKAGES += hello

3.5 编译验证

具体编译,运行虚拟机步骤参考前面章节 Android系统开发–3.Android源码编译
启动完后, adb shell

1
2
3
generic_pure:/ # hello 
hello android
generic_pure:/ #