记编译Android源码的经历

由于之前的渣本试过一次裸ubuntu编译Android源码,一次则用虚拟机。过程中参考了官网/网络博客的教程,失败了好几次,也成功了两次。上一次买了个稍微好一点的笔记本,也裸ubuntu编译过一次7.0的源码,后面因为其他工作的需求,暂且换成window系统,再一次重新折腾编译源码。整理下之前的笔记及参考资料,写下记录供需要者参考,减少踩坑。
题外话:把之前在ubuntu编译的源码拉到windowAS阅读,虽然能够根据方法栈不断深入查看方法调用的逻辑,但是无法调试且难以修改原生应用。所以强烈建议在ubuntu上折腾,把整个过程弄得够熟悉就行了,也对整个Android系统有个大致的了解。

准备环境

1
2
3
$ sudo apt-get install git  
$ git config --global user.email "yummyl.lau@gmail"
$ git config --global user.name "yummyLau"

下载源码

1
2
3
4
$ mkdir ~/bin
$ PATH=~/bin:$PATH
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
$ chmod a+x ~/bin/repo
  • 弃用官方下载源码方法,改用清华大学开源软件镜像
    这里我选择传统的初始化方法下载源码,创建存放源码的source目录,取名自定。
1
2
3
4
$ mkdir source
$ cd source
$ touch auto_asyn_source.sh
$ chmod +x auto_asyn_source.sh

新建auto_asyn_source.sh是用来保证执行repo编译环境选择 sync命令同步代码如果失败,则可以自动重试,参考的是自己动手编译最新Android源码及SDK一文的做法,感谢该博主。
写入shell脚本自动同步内容:

1
$ vim auto_asyn_source.sh

把下面的代码复制进去并保存

1
2
3
4
5
6
7
8
PATH=~/bin:$PATH
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-6.0.1_r66
repo sync
while [$? = 1]: do
echo "=========download failed,again============"
sleep 5
repo sync
done

其中android-6.0.1_r66是我选择下载的源码分支,可替换成其他。然后添加修改Repo的url

1
2
$ cd ~/
$ vim .bashrc

把下面的链接添加进入并保存

1
REPO_URL = 'https://gerrit-google.tuna.tsinghua.edu.cn/git-repo'

最后,跑一下脚本下载,静心等待呗…

1
2
$ cd source
$ ./auto_asyn_source.sh

Tip:过程中我的虚拟机比较卡,可能会出现卡死的现象=。=,只需要Ctrl+C结束之后重新执行脚本就行了。
由于上班折腾下很久才下载完,和下载环境的网络有关,下载完成后大概有58G大小。

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
yummylau@ubuntu:~/source$ du -h --max-depth=1
53M ./libcore
11M ./dalvik
124M ./device
128K ./abi
112M ./hardware
31M ./system
16M ./build
27G ./out //编译后生成的
308M ./development
84M ./ndk
1.5G ./frameworks
30M ./docs
44G ./.repo
948K ./platform_testing
686M ./tools
2.6G ./external
278M ./developers
6.5G ./prebuilts
408M ./packages
900K ./pdk
31M ./sdk
498M ./cts
29M ./bionic
29M ./art
5.7M ./bootable
232K ./libnativehelper
83G .

选择编译目标

使用lunch选择要编译的目标。根据官网教程。该命令表示针对模拟器进行完整编译,并且所有调试功能均处于启用状态。 如果没有提供任何参数就运行命令,lunch将提示您从菜单中选择一个目标,所有编译目标都采用BUILD-BUILDTYPE形式。
这里,我输入以下指令选择编译目标。

1
$ lunch aosp_arm-eng

解释下上述名下的参数,aosp_arm-engBUILD-BUILDTYPE形式,分为BUILDBUILDTYPE两部分。

  • BUILD为特定功能的组合代号,如aosp_arm为支持32位ARM架构,也有64位ARM架构,X86也有。关于CPUARMX86架构,可以看下这篇分不清ARM和X86架构,别跟我说你懂CPU!科普文。
  • BUILDTYPE为编译类型,主要用于指定系统权限,有以下三种:
    • user,权限受限,适用于生产环境;
    • userdebug,与“user”类似,但具有 root 权限和可调试性;是进行调试时的首选编译类型;
    • eng,具有额外调试工具的开发配置。

这里我选择aosp_arm-eng作为编译目标进行编译。

开始编译

官方使用make来编译任何代码。Make可以借助 -jN 参数处理并行任务,通常使用的任务数 N 介于编译时所用计算机上硬件线程数的 1-2 倍之间。由于我用的是虚拟机,主机是司核四线程,虽然我Ubuntu虚拟机分配了2个处理器,每个处理器核数为2。一开始我用make -j4编译,可卡死我了,不小心点击了清理内存和高CPU程序,把我虚拟机直接杀了,后面直接使用make编译,大家随自己设备决定即可。
window平台下可在运行中输入wmic,在新窗口中输入cpu get即可查看物理CPU数、CPU核心数、线程数。

  • Name:表示物理CPU数
  • NumberOfCores:表示CPU核心数
  • NumberOfLogicalProcessors:表示CPU线程数

运行make使用单线程编译。

1
$ make

慢悠悠看了几集冰与火之歌,大概编译了4个多小时,就搞定了,编译后的产物有out目录中。

1
2
3
4
yummylau@ubuntu:~/source/out$ du -h --max-depth=1
8.5G ./host
18G ./target
27G .

其中各个目录的内容为:
/out/host/:该目录下包含了针对主机的 Android 开发工具的产物。即 SDK 中的各种工具,例如:emulator,adb,aapt 等。
/out/target/common/:该目录下包含了针对设备的共通的编译产物,主要是 Java 应用代码和 Java 库。
/out/target/product/<product_name>/:包含了针对特定设备的编译结果以及平台相关的 C/C++ 库和二进制文件。其中,<product_name>是具体目标设备的名称,产物如下图。

1
2
3
4
5
6
7
8
9
10
yummylau@ubuntu:~/source/out/target/product$ cd generic/
yummylau@ubuntu:~/source/out/target/product/generic$ ls
android-info.txt hardware-qemu.ini symbols
cache installed-files.txt system
cache.img obj system.img
clean_steps.mk previous_build_config.mk userdata.img
data ramdisk.img userdata-qemu.img
dex_bootjars recovery
gen root
yummylau@ubuntu:~/source/out/target/product/generic$

注意下,上述有三个核心的镜像文件:system.imgramdisk.imguserdata.img
system.img:包含了 Android OS 的系统文件,库,可执行文件以及预置的应用程序,将被挂载为根分区。
ramdisk.img:在启动时将被 Linux 内核挂载为只读分区,它包含了 /init 文件和一些配置文件。它用来挂载其他系统镜像并启动 init 进程。
userdata.img:将被挂载为 /data,包含了应用程序相关的数据以及和用户相关的数据。
关于如何理解Android的Build系统,推荐阅读IBM-理解 Android Build 系统

运行编译系统

在源码个目录重新执行

1
2
3
yummylau@ubuntu:~/source$ source build/envsetup.sh
yummylau@ubuntu:~/source$ lunch aosp_arm-eng //选择之前的编译目标,如果还在当前编译环境,则这前两句不必执行
yummylau@ubuntu:~/source$ emulator

在之前的编译流程会自动将模拟器添加到您的路径中,emulator加载的内核所在的目录为

1
2
3
yummylau@ubuntu:~/source/prebuilts/qemu-kernel/arm$ ls
2.6 kernel-qemu-armv7 README vmlinux-qemu
kernel-qemu LINUX_KERNEL_COPYING rebuild.sh vmlinux-qemu-armv7

其中,kernel-qemu为加载的内核。
同时在~/source/out/target/product/generic中生成的system.imgramdisk.imguserdata.img也被载入了。
最后,我们就静静地等待系统启动吧~