Android编译命令与原理

Post on Jul 08, 2021 by Wei Lin

概述

Android编译概述

下载好Android源代码后,通过在Android源代码工程下执行make snod命令,然后得到Android的系统镜像system.img。

那么当我们修改了android源代码中某个模块或者android源代码工程中新增了一个自己的模块。此时可以用make命令进行重新编译,不过重新编译比较浪费时间。google提供了另外的命令来进行单独模块的编译,以及重新打包到system.img镜像中的命令。

Android源码编译系统

android的编译系统可以分为三部分:

  • build/core:在这个目录中包含了大量的.mk文件
  • 子项目:每个子项目都包含自己的Android.mk或Android.bp,在编译时会被包含进去,而如何编译子项目由Andorid.mk或Android.bp文件决定;
  • out/:编译结果输出到该目录下,编译的结果可以是jar包、APK、二进制文件等。


在Android系统主要就是根据.mk文件进行编译的。

他们之间的关系如下:

image-20230726000651556

Android编译命令

(1) 设置命令环境

命令:source build/envsetup.sh

用于导入命令的环境,在envsetup.sh文件中定义了编译需要用到的命令,如m、mm、mmm、mma、lunch、croot等。如果不导入则无法使用这些命令。


(2) 指定目标设备和编译类型

命令格式:lunch <product_name>-<build_variant>

如:lunch aosp_arm64-eng


(3) 编译指定模块

如:make framework -j8

Android模块名称

部分模块的编译:

模块 make命令 源码路径
all make /
init make init system/core/init
zygote make app_process frameworks/base/cmds/app_process
system_server make services frameworks/base/services
framework make framework frameworks/base
framework-res make framework-res frameworks/base/core/res
framework-jni make libandroid_runtime frameworks/base/core/jni
binder make libbinder frameworks/native/libs/binder

mmm系列命令

make、m、mm、mmm命令的区别

命令m是对make命令的简单封装,并且是用来对整个Android源代码进行编译,而命令mm和mmm都是通过make命令来对Android源码中的指定模块进行编译。

m、mm、mmm都定义在Android源码的build/envsetup.sh下。

- m:          Makes from the top of the tree.
- mm:        Builds and installs all of the modules in the current directory, and their
              dependencies.
- mmm:       Builds and installs all of the modules in the supplied directories, and their
              dependencies.
              To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma:        Same as 'mm'
- mmma:      Same as 'mmm'


(1) make命令

不带任何参数时,用于编译整个系统,编译时间比较长,除非是进行初次编译否则不建议使用此命令。

带参数时如make framework,是对单个模块的编译。它的优点是:会把该模块依赖的其他模块一起跟着编译。例如:make libmedia 就会把libmedia依赖库全部编译好。当然缺点也会很明显,那就是它会搜索整个源码来定位MediaProvider 模块所使用的Android.mk文件。并且还要判断该模块依赖的其他模块是否有修改。所以编译时间比较长。


(2) m命令

m是对make指令的简单封装,通常用于源码的第一次编译,时间较长。该命令很少使用,都是直接使用make指令。

使用m系列命令需要在android源码根目录执行source build/envsetup.sh命令设置环境,而make命令则不需要。


函数m的实现如下:

function m()  
{  
    T=$(gettop)  
    if [ "$T" ]; then  
        make -C $T $@  
    else  
        echo "Couldn't locate the top of the tree.  Try setting TOP."  
    fi  
}

函数m调用函数gettop得到的是Android源代码根目录T。在执行make命令的时候,先通过-C参数指定工作目录为T,即Android源代码根目录,接着又将执行命令m指定的参数$@作为命令make的参数。

从这里就可以看出,命令m实际上就是对命令make的简单封装。


(3) mm命令

​ mm命令编译当前目录下的模块,当前目录下要有Android.mk文件。一般需要cd 进入指定模块的目录,然后执行mm指令。

使用方法:

cd 目标模块路径
mm


(4) mmm命令

编译指定路径下的模块,指定路径下要有Android.mk文件。mm和mmm一样,只编译目标模块。mm和mmm编译的速度都很快。

使用方法:mmm 相对路径

mma与mmma

mma 编译当前路径下所有模块,且包含依赖

mmma [module_path] 编译指定路径下所有模块,且包含依赖

m系列命令使用方法与注意事项

mm与mmm命令注意事项:

  • 只能在第一次编译之后使用;
  • 只对目标模块进行编译,不对依赖模块编译;
  • 目标模块文件夹中需要包含android.mk文件。


使用上述三个命令之前,需要在android源码根目录执行source build/envsetup.sh命令设置环境,m和make的区别便在此处。

如果只知道目标模块的名称而不知道具体路径,则建议使用“make 模块名” 的方式编译目标模块。而且初次编译时也要采用这种方法。

如果不知道目标模块的名称,但知道目标模块所在的目录时,则可使用mm或者mmm 命令来编译。当然初次编译还必须使用make命令,以后编译就可以使用mmm或者mm了,这样会帮助我们节约不少时间。


一般的编译方式都会采用增量编译,即只编译发生变化的目标文件,但有时则需要重新编译所有目标文件,那么就可以使用make 命令行的-B选项。例如:mm -B 模块名,或者mm -B、mmm -B。在mm 和 mmm内部也是调用make命令的,而make的-B选项将强制编译所有的目标文件。

Android.mk

Android.mk和Android.bp区别

Android.mk是在Android使用的一种makefile文件,属于GUN makefile的一部分,Android.mk用一定的语法规范来告诉编译器需要编译哪些文件、需要引用的库,然后Android编译系统会根据Android.mk中的描述生成指定的文件。

从Android O开始,Android开始使用Android.bp代替Android.mk来管理代码的编译,Android通过soong和blueprint将Android.bp转换为ninja文件(保存在out/soong/build.ninja文件),通过ckati将Android.mk转换为ninja文件(保存在out/buil-$(target).ninja文件)。然后将这两个ninja文件include到一个文件,最后使用ninja来编译。

Android.mk文件编写

示例:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS :=optional
LOCAL_SRC_FILES :=xxx.cpp
LOCAL_SHARED_LIBRARIES:= libname
LOCAL_MODULE := targetname
include $(BUILD_EXECUTABLE)


(1) LOCAL_PATH

每个Android.mk文件必须以LOCAL_PATH开始,表示指定项目的根路径,用于在开发tree中查找源文件。

my-dir是一个宏,由Build System提供,call my-dir返回当前Android.mk文件的路径。


(2) LOCAL_MODULE_TAGS

该变量用于指定编译类型,如:

  • optional,表示该模块可以在所有版本下进行编译;
  • user,表示该模块只在user版本下进行编译;
  • 其它的还有eng(engineering)表示工程版本,tests表示测试版本。


(3) CLEAR_VARS

CLEAR_VARS变量由Build System提供,并指向一个指定的GNU Makefile,由它清理各种LOCAL_xxx变量(因为所有的编译控制文件由同一个GNU make解析和执行,这些变量都是全局的,所以清理后才能避免相互影响),比如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等,但是不清理LOCAL_PATH。


(4) LOCAL_SRC_FILES

后面接需要编译的源码文件,多个文件用空格分隔,换行使用“\”。根目录为LOCAL_PATH。


(5) LOCAL_SHARED_LIBRARIES

表示所需的动态库。其它类型的引用文件变量为:

  • LOCAL_STATIC_LIBRARIES := 所需要的静态库
  • LOCAL_SHARED_LIBRARIES := 所需的动态库
  • LOCAL_C_INCLUDES := 头文件所在的路径


(5) LOCAL_MODULE

表示此Android.mk生成的目标模块名,名字在Android所有模块中必须唯一且不包含空格,Build System会自动添加适当的前缀和后缀,比如,自己要产生动态库且命名为foo,则生成后名字会变为libfoo.so,但如果自己定义名字为libfoo,则不加前缀,为libfoo.so。


(6) #include $(target_type)

表示编译的目标类型。

# 静态库,后缀为.a
include $(BUILD_STATIC_LIBRARY)
 
# 动态库,后缀为.so
include $(BUILD_SHARED_LIBRARY)

# java可执行文件,后缀为.jar
include $(BUILD_JAVA_LIBRARY)

# C/C++可执行文件,无后缀
include $(BUILD_EXECUTABLE)