Android编译系统介绍

article/2025/10/11 15:21:47

1. 编译系统变化

Android 最初是用 Android.mk 来定义模块的, Android.mk 本质上就是 Makefile。随着 Android 工程越来越大,包含的模块越来越多,以 Makefile 组织的项目编译花费的时间越来越多。google 在 Android 7.0 开始引入了 ninja 编译系统。相对于 make 来说 ninja 在大的项目管理中速度和并行方面有突出的优势,因此 google 采用了 ninja 来取代之前使用的 make。关于 ninja 的介绍,请参考 ninja 构建系统 和 ninja 官方说明文档。由于 Android.mk 的数量巨大且复杂,不可能把所有的 Android.mk 改写成 ninja 的构建规则,因此 google 搞了个 kati 工具,用于将 Androd.mk 转换成 ninja 的构建规则文件 build.ninja,再使用 ninja 来进行构建工作。

Android 8.0 开始,google 引入了 Android.bp 文件来替代之前的 Android.mk 文件,Android.bp 只是纯粹的配置文件,不包括分支、循环等流程控制, 本质上就是一个 json 配置文件。同时还引入 Soong 这个工具,用于将 Android.bp 转换为 ninja 的构建规则文件 build.ninja,再使用 ninja 来进行构建工作。但之前的模块全部是用 Android.mk 来定义的,google 不可能一下子把所有模块都修改成 Android.bp,只能逐步替换,目前 Android 10 上还有少部分 Android.mk 的模块,按这进度,估计要到 Android 12 才能完全替换成 Android.bp 了。无论是 Android.mk 还是 Android.bp 最后都是转化成 ninja 的构建规则,再进行编译的。 各种文件跟工具的关系如下:

2. 编译参数配置

Android编译系统的配置,可以分为三个层级,从下到上依次是:

2.1 平台级(Board)参数配置

平台级配置主要在 BoardConfig.mk 文件中参数主要有以下几个类别的配置:

  • CPU 体系结构: TARGET_ARCH, TARGET_CPU_ABI, TARGET_ARCH_VARIANT等。配置 CPU 是 x86 架构还是 arm 架构, arm 架构下又分好几个版本。编译系统会根据这个配置去加载对应 CPU 构架的 mk:build/core/combo/arch/$(TARGET_ARCH)/$(TARGET_ARCH_VARIANT).mk
  • 内核参数配置: BOARD_KERNEL_BASE, BOARD_KERNEL_PAGESIZE, BOARD_KERNEL_CMDLINE 等, 这些参数最终会被打包到 boot 分区的镜像文件中(boot.img),作为内核的启动参数。
  • 分区镜像: TARGET_USERIMAGES_USE_EXT4, BOARD_BOOTIMAGE_PARTITION_SIZE, BOARD_SYSTEMIMAGE_PARTITION_SIZE 等与分区格式、分区大小相关的参数。system, vendor 等分区的大小就是在这里面配置的。
  • 外设参数配置: 蓝牙, wifi 等外接设备的参数配置。

2.2 产品级(Product)的参数配置

同一块板子可以做不同的产品,各产品之间的差异主要是软件方面的差异,比如内置的应用不一样, 默认语言不一样等。AndroidProducts.mk 文件中的 PRODUCT_MAKEFILES 列出了所有产品的配置文件。
每一个产品都会有一个对应的 $(PRODUCT_NAME).mk 文件, 这个产品的所有配置都在里面列出,主要的配置项目如下:

  • PRODUCT_NAME : 产品名, 我们通过 lunch 选择的产品, 就要与这个配置保持一致, 同时产品的 mk 文件也要命名为 $(PRODUCT_NAME).mk。
  • PRODUCT_DEVICE : 板子的名称,编译系统会使用该名称查找 BoardConfig.mk。产品的输出目录也是根据该名称创建 out/target/product/$PRODUCT_DEVICE。
  • PRODUCT_BRAND : 对软件进行自定义所针对的品牌(如果有),例如运营商。
  • PRODUCT_MANUFACTURER : 制造商的名称, 会赋值给 ro.product.manufacturer 属性。
  • PRODUCT_MODEL : 最终产品的最终用户可见名称, 或者叫机型名。
  • PRODUCT_LOCALES : 以空格分隔的列表,用于列出由双字母语言代码和双字母国家/地区代码组成的代码对,以便说明针对用户的一些设置,例如界面语言和时间、日期以及货币格式。PRODUCT_LOCALES 中列出的第一个语言区域会被用作产品的默认语言区域。
  • PRODUCT_PACKAGES : 列出要安装的 APK 和模块。
  • PRODUCT_COPY_FILES : 预置文件列表,例如 source_path:destination_path。在编译相应产品时,应将源路径下的文件复制到目标路径。
  • PRODUCT_PROPERTY_OVERRIDES : 系统属性列表(采用“key=value”格式)列表。
  • PRODUCT_PACKAGE_OVERLAYS : 资源 overlay 目录。

以上是一个产品的常见配置, 基本上每一个产品都会用到这些配置, 作为一个系统 rom 开发者, 我们要非常熟悉这些配置的作用。
对于产品的配置还有一个重要的配置:编译类型(TARGET_BUILD_TYPE), 这个配置是我们在 lunch 选择要编译的产品的时候选择的。
我们在添加产品的时候有以下配置,把产品添加到 lunch 的选择列表中,其中的形式就是 $(PRODUCT_NAME)-$(TARGET_BUILD_TYPE)

1
2
3
4
COMMON_LUNCH_CHOICES := \pure-eng \pure-userdebug \pure-user

编译类型的取值范围为:eng, user, userdebug。这三者的区别如下:

    1. eng 工程师版本, 主要用于开发调试阶段:
      • 安装带有 eng 或 debug 标记的模块。
      • 除了带有标记的模块之外,还会根据产品定义文件安装相应模块。
      • ro.secure=0
      • ro.debuggable=1
      • ro.kernel.android.checkjni=1
      • adb 默认处于启用状态。
      • WITH_DEXPREOPT 可以设置为 false, 即在编译时不对系统映像上安装的 DEX 代码调用 dex2oat。 具体作用下面单独讲。
    1. user 用户发布版本:
      • 安装带有 user 标记的模块。
      • 除了带有标记的模块之外,还会根据产品定义文件安装相应模块。
      • ro.secure=1
      • ro.debuggable=0
      • adb 默认处于停用状态。
      • WITH_DEXPREOPT 只能为 true, 不能设置为 false。
    1. userdebug 用户调试版本, 除了以下几点之外,其余均与 user 相同:
      • 还会安装带有 debug 标记的模块。
      • ro.debuggable=1
      • adb 默认处于启用状态。

WITH_DEXPREOPT 意思为预优化, 也就是把 Android 在启动或 APP 在运行时所需要做的一些事情,把这些事情转移到编译 APK 时完成,来达到更快的 Android 系统启动速度和更快的APP运行速度。Android 在首次启动和首次安装应用时,需要将字节码翻译成机器码,这样 Android 系统的启动速度将会大大减慢,如果没有预优化,APP 的运行速度也会加上翻译所需要的时间。所以,这个翻译的工作需要转移到编译上面来,也就是所,在编译 APK 文件时,将会预先对 APK 进行翻译的优化,然后再打包到系统里面去,这样 Android 系统在首次启动时,就不再需要花费大量的时间去翻译 APK 的字节码。

WITH_DEXPREOPT 虽然能够提高系统的首次开机速度和APP的首次运行速度, 但是会大大增加镜像的大小,从经验上来看,会增加1.5倍左右。而且 jar 经过预优化之后, 就没法简单的直接替换生效了。我们在开发过程经常需要修改 framework.jar, services.jar 等, 如果系统不是 eng 版本,替换之后是不生效的, 需要重新编译升级。所以为了提高开发验证速度, 我们需要替换 jar 包的话,就要使用 eng 版本的系统。

2.3 模块级(Module)参数配置

目前模块配置有两种形式,Android.mk 和 Android.bp。这两种模块定义系统是独立的,也就是 Android.mk 中定义的模块,不能被 Android.bp 中的模块依赖。 我们分别讨论一下这两种模块配置形式。

2.3.1 Android.mk

一个模块最基本的四个要素为:模块名, 源文件列表, 依赖关系, 模块类型。对于 Android.mk 模块来说,这几个基本要素的配置方法如下:

1
2
3
4
5
6
7
LOCAL_PATH := $(call my-dir)  
include $(CLEAR_VARS)  
LOCAL_MODULE    := hello          # 模块名
LOCAL_SRC_FILES := hello.cpp    # 源文件列表
LOCAL_SHARED_LIBRARIES := liblog  # 依赖关系
LOCAL_xxx       := xxx  
include $(BUILD_EXECUTABLE)       # 模块类型

一个模块通常以以下两行配置作为开头:

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_DEX_PREOPT: 禁止对当前模块进行预优化。
  • LOCAL_JACK_ENABLED: 禁止使用Jack编译工具链编译该模块。
  • LOCAL_MODULE_TAGS:当前模块所包含的标签,一个模块可以包含多个标签。标签的值可能是 user,eng, debug, optional。其中,optional 是默认标签。标签是提供给编译类型使用的。具体的规则见上面的 2.2 小节(产品级(Product)的参数配置 )中对编译类型的解析。 在 Android 10 中取消了 eng, debug 这两种类型,具体说明见路径 Android/build/make/Changes.md 中的说明。

除此以外,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.3.3 Android.bp

对于 Android.bp 模块来说,四个基本要素的配置方法如下:

1
2
3
4
5
6
7
cc_binary {              // 模块类型name: "hello",       // 模块名srcs: ["hello.cpp"], // 源文件列表shared_libs: [       // 依赖关系"liblog",],
}

Android.bp 支持的模块类型基本上跟 Android.mk 是一样的。它们的对应关系如下(Android/build/soong/androidmk/cmd/androidmk/android.go):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var moduleTypes = map[string]string{"BUILD_SHARED_LIBRARY":        "cc_library_shared","BUILD_STATIC_LIBRARY":        "cc_library_static","BUILD_HOST_SHARED_LIBRARY":   "cc_library_host_shared","BUILD_HOST_STATIC_LIBRARY":   "cc_library_host_static","BUILD_HEADER_LIBRARY":        "cc_library_headers","BUILD_EXECUTABLE":            "cc_binary","BUILD_HOST_EXECUTABLE":       "cc_binary_host","BUILD_NATIVE_TEST":           "cc_test","BUILD_HOST_NATIVE_TEST":      "cc_test_host","BUILD_NATIVE_BENCHMARK":      "cc_benchmark","BUILD_HOST_NATIVE_BENCHMARK": "cc_benchmark_host","BUILD_JAVA_LIBRARY":             "java_library_installable", // will be rewritten to java_library by bpfix"BUILD_STATIC_JAVA_LIBRARY":      "java_library","BUILD_HOST_JAVA_LIBRARY":        "java_library_host","BUILD_HOST_DALVIK_JAVA_LIBRARY": "java_library_host_dalvik","BUILD_PACKAGE":                  "android_app","BUILD_CTS_EXECUTABLE":          "cc_binary",               // will be further massaged by bpfix depending on the output path"BUILD_CTS_SUPPORT_PACKAGE":     "cts_support_package",     // will be rewritten to android_test by bpfix"BUILD_CTS_PACKAGE":             "cts_package",             // will be rewritten to android_test by bpfix"BUILD_CTS_TARGET_JAVA_LIBRARY": "cts_target_java_library", // will be rewritten to java_library by bpfix"BUILD_CTS_HOST_JAVA_LIBRARY":   "cts_host_java_library",   // will be rewritten to java_library_host by bpfix
}var prebuiltTypes = map[string]string{"SHARED_LIBRARIES": "cc_prebuilt_library_shared","STATIC_LIBRARIES": "cc_prebuilt_library_static","EXECUTABLES":      "cc_prebuilt_binary","JAVA_LIBRARIES":   "java_import","ETC":              "prebuilt_etc",
}

每一种模块类型都有很多配置选项,这里就不一一列举了,请参考 soong官方文档

需要注意的是目前 Android.bp 还不支持预置APK模块。所以我们还是需要 Android.mk 来定义预置 APK。


http://chatgpt.dhexx.cn/article/nexuVan0.shtml

相关文章

Android 编译系统

主要是没有一个完整的Android Build System 中文版,所以写了一个也可以以后作为参考。 1.Makefile & Android build system 在进行讲述Android编译系统之前,应该先了解一下编译时所使用的Makefile,或者说复习下这方面的知识,这…

自己编译安卓系统实践

一、需要的环境和信息来源 版本号与驱动匹配表:https://developers.google.com/android/drivers#hammerheadmra58k android细分版本号:https://source.android.com/setup/start/build-numbers?hlzh_cn 编译环境需求:https://source.androi…

Android 源码编译详解【合集篇】

Android 源码编译详解【一】:服务器硬件配置及机型推荐 做 Android系统开发多年,开发环境都是入职就搭建好了,入职时拿个账号密码就直接开始搞开发了,年初换了新公司,所有的项目都是刚起步,一切环境都要重新…

java枚举类型字段与mysql中数据类型映射关系

java枚举类型字段与mysql中数据类型映射关系 domain对象数据库字段swagger测试 domain对象 public class Business {/*** 商家类型*/private Integer type;/*** 状态*/private Integer status;}数据库字段 枚举值设置 swagger测试 type值设置为1,status值也设置…

java枚举类型的构造和get\set方法

可以看出枚举时和类平级的,是定义类级别的关键字 因为枚举都是直接用.xxx的形式,所以里面的值等都是静态的!!! 但是其也有get和set方法,构造方法等 常量 枚举(enum)类型是Java 5新…

Java枚举类型与泛型

一、枚举类型 1、使用枚举类型设置常量 以往设置常量,通常将常量放置在接口中,这样在程序中就可以直接使用,并且该常量不能被修改,因为在接口中定义常量时,该常量的修饰符为final与static。常规定义常量的代码如下所示…

【Java系列】深入解析枚举类型

序言 即便平凡的日子仿佛毫无波澜,但在某个特定的时刻,执着的努力便会显现出它的价值和意义。 希望这篇文章能让你不仅有一定的收获,而且可以愉快的学习,如果有什么建议,都可以留言和我交流 问题 思考一下这寄个问题&a…

Java枚举类型(enum)详解

文章目录 理解枚举类型枚举的定义枚举实现原理枚举的常见方法Enum抽象类常见方法编译器生成的Values方法与ValueOf方法 枚举与Class对象枚举的进阶用法向enum类添加方法与自定义构造函数关于覆盖enum类方法enum类中定义抽象方法enum类与接口 枚举与switch枚举与单例模式EnumMap…

java枚举数字_Java枚举类型的使用,数值的二进制表示

一.Java枚举类型的使用 首先请看这段代码: packagejava上课;public classEnumTest {public static voidmain(String[] args) {Size s=Size.SMALL; Size t=Size.LARGE;//s和t引用同一个对象? System.out.println(s==t);//是原始数据类型吗? System.out.println(s.getClass().…

java枚举类型赋值_java枚举类型(转载)

public class TestEnum { /*最普通的枚举*/ public enum ColorSelect { red, green, yellow, blue; } /* 枚举也可以象一般的类一样添加方法和属性,你可以为它添加静态和非静态的属性或方法,这一切都象你在一般的类中做的那样. */ public enum Season { // 枚举列表必须写在最前…

java 枚举类 int_【转】掌握java枚举类型(enum type)

原文网址:http://iaiai.iteye.com/blog/1843553 1 背景 在java语言中还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组具有int常量。之前我们通常利用public final static 方法定义的代码如下,分别用1 表示春天,2表示夏天,3表示秋天,4表示冬天。 Java代码 p…

java枚举类型转换_java枚举类型enum值与整数的转换

java编程中偶尔会用到枚举,为了直观,我们通常将枚举值设置为形象的单词,方便理解和使用。枚举类型相当于数据库 中字典表,但是枚举只有字典表的值,缺少其他用来表示和值一一对应的字段,当我们在数据库中保存…

Java中的枚举类型

文章目录 前言一、枚举类简介二、枚举底层原理总结 前言 这里复习一下Java中的枚举类型。实际上,枚举类型是特殊的类,和C语言C中的枚举不太一样,下面我们做详细说明。关于枚举类型有一个单独的设计模式:即单例设计模式。单例类是…

Java枚举类型

目录 一、前言: 二、枚举类型: 三、底层原理 四、应用 应用一:定义常量 底层原理详解 应用二:添加新方法 应用三:与switch结合使用 应用四:实现接口 应用五:使用接口组织枚举 一、前言…

深入理解Java枚举类型(enum)

【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/71333103 出自【zejian的博客】 关联文章: 深入理解Java类型信息(Class对象)与反射…

Python中对基本文件操作

Python中对基本文件操作 一、前言二、创建和打开文件1.打开一个不存在的文件时先创建该文件2.以二进制形式打开文件3.打开文件时指定编码方式 三、关闭文件四、打开文件时使用with语句五、写入文件内容六、读取文件1.读取指定字符2.读取一行3.读取全部行 一、前言 在Python中&a…

C语言——文件操作

文章目录 1. 为什么使用文件2. 什么是文件2.1 程序文件2.2 数据文件2.3 文件名 3. 文件的打开和关闭3.1 文件指针3.2 文件的打开和关闭 4. 文件的顺序读写4.1 fgetc与fputc4.2 fgets与fputs4.3 fscant与fprintf4.3.1 fprintf4.3.2 fscanf 4.4 fread与fwrite4.4.1 fwrite4.4.2 f…

C语言之文件操作

目录 为什么使用文件 文件名 文件指针 流 文件的打开和关闭 前言 文件的打开方式 文件打开关闭函数 fopen函数 fclose函数 文件的顺序读写 fputc函数 fgetc函数 fputs函数 fgets函数 fprintf函数 fscanf函数 fwrite函数 fread函数 文件的随机读写 fseek函…

文件操作(图解)

文件操作 1、文件是什么?1.1 程序文件1.2 数据文件1.3 文件名 2、文件的打开和关闭2.1 文件指针2.2 文件的打开和关闭 3、文件的顺序读写4、文件的随机读写4.1 fseek4.2 ftell4.3 rewind 5、文本文件和二进制文件6、文件读取结束的判定6.1 被错误使用的feof 7、文件…

C++中的文件操作

目录 文件操作C中的文件操作时基于面向对象的C中文件类型分为两种:操作文件的三大类(这些类都属于标准模板库):1 文本文件1.1 写文件写文件步骤如下:文件打开方式: 1.2 读文件读取文件数据的四种方式按照喜好记前三个中…