Android 源码编译及真机刷机实录

最近接到一个项目,需要接触 Android 源码,所以走了一遍源码编译和刷机的过程,从开始到成功花了差不多一周的时间,踩了很多坑,在此记录一下。

整体分为四个部分:

文章目录

    • 1、环境配置
          • 1.1 创建区分大小写的磁盘映像
          • 1.2 配置编译环境
    • 2、下载源码
          • 2.1 安装 repo 工具
          • 2.2 开始下载源码
    • 3、编译源码
          • 3.1 下载设备驱动
          • 3.2 开始编译源码
          • 整理下我编译过程中遇到的错误:
    • 4、源码刷机

1、环境配置

参考:https://source.android.com/source/initializing.html
Android 源码需要在 Linux 或 Mac OS 上编译,我需要编译的源码版本是 8.1.0,Google 官方推荐的编译环境是 Ubuntu LTS (14.04),如果是 Windows 平台搭个虚拟机就好了。

我的电脑是 MBP,系统版本是 10.14.3,处理器是 i5 的双核,磁盘大小只有 256GB,内存是 8GB,配置一般,编起来还是稍微有点吃力的。我本来是图方便同时也担心损坏本机环境,用的 docker 创建的镜像编译,无奈电脑带不动,速度慢不说还经常报 OOM,索性最后就直接使用本机环境了。

好,开始了 …

1.1 创建区分大小写的磁盘映像

在默认安装过程中,Mac OS 会在一个保留大小写但不区分大小写的文件系统中运行。Git 并不支持此类文件系统,而且此类文件系统会导致某些 Git 命令(例如 git status)的行为出现异常。

因此我们需要在现有的 Mac OS 环境中创建一个区分大小写的文件系统,有两种方式:
直接使用磁盘工具
启动磁盘工具,“文件 - 新建映像 - 空白映像” 或者 “Command + N”创建一个映像,设置存储名称、存储位置、文件系统的名称、大小和格式,点击存储即可。
Android 源码编译及真机刷机实录_第1张图片
“存储为”处设置的名称为镜像的名称,如 AOSP.dmg,“名称”处设置的名称为文件系统的名称,装载后可以到 /Volumes/Android 路径下访问,大小我设置的是 100GB(事实证明是不够的,磁盘空间足够的话最好一开始就给到 150 GB),格式为 “Mac OS 扩展(区分大小写,日志式)”。
磁盘镜像创建完之后还可以修改,在磁盘工具左侧退出镜像,然后到磁盘工具,选择“映像 - 调整大小”,然后选定需要调整的映像,输入调整后的大小即可。
Android 源码编译及真机刷机实录_第2张图片
通过命令行
当然,这些也可以通过命令行来实现,创建和调整大小的命令分别为:

hdiutil create -type SPARSE -fs 'Case-sensitive Journaled HFS+' -size 100g ~/AOSP.dmg
hdiutil resize -size 159g ~/AOSP.dmg

注意:如果系统创建的镜像是 .dmg.sparseimage 文件,相应的 AOSP.dmg 换成 AOSP.dmg.sparseimage 也是没有影响的。

装载和卸载也有相应的命令,分别为:

hdiutil attach ~/AOSP.dmg -mountpoint /Volumes/Android
hdiutil detach /Volumes/Android

有了磁盘映像后,就可以往这个目录下放源码了,如何下载在下一节说,继续配置编译环境。

1.2 配置编译环境

首先是安装 JDK,编译 8.1.0 的源码需要的 JDK 版本为 1.8.0,其他对应的版本信息为

Android 7.0 (Nougat) - Android 8.0 (O):Ubuntu - OpenJDK 8;Mac OS - jdk 8u45 或更高版本
Android 5.x (Lollipop) - Android 6.0 (Marshmallow):Ubuntu - OpenJDK 7;Mac OS - jdk-7u71-macosx-x64.dmg
Android 2.3.x (Gingerbread) - Android 4.4.x (KitKat):Ubuntu - Java JDK 6;Mac OS - Java JDK 6
Android 1.5 (Cupcake) - Android 2.2.x (Froyo):Ubuntu - Java JDK 5

我本机有三个 JDK 版本:

$ /usr/libexec/java_home -V
Matching Java Virtual Machines (3):
    10.0.2, x86_64:	"Java SE 10.0.2"	/Library/Java/JavaVirtualMachines/jdk-10.0.2.jdk/Contents/Home
    1.8.0_201, x86_64:	"Java SE 8"	/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
    1.7.0_80, x86_64:	"Java SE 7"	/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home

/Library/Java/JavaVirtualMachines/jdk-10.0.2.jdk/Contents/Home

通过 alias 命令实现动态切换:

alias jdk7="export JAVA_HOME=$JAVA7_HOME"
alias jdk8="export JAVA_HOME=$JAVA8_HOME"
alias jdk10="export JAVA_HOME=$JAVA10_HOME"

然后是安装 Xcode(一般都有吧),或者使用命令安装 Xcode 命令行工具

xcode-select --install

然后是各种依赖包,官方推荐是使用 MacPorts 工具安装各种程序包,下载安装完后需要配置环境变量,确保可以访问命令

export PATH=/opt/local/bin:$PATH

然后通过 MacPorts 安装 Make、Git 和 GPG 程序包,执行命令为

POSIXLY_CORRECT=1 sudo port install gmake libsdl git gnupg

环境变量可以配置到 .bash_profile (或其他同类型配置文件)中,这样每次打开终端都得到执行;另外,Mac OS 中可同时打开的文件描述符的默认数量上限太低,为了确保高度并行的编译流程,可以提高此上限,将下面这句命令也加到 .bash_profile 中。

ulimit -S -n 1024

官方还给出了一些优化选项,如设置 ccache,这个工具主要是适用于 C/C++ 的编译器缓存,有助于提高编译速度。而对于增量编译则不要设置,否则会减慢编译速度,详情可以查看官方文档说明。

2、下载源码

源码下载使用的是一个类似 Git 的工具,叫做 repo,是 Google 提供的一个专门用于下载管理 Android 项目仓库的工具,底层实现是 Git。

2.1 安装 repo 工具

确保主目录下有 /bin 目录,且该目录包含在路径中

mkdir ~/bin
PATH=~/bin:$PATH

下载 repo 工具,确保该工具可执行

curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

安装时如果提示 GnuPG 不可用,brew install 安装即可

brew install GnuPG

下载时如果提示 “gerrit.googlesource.com 无法链接”,是因为被墙的缘故,修改 repo 文件中的 REPO_URL 为清华镜像,再次下载即可

REPO_URL = ‘https://gerrit.googlesource.com/git-repo’
REPO_URL = ‘https://gerrit-googlesource.lug.ustc.edu.cn/git-repo’

2.2 开始下载源码

下载完后进入前面创建的那个磁盘映像目录

cd /Volumes/Android

创建一个工作目录,随便叫什么名字都行

mkdir android-8.1.0_r1
cd android-8.1.0_r1

配置 git,如果是使用 VPN 从 Google 仓库下载,最好使用 gmail 账号

git config --global user.name "Your Name"
git config --global user.email "[email protected]"

接着就是初始化 repo 客户端,使用 -b 指定分支

repo init -u https://android.googlesource.com/platform/manifest -b android-8.1.0_r1

我编译的版本 android-8.1.0_r1,前面的 8.1.0 是我们熟知的代码版本,后面的 r1 是细分分支。需要注意的是,不同的细分分支可能对应不同的设备,如果想要将源码刷入特定手机,需要找到支持对应设备的细分版本,详细的分支列表可以看这里。

初始化完了之后就可以运行命令将源码同步到本地,因为源码数据非常大因此这个过程比较长。

repo sync

当然,如果你不强求某个特定的源码版本,只是想体验一下源码编译的过程,也可以使用清华每个月提供的最新包,然后在这个包的基础上进行同步。
下载最新包,下载完成后可以校验下 checksum

wget -c https://mirrors.tuna.tsinghua.edu.cn/aosp-monthly/aosp-latest.tar

解压得到 AOSP 目录,在此目录下有一个隐藏的 .repo 目录,同步后即可以得到完整目录

tar -xf aosp-latest.tar
repo sync

附录:清华镜像、科大镜像

源码整个下载完成后大概有 50+ GB,如果磁盘紧张(比如说我)且不需要再同步代码可以删掉 .repo 目录,最后纯源码大概 33GB 左右,接下来就可以开始编译了。

3、编译源码

编译源码其实很简单,只有几条命令,执行完就可以等着了,编译时间视你开的线程数和机器性能决定,开始编译——

3.1 下载设备驱动

因为我需要将源码编译完跑在真实设备上,因此除了使用 AOSP 源码编译出可以在特定设备中运行的系统镜像,还需要在驱动程序下载对应设备的「硬件相关专有库」。
注意这里的驱动程序也有特定的版本要求,可以通过 printconfig 命令查看本地源码的配置,然后下载对应版本的硬件设备驱动。
Android 源码编译及真机刷机实录_第3张图片
比如说,对于 Pixel 2 的 OPM1.171019.011 的源码,下载如下对应的驱动
Android 源码编译及真机刷机实录_第4张图片
下载完成后,将文件解压得到两个脚本,放到源码根目录下,运行脚本,会有很长的协议,在命令模式下输入 q 即可跳到最后,输入 “I ACCEPT” 确认协议, 二进制文件将会在源码根目录下新建 /vender 文件存放二进制文件及其对应的 Makefile
Android 源码编译及真机刷机实录_第5张图片
说明下,如果在执行了一些编译操作后再下载的二进制文件,需要清理原编译操作的输出,确保新安装的二进制文件被包含,执行完下面的命令后再重新走一遍编译流程即可

make clobber
3.2 开始编译源码

进入源码目录,执行初始化脚本,这里会配置各种环境变量的信息,比如我们上面用到的 printconfig 命令就需要在这个之后执行

source build/envsetup.sh

或者是

. build/envsetup.sh

然后设置编译目标

lunch XXX

也可以只输入 lunch 命令,然后在弹出的菜单中选择需要编译的版本
Android 源码编译及真机刷机实录_第6张图片
编译目标格式为 “BUILD-BUILDTYPE” 形式,BUILD 表示特定功能组合的代号,如 aosp 加上设备代号,BUILDTYPE 有以下三种:

  • user:权限受限,适用于生产环境
  • userdebug:与 user 类似,有 root 权限和可调试性
  • eng:具有额外调试工具的开发配置

Pixel 2 的设备代号是“walleye”,所以我选择的编译目标是 25. aosp_walleye-userdebug

最后执行编译,通过 -j 指定并行任务数

make -jX

我的机器是双核四线程,所以我开了四个线程来编译,导致的结果就是,风扇呼呼响发热厉害且编的特别慢,最后大概花了十个多小时才编译完成吧。

整理下我编译过程中遇到的错误:

错误一:找不到 JDK 的 tools.jar 包

build/core/config.mk:663: error: Error: could not find jdk tools.jar at /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/…/lib/tools.jar, please check if your JDK was installed correctly.

我首先 Java -v 看了下 JDK 确实是安装了的,且包含在系统路径下,后来发现编译时使用的环境变量并不是 “JAVA_HOME” 而是 “ANDROID_JAVA_HOME”,解决方法很简单,就是在~/.bash_profile 中添加
export ANDROID_JAVA_HOME=$JAVA_HOME

错误二:找不到对应的 sdk 版本

FAILED: out/soong/build.ninja
out/soong/.bootstrap/bin/soong_build -t -l out/.module_paths/Android.bp.list -b out/soong -n out -d out/soong/build.ninja.d -o out/soong/build.ninja Android.bp
internal error: Could not find a supported mac sdk: [“10.10” “10.11” “10.12” “10.13”]
ninja: build stopped: subcommand failed.

运行命令查看本机安装的 sdk 版本

find /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs -iname "*.sdk"

发现我本机安装的版本是 10.14,确实不在配置文件的要求中,最简单粗暴的方法就是修改配置文件,在 /build/soong/cc/config/x86_darwin_host.godarwinSupportedSdkVersions 变量中添加对应的版本。

事实证明,这样子做是不行的,编译之后出现了新的问题,就是 MacOSX10.14.sdk 版本导致的

/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSUUID.h:26:49: error: nullability specifier ‘_Nullable’ cannot be applied to non-pointer type ‘uuid_t’ (aka ‘unsigned char [16]’)
(instancetype)initWithUUIDBytes:(const uuid_t _Nullable)bytes;
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSUUID.h:29:30: error: nullability specifier ‘_Nonnull’ cannot be applied to non-pointer type ‘uuid_t’ (aka ‘unsigned char [16]’)
(void)getUUIDBytes:(uuid_t _Nonnull)uuid;

(所以,这个故事告诉我们,不要随便乱改配置文件,别人没有写新的版本肯定是有原因的)
于是下载了一个低版本的 MacOSX.sdk,解压复制到 /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ 目录下
注意下载文件是 .tar.xz 格式的,需要先用 xz -d 将 XXX.tar.xz 文件解压成 XXX.tar 格式,然后用tar -xvf 解压

错误三:Mac OS 版本过高导致 bison 有什么问题(我也不太清楚)

FAILED: out/soong/.intermediates/system/tools/aidl/libaidl-common/darwin_x86_64_static/gen/yacc/system/tools/aidl/aidl_language_y.cpp
out/soong/.intermediates/system/tools/aidl/libaidl-common/darwin_x86_64_static/gen/yacc/system/tools/aidl/aidl_language_y.h
BISON_PKGDATADIR=external/bison/data prebuilts/misc/darwin-x86/bison/bison -d --defines=out/soong/.intermediates/system/tools/aidl/libaidl-common/darwin_x86_64_static/gen/yacc/system/tools/aidl/aidl_language_y.h -o out/soong/.intermediates/system/tools/aidl/libaidl-common/darwin_x86_64_static/gen/yacc/system/tools/aidl/aidl_language_y.cpp system/tools/aidl/aidl_language_y.yy
[ 2% 1270/59686] cc out/soong/.intermediates/system/core/adf/libadf/libadf/android_arm_armv7-a_static_core/obj/system/core/adf/libadf/adf.o
ninja: build stopped: subcommand failed.
11:47:11 ninja failed with: exit status 1
make: *** [run_soong_ui] Error 1

解决办法是安装 bison 的补丁并重新编译
首先进入 bison 的目录,并安装补丁

cd /external/bison
git cherry-pick c0c852bd6fe462b148475476d9124fd740eba160

重新编译

mm

这里 mm 命令如果执行失效,则需退回根目录重新执行 source build/envsetup.shlunch XXX,然后再回来执行 mm

编译成功后,替换 bison 目录,然后重新编译即可

cp /Volumes/Android/android-8.1.0_r1/out/host/darwin-x86/bin/bison /Volumes/Android/android-8.1.0_r1/prebuilts/misc/darwin-x86/bison/

错误四:内存不够了

GC overhead limit exceeded.
Try increasing heap size with java option ‘-Xmx’.

怎么操作都提示了,就是增加虚拟机的堆栈

export JACK_SERVER_VM_ARGUMENTS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4g"

修改后注意重启下服务

./prebuilts/sdk/tools/jack-admin kill-server 
./prebuilts/sdk/tools/jack-admin start-server

我最后一次编译了七万多个文件,花了五个多小时,正常情况下开始编译大概是十万多个文件。中间断了也没事,已经编好的还在,程序会继续编译。
编译完了之后,屏幕会出现这样的画面,就表示成功了(顺便吐槽一句编译完整个源码目录大概占了 128 GB 左右的磁盘空间,阔怕)

4、源码刷机

源码编译成功后,所有输出都在 out 目录,刷机的镜像文件在 /out/target/product/walleye/ 路径下,大概有这么些东西,其中用到的也就是几个镜像了

输出目录
开始刷机,刷机前检查 ANDROID_PRODUCT_OUT 环境变量的值,若为空则设置为镜像文件的路径

export ANDROID_PRODUCT_OUT=/Volumes/Android/android-8.1.0_r1/out/target/product/walleye/

然后连接手机,打开 USB 调试模式,以 bootloader 模式进入手机并解锁

adb reboot bootloader
fastboot oem unlock

刷入镜像

fastboot -w flashall

输出大概是这样子的:
Android 源码编译及真机刷机实录_第7张图片
手机重启后,就是我们自己编译的源码的系统了,非常干净简洁

Android 源码编译及真机刷机实录_第8张图片

Android 源码编译及真机刷机实录_第9张图片

你可能感兴趣的:(android源码学习)