Android源码编译系列博客:
Android.bp你真的了解吗
Android.bp入门指南之Android.mk转换成Android.bp
Android.bp入门指南之浅析Android.bp语法
Android.bp正确姿势添加宏控制编译指南
Android高版本P/Q/R源码编译指南
时代在进步,第三套少儿广播体操!不好意思,搞错频道了,重来!时代在进步,Android的版本也是快速的进行着迭代着,从我们以前最常见的Android 4.4一直发展到了今天的Android 11版本(即Android K到Android R),Android版本的快速迭代对于消费者来说是一件普天同庆的大好事情,但是对于我们开发者来说各种适配各种改造有时候吃翔的心情都有了。而对于Android版本的适配和各种改造的第一步就是从编译Android源码开始,可是不幸的是随着Android版本的迭代连编译Android源码的相关流程都发生了翻天覆地的变化,正所谓工欲利其事必先利器,所以我们今天的这篇博客将带领读者一起来捯饬捯饬Android各个版本的源码编译发展和编译具体操作步骤!
这里我们需要注意一下Android各个版本的对应关系如下:
- Android 5.x (Lollipop)简称Android L版本
- Android 6.0 (MarshMallow) 简称Android M版本
- Android 7.x (Nougat)简称Android N版本
- Android 8.x (Oreo)简称Android O版本
- Android 9.0 (Pie)简称Android P版本
- Android 10.0 (Q)简称Android Q版本
- Android 11.0 (R)简称Android R版本
并且这里还有一点需要特别注意,本文演示的Android R版本是以高通平台为基准进行的。
俗话说天时地利人和缺一不可,而这其中的地利翻译过来说的就是环境因素了,人的成长离不开环境因素,而我们的Android编译也离不开编译环境的构建!虽然我们本篇博客的主题是Android源码编译指南,但是我们还是有必要抽出一个章节来简单说明下Android编译环境的构建和初始化过程,以及初始化完毕后常见的命令。
本章节重点偏向的是Android编译环境的构建,而不是编译环境构建的原理分析(如果是原理分析,那内容就多了)。
虽然Android的版本一直在迭代着,但是Android编译环境的构建步骤还是比较良心的依然没有多大的变化(注意这里的措辞,只是步骤),对于有过Android源码开发经验的读者来说是再为熟悉不过的了,通常是如下二部曲:
$ source build/envsetup.sh
$ lunch aosp-eng
虽然各位对上述的命令应该已经烂熟于心了,但是这里我还是简单说明一下:
第一行命令”source build/envsetup.sh”引入了build/envsetup.sh 脚本。该脚本的作用是初始化编译环境,并引入一些辅助的Shell函数,这其中就包括第二步使用 lunch 函数
第二行命令”lunch aosp-eng”是调用 lunch 函数,并指定参数为”aosp-eng”。lunch 函数的参数用来指定此次编译的目标设备以及编译类型。在这里,这两个值分别是”aosp”和”eng”。”aosp”是 Android 源码中已经定义好的一种产品,是为模拟器而设置的。而编译类型会影响最终系统中包含的模块。如果在调用lunch函数的时候没有指定参数,那么该函数将输出列表以供选择,列表内容不同Android版本,不同厂家的基线源码会有所不同,如下:
这里补充一点对Android的源码编译类型简单说明一下,它可以分为如下三种功能,每种类型的特点如下:
在编译环境初始化完成后,我们就可以使用各种各种编译环境提供的指令和make编译命令族来开启Android的构建之旅了,这里我简单的总结了下,我们在Android编译中可能会用到的编译环境提供的指令和make编译命令族,如下:
指令 | 说明 |
---|---|
croot | 切到Android源码树的根目录(当你深入Android源码树的子目录,想回到根目录的时候此命令就非常实用了) |
m | 相当于在源码树的根目录执行make,并且该命令不一定要在根目录下执行 |
mm | 编译当前目录路径下的所有模块(包括include进来的,但是不包括存在依赖关系模块) |
mma | 编译当前目录路径下的所有模块(包括include进来的,且包括存在依赖关系模块) |
mmm[module_path] | 编译指定目录路径下的所有模块(包括include进来的,但是不包括存在存在依赖关系模块) |
mmma[module_path] | 编译指定目录路径下的所有模块(包括include进来的,包括存在存在依赖关系模块) |
cgrep | 对C/C++文件执行grep(即grep的时候只搜寻C/C++文件类型,注意这里也包括.h文件类型) |
jgrep | 对Java文件执行grep(即grep的时候只搜寻Java文件类型) |
resgrep | 在所有res/.xml文件上执行 grep即grep的时候只搜寻res/.xml文件类型) |
printconfig | 显示当前Android编译的相关配置信息 |
add_lunch_combo | 在lunch命令的的菜单中添加一个条目 |
这里我们对上述表格中的不包括存在依赖关系模块::
1.依赖关系模块这个要怎么说呢,这里我们举个栗子!譬如模块A的编译需要依赖模块B,此时的B是一个so库。
2.假如我们通过mm或者mmm编译模块A的时候,此时B模块还没有编译那么此时就会报错
3.假如我们使用的是mma或者mmma编译模块A,假如依赖的模块B还没有编译,那么会先将模块B编译OK,然后编译模块A(当然这里只是举栗子,可能A还依赖C,D同理也会先编译)
Android的Build编译系统处理常见的make单命令之外,还提供了其它的一系列make命令族,这里我们简单过下:
指令 | 说明 |
---|---|
make update-api | 更新API文件,在framework API改动之后,需要首先执行该命令来更新API,公开的API记录在frameworks/base/api目录下 |
make | Android默认系统编译指令,会编译出整个系统的所有镜像(其实质最终执行的是make droid) |
make droid | 同上 |
make sdk | 编译出Android的SDK开发套件 |
make clean-sdk | 清理SDK的编译产物 |
make dist | 执行整个编译,并将 MAKECMDGOALS变量定义的输出文件拷贝到 /out/dist目录下, 这个命令在实际中用的比较少 |
make all | 编译所有内容,不管当前产品的定义中是否会包含,官方解释如下: builds everything make droid does,plus everything whose LOCAL_MODULE_TAGS do not include the “droid” tag. The build server runs this to make sure that everything that is in the tree and has an Android.mk builds. |
make help | 帮助信息命令,显示当前Android版本主要支持的make命令 |
make snod | 从已经编译出的包快速构建系统镜像(譬如你重新单独编译了某个模块,然后想快速进行打包到system.img,可以使用此命令加快速度) |
make clean-$(LOCAL_MODULE) | Let you selectively clean one target. For example, you can type make clean-libutils and t will delete libutils.so and all of the intermediate files. 即清理掉一个指定模块的编译结果和中间产物 |
make clean-$(LOCAL_PACKAGE_NAME) | Let you selectively clean one target. For example, you can type make clean-Home and it will clean just the Home app… 即清理掉一个指定模块的编译结果和中间产物 |
make clean | deletes all of the output and intermediate files for this configuration. This is the same as rm -rf out/ 通常删除的是整个Android源码工程的out/*目录 |
make clobber | deletes all of the output and intermediate files for all configurations. This is the same as rm -rf out/. 这个命令在实际中,应用得比较少 |
make dataclean | deletes contents of the data directory inside the current combo directory. This is especially useful on the simulator and emulator, where the persistent data remains present between builds. 这个命令在实际中应用得也比价少 |
make installclean | 当我们在执行切换编译目标时可以执行make installclean,用以清除之前编译生成的文件,但是又不会将整个out目录清空,这样可以加快编译目标的构建速度 |
make LOCAL_MODULE | 编译一个指定的模块,LOCAL_MODULE 为模块的名称,这种编译方法通常运用在整个Android工程没有构建,但是想快速编译一个模块时可以使用,可以加快单个模块构建速度 |
make targets | will print a list of all of the LOCAL_MODULE names you can make. |
make libandroid_runtime | 编译所有JNI framework内容。 |
make framework | 编译所有Javaframework内容(做Android framework开发的小伙们对这条命令应该是再熟悉不过的了)。 |
make services | 编译系统服务和相关内容 |
make bootimage | 编译生成boot.img |
make recoveryimage | 编译生成recovey.img |
make cacheimage | 编译生成cache.img |
make systemimage | 编译生成system.img |
make vendorimage | 编译生成vendor.img |
make superimage | 编译生成superi.img |
对上述的make命令有几点需要注意:
1.可能在不同的Android版本有不同表现,且有的可能已经不支持了
2.读者最好对于每个make编译命令,自行使用一番,然后慢慢品尝
有过一定Android开发经验的读者应该知道Android最初是用Android.mk配置来编译源码的(这里的Android.mk本质上有点类似Makefile文件)。但是随着Android版本的迭代,源码工程文件越来越大,包含的模块越来越多,而以Android.mk组织的项目编译花费的时间越来越多。面对这个严峻的问题,Android的妈咪谷歌终于在在Android7.0开始引入了ninja编译系统。相对于make来说ninja在大的项目管理中速度和并行方面有突出的优势,因此Google采用了ninja来取代之前使用的make。由于Android.mk的数量巨大且复杂,不可能把所有的Android.mk改写成ninja的构建规则,因此Google搞了个kati工具,用于将Androd.mk转换成ninja的构建规则文件build.ninja,再使用ninja来进行构建工作。
Android编译的发展依然没有停止进化,果不其然Android8.0开始,Google引入了Android.bp文件来替代之前的Android.mk文件,Android.bp只是纯粹的配置文件,不包括分支、循环等流程控制,本质上就是一个json配置文件。同时还引入Soong这个工具,用于将Android.bp转换为ninja的构建规则文件build.ninja,再使用ninja来进行构建工作。但之前的模块全部是用Android.mk来定义的,google不可能一下子把所有模块都修改成Android.bp,只能逐步替换。无论是Android.mk还是Android.bp最后都是转化成ninja的构建规则,再进行编译的。
如果你对上述的概述,还是觉得太麻烦了,这里我们整体来概括一下Android build系统随着Android版本相应的发展演变过程:
前面一顿咔咔,我们简单介绍了Android编译系统的范展示,其中突然一下子冒出了许多的概念,这里我们先暂且不对其中涉及的概念讲述,我们先说说Soong、Blueprint、Kati、Ninja之间的关系,如下:
上图是整体的关系图,同时在Android源码工程构建过程中的转换关系如下:
如果对上述的关系还是没有捯饬清楚的,我们再来说说,说说:
不容易啊,这里我们对涉及到Ninja, kati, Soong, bp关系搞清楚了(各种三角恋)!那么关于它们的概念,接下来我们也得简单介绍介绍,安排上才行!
Kati是专为Android开发的一个基于Golang和C++的工具,主要功能是把Android中的Android.mk文件转换成 Ninja文件。代码路径是build/kati,编译后的产物是ckati。
Kati代码是开源的,可以把它clone下来,如果感兴趣可以查看下其实现原理
这里我们构建一个通过Android.mk配置的LOCAL_MODULE模块,然后通过top命令就可以查看在编译的过程中执行了ckati的命令。
ninja是一个编译框架,会根据相应的ninja格式的配置文件进行编译,但是ninja文件一般不会手动修改,而是通过将Android.bp文件转换成ninja格文件来编译。
Android.bp的出现就是为了替换Android.mk文件。而bp跟mk文件不同,它是纯粹的配置,没有分支、循环等流程控制,不能做算数逻辑运算。如果需要控制逻辑,那么只能通过Go语言编写。Android的妈咪谷歌为了让开发者能更加的快速掌握Android.bp特意提供了androidmk命令(关于它的详细介绍可以参见博客Android.bp入门指南之Android.mk转换成Android.bp,这里就不过多的戏说了)用于Android.mk转换成Android.bp使用,如下转换命令:
$ androidmk Android.mk > Android.bp
Soong类似于之前的Makefile编译系统的核心,负责提供Android.bp语义解析,并将之转换成Ninja文件。Soong还会编译生成一个androidmk命令,用于将Android.mk文件转换为Android.bp文件,不过这个转换功能仅限于没有分支、循环等流程控制的Android.mk才有效。
Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong负责Android编译而设计的工具,而Blueprint只是解析文件格式,Soong解析内容的具体含义。Blueprint和Soong都是由Golang写的项目,从Android 7.0,prebuilts/go/目录下新增Golang所需的运行环境,在编译时使用。并且因为Soong和Blueprint是Google谷歌为Android.bp特别定制的工具,所以不需要要摘出来单独来操作。
通过前面的章节我们了解Android编译环境的基本构建和编译的发展史,那么本章节将重点分析Android O之后高阶版本的编译的不同之处。并且本文的博客前年也有说到是以高通版本的Android为基线的。所以在开始本章节的博客前,有两个知识点需要提前介绍下,一个是Android Q以及之后的动态分区,以及qssi的概念!
动态分区是Android的用户空间分区系统。使用此分区系统,您可以在无线下载(OTA)更新期间创建、销毁分区或者调整分区大小。借助动态分区,供应商无需担心各个分区(例如system、vendor和product)的大小。取而代之的是,设备分配一个super分区,其中的子分区可动态地调整大小。单个分区映像不再需要为将来的OTA预留空间。相反,super中剩余的可用空间还可用于所有动态分区(关于动态分区详见谷歌官方Android实现动态分区)。
QSSI 是 Qualcomm Single System Image 的缩写,并且高通平台从Android Q开始支持。并且其编译也和Android原生编译有差别,其差别如下:
通过前面看到QSSI特性的固件编译流程也和通用版本的有一定的区别,这里的编译分为两种模式,第一种Android的标准编译模式,另外一种就是高通提供的编译脚本。
这里需要注意的的是通用版本的Android还是可以直接通过make相关的分区进行直接编译的,譬如make superimage或者直接执行make编译
source build/envsetup.sh
- 编译 system.img
lunch qssi-userdebug
make target-files-package- 编译除system.img外的其他img
lunch xx-userdebug
make target-files-package
source build/envsetup.sh
lunch XX-userdebug
./build.sh dist -j32
source build/envsetup.sh
lunch xx-userdebug
./build.sh dist qssi_only -j32
source build/envsetup.sh
lunch xx-userdebug
./build.sh dist merge_only -j32
source build/envsetup.sh
lunch xx-userdebug
./build.sh vendorimage dist target_only -j32
非QSSI特性的编译流程,依然和以前的版本Android编译变化不大,通常是如下的步骤:
source build/envsetup.sh
lunch xx-userdebug
make
Android Q版本以及以上将system和vendor分区合并为super分区,无法通过adb reboot bootloader模式单独刷动态分区里面的img,例如system,vendor,product,odm,只能刷super.img和其他的,但是fastboot模式下可以单独刷动态分区里面的img,其方法如下:
#推荐进入fastboot模式刷机:
adb reboot fastboot
fastboot getvar is-userspace
is-userspace: yes
Finished. Total time: 0.002s
fastboot flash vendor vendor.img
fastboot flash system system.img
fastboot flash vbmeta vbmeta.img
fastboot flash vbmeta_system vbmeta_system.img
#fastbootd是用户空间的代码,因为动态的逻辑分区只能在应用空间识别
1.如果是在linux下fastboot刷机出现权限问题,需要将fastboot的所有者属性改成root
sudo chown root:root fastboot
sudo chmod +s fastboot
2.如果是在windows环境下使用fastboot,很大概率可能不识别fastboot,此时推荐下载360的手机助手借助它安装对应的驱动,这样就能进行相关的识别了,此处是个人经验
现在Android R之上的Framework的编译已经和之前有所不同,具体参见下面解释:
make -j8 framework
make -j8 services
make -j8 framework-minus-apex
make -j8 services
在前面我们简单说了下动态分区的概念,即在Android Q以及以后得编译包中,我们找不到了对应的system,vendor等img文件,但是多了一个super.img,system,vendor,product,ODM合并为super分区,这个就是动态分区了。简单来说就是为了在ota的时候能够灵活创建分区和修改分区大小,将system,vendor,odm,product合并成super分区,并在super分区上预留出一定量的free space,这样就可以动态调整这些分区的大小,解决了ota的时候分区不足,以及调整分区的风险.。
好了今天的博客Android高版本P/Q/R源码编译指南就到这里了,由于这是一篇实战类型的博客所以也没有多少总结的了,跟着干就行了。总之,青山不改绿水长流先到这里了。如果本博客对你有所帮助,麻烦关注或者点个赞,如果觉得很烂也可以踩一脚!谢谢各位了!!好了,青山不改绿水长流,各位江湖见!当然各位读者的点赞和关注是我写作路上前进的最大动力了,如果有啥不对或者不爽的也可以踩一踩也无妨!
最后附上参考文档路径AndroidP/Q/R编译系统,这里我对做了简化,并且加上了自己实际开发中的一些理解,主要是为了方便大家快速入手Android高版本编译。