五、Andriod编译分析之Android build框架介绍

一、引言

Android源码的巨大(repo下来,大概2、3G)给人以Android相当复杂的错觉。本文从Android编译系统的角度,让大家了解Android。
Android编译系统(build system)集中于Android源码下的build/core下,其下有n多个*.mk文件,另外还有一些shell脚本,可谓相当庞大。而main.mk是整个编译系统的主导文件。

二、Android Build介绍

Android Build 系统用来编译 Android 系统,Android SDK 以及相关文档。该系统主要由 Make 文件,Shell 脚本以及 Python 脚本组成,其中最主要的是 Make 文件。
众所周知,Android 是一个开源的操作系统。Android 的源码中包含了大量的开源项目以及许多的模块。不同产商的不同设备对于 Android 系统的定制都是不一样的。如何将这些项目和模块的编译统一管理起来,如何能够在不同的操作系统上进行编译,如何在编译时能够支持面向不同的硬件设备,不同的编译类型,且还要提供面向各个产商的定制扩展,是非常有难度的,但 Android Build 系统很好的解决了这些问题,这里面有很多值得我们开发人员学习的地方。

三、编译Android过程

1、完成编译环境的搭建以及获取到完整的 Android 源码,这一步如果有问题,可以查看Android编译环境的构建方法
2、执行以下命令编译整个工程(Android 系统的编译环境目前只支持 Ubuntu 以及 Mac OS 两种操作系统)。

  1. source build/envsetup.sh
  2. lunch full-eng(当前使用的产品型号)
  3. make -j8

解析:

第一行命令“source build/envsetup.sh”引入了 build/envsetup.sh脚本。该脚本的作用是初始化编译环境,并引入一些辅助的 Shell 函数,这其中就包括第二步使用 lunch 函数。
除此之外,该文件中还定义了其他一些常用的函数

常用函数名 说明
croot 切换到源码树的根目录
m 在源码树的根目录执行 make
mm Build 当前目录下的模块
mmm Build 指定目录下的模块
cgrep 在所有 C/C++ 文件上执行 grep
jgrep 在所有 Java 文件上执行 grep
resgrep 在所有 res/*.xml 文件上执行 grep
godir 转到包含某个文件的目录路径
printconfig 显示当前 Build 的配置信息
add_lunch_combo 在 lunch 函数的菜单中添加一个条目

第二行命令“lunch full-eng”是调用 lunch 函数,并指定参数为“full-eng”。lunch 函数的参数用来指定此次编译的目标设备以及编译类型。在这里,这两个值分别是“full”和“eng”。“full”是 Android 源码中已经定义好的一种产品,是为模拟器而设置的。而编译类型会影响最终系统中包含的模块。
lunch命令如下:

user@user-QiTianxxxx:~/at5/android$ lunch

You're building on Linux

Lunch menu... pick a combo:
     1. aosp_arm-eng
     2. aosp_x86-eng
     3. aosp_mips-eng
     4. vbox_x86-eng
     5. sf1432-eng
     6. rksdk-eng

Which would you like? [aosp_arm-eng]

第三行命令“make -j8”才真正开始执行编译。make 的参数“-j”指定了同时编译的 Job 数量,这是个整数,该值通常是编译主机 CPU 支持的并发线程总数的 1 倍或 2 倍(例如:在一个 4 核,每个核支持两个线程的 CPU 上,可以使用 make -j8 或 make -j16)。在调用 make 命令时,如果没有指定任何目标,则将使用默认的名称为“droid”目标,该目标会编译出完整的 Android 系统镜像。

Build 结果的目录结构

所有的编译产物都将位于 /out 目录下,该目录下主要有以下几个子目录:
/out/host/: 该目录下包含了针对主机的 Android 开发工具的产物。即 SDK 中的各种工具,例如:emulator,adb,aapt 等。
/out/target/common/: 该目录下包含了针对设备的共通的编译产物,主要是 Java 应用代码和 Java 库。
/out/target/product/<product_name>/: 包含了针对特定设备的编译结果以及平台相关的 C/C++ 库和二进制文件。其中,<product_name>是具体目标设备的名称。
/out/dist/: 包含了为多种分发而准备的包,通过“make disttarget”将文件拷贝到该目录,默认的编译目标不会产生该目录。

Build 生成的镜像文件

Build 的产物中最重要的是镜像文件,它们都位于 /out/target/product/<product_name>/ 目录下。具体哪几种镜像文件可以看我之前的博客三、Android系统的分区及img文件、移植烧写过程

三、 Android编译系统的架构

分析Android编译系统,你会发现,Android编译系统完成的并不仅仅是对目标(主机)系统二进制文件、java应用程序的编译、链接、打包等,而且还有包括生成各种依赖关系、确保某个模块的修改引起相依赖的文件的重新编译链接,甚至还包括目标文件系统的生成,配置文件的生成等,因此Android编译系统具有支持多架构(linux-x86、windows、arm等)、多语言(汇编、C、C++、Java等)、多目标、多编译方式。这些目标和结构决定其架构也很重要。

从大的方面来说,Build系统分为三大块:
第一块:处于build/core目录下的文件,这是Build的基础框架和核心部分
第二块:处于device目录下的文件,存放的是项目的一些配置信息
第三块:就是各大模块的Android.mk了。

编译系统入口

整个 Build 系统的入口文件是源码树根目录下名称为“Makefile”的文件,当在源代码根目录上调用 make 命令时,make 命令首先将读取该文件。

扫描二维码关注公众号,回复: 8615718 查看本文章

Makefile 文件的内容只有一行:“include build/core/main.mk”。该行代码的作用很明显:包含 build/core/main.mk 文件。在 main.mk 文件中又会包含其他的文件,其他文件中又会包含更多的文件,这样就引入了整个 Build 系统。

build/core

Android编译系统集中于build/core下,这边之罗列了几个比较重要的*.mk文件:

  • main.mk:(主控Makefile)
  • base_rules.mk:(对一些Makefile的变量规则化)
  • config.mk:(关于编译参数、编译命令的一些配置)
    例:
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_RAW_STATIC_LIBRARY := $(BUILD_SYSTEM)/raw_static_library.mk
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
BUILD_RAW_EXECUTABLE:= $(BUILD_SYSTEM)/raw_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_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_DROIDDOC:= $(BUILD_SYSTEM)/droiddoc.mk
BUILD_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk
BUILD_NATIVE_TEST := $(BUILD_SYSTEM)/native_test.mk
BUILD_HOST_NATIVE_TEST := $(BUILD_SYSTEM)/host_native_test.mk
BUILD_NOTICE_FILE := $(BUILD_SYSTEM)/notice_files.mk

  • definations.mk:(定义了很多编译系统中用到的宏,相当于函数库)

  • Makefile:(这个Makefile特指build/core下的Makefile,此文件主要控制生成system.img,ramdisk.img,userdata.img,以及recorvery image,sdk等)

  • Binary.mk:(控制如何生成目标文件)

  • Clear_vars.mk:(清除编译系统中用到的临时变量)

  • Combo/linux-arm.mk:(控制如何生成linux-arm二进制文件,包括ARM相关的编译器,编译参数等的设置)

  • Copy_headers.mk(:将头文件拷贝到指定目录)
    分散于各个目录下的Android.mk(控制生成局部模块的源码,名称所需头文件路径,依赖库等特殊选项)

  • Build/envsetup.mk:编译环境初始化,定义一些实用的shell函数,方便编译使用

    以上几个主要的文件,可以按照社会分工打一个比方:
    Main.mk是总统,是老大,承担了很多工作。
    Makefile是副总统,辅佐老大Main.mk
    Base_rules.mk是交警,让不规则的东西,变得规则。
    Config.mk是省长,规定了各个人民群众该如何行事
    Definations.mk是图书馆管理员
    Binary.mk应该属于村长了,规定每个人该如何行事
    Clear_vars.mk应该属于保洁公司的工人吧
    Combo/linux-arm.mk应该属于社会公民了,他决定自己该如何去做

我这里找了张图,能比较完整的展示整个结构
在这里插入图片描述

main.mk

如果在源码树的根目录直接调用“make”命令而不指定任何目标,则会选择默认目标:“droid”(在 main.mk 中定义,如下)。因此,这和执行“make droid”效果是一样的。

# This is the default target.  It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL):

droid 目标将编译出整个系统的镜像。从源代码到编译出系统镜像,整个编译过程非常复杂。这个过程并不是在 droid 一个目标中定义的,而是 droid 目标会依赖许多其他的目标,这些目标的互相配合导致了整个系统的编译。
下面这张图粗略的表示了编译过程和依赖的其他mk文件
在这里插入图片描述
其他更多关于main.mk的详细内容,可以参照Android编译系统——main.mk(Android 6.0),这篇文章写的很详细。

最后

整个 Build 系统包含了非常多的内容,本文只向大家介绍了大致框架,其中的细节还需大家自己根据需求,深入下去!

发布了12 篇原创文章 · 获赞 8 · 访问量 5342

猜你喜欢

转载自blog.csdn.net/weixin_38019025/article/details/103983987
今日推荐