1. 环境准备:搭建你的“Android源码厨房”
想自己动手编译一个Android模拟器的系统固件,听起来是不是挺酷的?这就像你想做一道顶级大餐,首先得有个功能齐全的厨房。编译Android源码这个“大餐”,对“厨房”的要求可不低,咱们得先把灶台、锅碗瓢盆都准备好。
我刚开始折腾的时候,第一件事就是检查我的电脑配置。Android源码的编译是个资源消耗大户,尤其是内存和硬盘。我强烈建议你的机器至少要有16GB的内存,如果能有32GB或以上,那编译过程会顺畅很多,不会动不动就卡死。硬盘空间更是关键,光是下载源码就需要至少150GB的可用空间,而编译过程中产生的中间文件和最终的镜像文件,还会再占用几十到上百GB。所以,一块500GB以上的固态硬盘(SSD)是理想选择,机械硬盘的编译速度会让你等到怀疑人生。处理器嘛,核心越多越好,现在主流的8核、16线程的CPU能大大缩短编译时间。
操作系统方面,官方推荐的是Ubuntu LTS版本,比如20.04或22.04。我实测下来,在Ubuntu上遇到奇怪问题的概率最小,社区支持也最完善。如果你用Windows,可以通过WSL2(Windows Subsystem for Linux)来搭建Ubuntu环境,这也是一个可行的路子,但性能会有一些损耗,而且某些涉及硬件的操作可能会受限。至于macOS,理论上也可以,但需要处理更多依赖库的兼容性问题,对新手不太友好。
接下来就是安装一大堆的依赖包。这些依赖包就像是做菜需要的各种调料,缺一不可。你可以一次性用下面这条命令来安装(针对Ubuntu 22.04):
sudo apt-get update
sudo apt-get install -y git-core gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig python3 openjdk-11-jdk
注意看,这里指定了openjdk-11-jdk。Android源码的编译对Java版本有严格要求,不同版本的Android源码需要的Java版本可能不同。比如Android 12(S)及更早版本通常需要Java 8,而Android 13(T)及之后版本则需要Java 11或17。你一定要根据你打算编译的Android版本,去官方文档确认所需的Java版本,装错了会导致编译失败。我就在这上面栽过跟头,编译到一半报错,排查了半天才发现是Java版本不对。
最后,我们还需要安装一个至关重要的工具:repo。它不是“仓库”的意思,而是Google专门为管理Android这种超大型项目(由数百个Git仓库组成)而开发的脚本工具。你可以把它想象成一个超级项目经理,能帮你一次性同步所有需要的代码仓库。安装命令很简单:
mkdir ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
然后,你需要把~/bin这个目录添加到系统的PATH环境变量里,这样在任何位置都能直接运行repo命令了。通常是在你的~/.bashrc或~/.zshrc文件末尾加上一行:export PATH=~/bin:$PATH,然后执行source ~/.bashrc让它生效。
做完这些,你的“厨房”就算初步搭建好了。接下来,我们就可以去“采购”最核心的食材——Android源代码了。
1.1 搞定网络与镜像源:绕过下载的“老大难”
下载Android源码是新手遇到的第一个,也可能是最大的一个拦路虎。因为源码仓库默认托管在Google的服务器上,在国内直接访问速度慢且不稳定,经常断线,导致repo sync命令失败。
别担心,我们完全可以使用国内的镜像源,速度飞快。国内高校和机构提供了非常棒的AOSP(Android Open Source Project)镜像,最常用的是清华大学和北京外国语大学的镜像站。这里我以清华大学镜像站为例,手把手教你配置。
首先,我们需要配置repo工具本身使用的镜像源。编辑或新建一个环境变量配置文件:
export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'
同样,把这行代码加到你的~/.bashrc文件里并source一下。这样,repo这个工具脚本就会从国内镜像下载更新了。
接下来是重头戏:在初始化源码仓库时,指定使用国内镜像。假设我们要下载Android 13(Tiramisu)的源码,对应的分支可能是android-13.0.0_rXX。命令如下:
repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-13.0.0_rXX
看到没?我们把Google的地址 https://android.googlesource.com/platform/manifest 换成了清华大学的镜像地址。-b 后面跟的就是你想要的具体分支标签。你可以在Android官方源码标记网站或镜像站的页面里找到所有可用的分支名。
初始化完成后,就是漫长的同步代码过程:
repo sync -c -j8
这里的 -c 参数表示只同步当前分支的代码,可以节省一些时间和空间。-j8 表示用8个线程并行下载,你可以根据你的网络和CPU情况调整这个数字,比如-j16。这个过程视网速而定,可能需要几个小时。如果中途断线了,别慌,直接重新执行同样的repo sync命令,它会自动续传,非常智能。
我个人的经验是,最好在晚上睡觉前开始同步,第二天早上起来就差不多了。同步完成后,你的目录下会出现一个巨大的aosp(或者你命名的)文件夹,里面就是完整的Android系统源代码了,足足有几十个GB。走到这一步,你已经成功了一大半!
2. 源码编译:启动你的“构建流水线”
代码下载完毕,我们正式进入最核心的编译环节。这个过程就像把采购来的生鲜食材,通过一道道工序加工成美味的菜肴。Android的编译系统(主要是Soong和Bazel)非常强大,但也需要我们给出正确的指令。
首先,我们需要“激活”编译环境。进入你的源码根目录,执行:
source build/envsetup.sh
这条命令会向当前终端会话注入一大堆有用的函数和变量,比如后面要用到的lunch、m、mm等。你可以把它理解为启动了整个编译工厂的总电源和控制面板。
接下来是关键一步:选择编译目标。我们是要编译给模拟器用的系统镜像,所以对应的“产品”(product)名称是固定的格式:sdk_phone_<架构>。模拟器支持的架构主要有:
x86: 32位Intel/AMD CPUx86_64: 64位Intel/AMD CPU(目前最常用)arm: 32位ARM CPU(已较少使用)arm64: 64位ARM CPU(用于测试ARM原生应用)
对于大多数在PC上做开发的场景,选择x86_64是最合适的,因为可以利用CPU的硬件虚拟化加速(Intel HAXM或AMD SVM),运行速度极快。选择目标的命令是:
lunch sdk_phone_x86_64
执行后,终端会输出一系列环境变量,显示你当前选择的目标设备、编译类型(通常是eng工程师版本)等信息。确认无误后,就可以开始“烧火做饭”了——执行编译命令:
make -j$(nproc)
这里 $(nproc) 会自动获取你CPU的核心数,并用这个数字来启动并行编译任务,最大化利用硬件性能。比如你是16核的CPU,就相当于make -j16。编译是整个过程中最耗时的一步,第一次编译可能需要2到6个小时不等,取决于你的机器性能。期间CPU风扇会狂转,这是正常的。
2.1 解读编译输出与常见问题
编译开始后,终端会疯狂刷屏。别被这些信息吓到,我们只需要关注几个关键点。编译系统会显示一个进度百分比,比如 [ 15% 5210/34521],告诉你总体进度。只要这个数字在稳步增长,没有红色的error字样,就基本没问题。
我遇到过几个典型问题,这里分享给你:
- 内存不足(Out of memory):这是最常见的错误。编译到后期,链接器等组件会消耗巨量内存。如果报错,尝试减少并行任务数,比如用
make -j8甚至make -j4。最根本的解决办法还是增加物理内存或创建足够大的交换分区(swap space)。 - Java版本错误:编译中途报错,提示某些Java类或方法找不到。请务必回头检查你的Java版本是否符合源码要求。使用
java -version确认。 - 依赖缺失:虽然我们一开始安装了很多包,但偶尔还是会漏掉一两个。仔细阅读错误信息,它通常会告诉你缺少哪个
.so库或哪个工具,用apt-get install把它补上就行。 - 磁盘空间不足:编译过程中
out目录会急剧膨胀。务必保证有充足的剩余空间(建议100GB以上)。
当编译最终成功时,你会看到令人振奋的提示:
#### build completed successfully (02:15:38 (hh:mm:ss)) ####
恭喜!系统固件已经新鲜出炉了。所有的镜像文件都生成在 out/target/product/emulator_x86_64/ 目录下。对我们最重要的几个文件是:
system.img: 核心系统镜像。vendor.img: 厂商相关镜像。ramdisk.img: 内存磁盘镜像,用于初始化启动。userdata.img: 用户数据分区镜像。kernel-ranchu: 模拟器专用的内核文件。
现在,你已经拥有了一个完全由自己编译出来的Android系统。你可以直接用源码目录下的emulator命令来启动它试试看:
emulator -verbose -show-kernel
如果一切正常,你应该能看到模拟器窗口弹出,并启动你刚刚编译的系统。这种成就感,是直接用现成镜像无法比拟的。
3. 打包与部署:让你的镜像“流通”起来
自己编译的镜像,除了自己用,可能还想分享给团队的同事测试,或者集成到持续集成(CI)流程中。这就需要我们把编译产出物打包成一个标准格式,方便分发和使用。
Android SDK工具链为我们提供了专门的打包命令。在编译成功的源码根目录下,执行:
make emu_img_zip
这个命令会启动一个相对较快的打包流程(因为它不需要重新编译,只是打包已有文件)。打包完成后,你会在 out/target/product/emulator_x86_64/ 目录下找到一个名字类似 sdk-repo-linux-system-images-eng.[你的用户名].zip 的压缩包。这个包就是标准的AVD系统映像包。
解压这个ZIP包,你会发现里面有一个以架构命名的文件夹(如x86_64),文件夹里包含了所有模拟器运行所需的镜像文件(system.img, vendor.img, kernel-ranchu等),以及一个重要的 package.xml 文件。这个XML文件描述了这个系统镜像的元数据,比如API等级、版本号、ABI(应用二进制接口)等。
3.1 集成到Android Studio的两种实战方法
有了这个ZIP包,怎么才能在大家熟悉的Android Studio里用起来呢?我常用两种方法,一种“正规军”打法,一种“快速部署”打法。
方法一:作为本地SDK组件安装(正规军) 这是最规范的方式,模拟器会把它当作一个标准的系统镜像来管理。
- 打开Android Studio,进入 Tools > SDK Manager。
- 在 SDK Platforms 选项卡中,点击右下角的 Show Package Details。
- 你会看到列表最下方有一个 “Add” 按钮(或者在某些版本是 “Install” 按钮旁的齿轮图标,选择 “SDK Update Sites”)。
- 我们需要添加一个本地更新站点。实际上,更直接的方法是:找到你的Android SDK安装目录(通常在
~/Android/Sdk或C:\Users\你的用户名\AppData\Local\Android\Sdk),进入system-images目录。 - 按照官方镜像的目录结构,新建路径,例如
android-33(对应你的API级别)/my_custom(变体名)/x86_64/,把你ZIP包解压出来的x86_64文件夹里的所有内容,拷贝到这个新建的目录下。 - 关键一步:从SDK里一个已有的同架构镜像目录(比如
android-33/default/x86_64/)复制一份package.xml文件过来,然后用文本编辑器打开,修改其中的<version>标签和<display-name>标签,使其与你自定义的镜像信息相符,避免冲突。 - 重启Android Studio,打开 Tools > AVD Manager,点击 Create Virtual Device。在选择系统镜像的步骤,你应该能在
x86 Images或Other Images选项卡里,看到你自己编译的镜像(名称就是你修改的display-name)了。
方法二:通过命令行直接指定启动(快速部署) 如果你只是想快速测试,或者给脚本调用,这个方法更直接。
- 将ZIP包解压到任意你喜欢的目录,例如
~/my_android_images/x86_64/。 - 在Android Studio的AVD Manager里,先任意创建一个虚拟设备(比如选择Pixel 5, API 33),假设设备名是
Pixel5_API33。 - 关闭这个AVD。打开终端,使用以下命令,通过
-sysdir参数指定你自定义镜像的目录来启动它:
emulator @Pixel5_API33 -sysdir ~/my_android_images/x86_64/ -verbose
-verbose 参数会输出详细日志,方便排查问题。第一次启动时,强烈建议加上 -wipe-data 参数,以确保使用全新的数据分区启动,避免与之前AVD的数据冲突。
emulator @Pixel5_API33 -sysdir ~/my_android_images/x86_64/ -wipe-data
这种方法跳过了向SDK注册的步骤,非常灵活,特别适合调试阶段频繁更换不同版本镜像的场景。
4. 进阶与排错:从“能用”到“精通”
当你成功编译并运行起自己的镜像后,可能会不满足于此,想要进行一些定制,或者解决遇到的一些棘手问题。这里我分享几个进阶方向和排错技巧。
定制内核(Kernel):我们之前编译的镜像,使用的是AOSP源码树中预编译好的 kernel-ranchu 或 kernel-qemu。如果你想修改内核配置,或者需要开启某些特定的内核模块,就需要自己编译内核。Android为模拟器维护了一个独立的内核源码仓库(Goldfish内核)。编译它需要另一套环境,大致步骤是:
- 新建目录,用
repo初始化内核仓库(同样建议使用国内镜像)。 - 选择与你的Android版本匹配的内核分支(如
common-android13-5.4)。 - 执行内核的构建脚本。这个过程比编译整个AOSP要快得多,产出就是新的
kernel-ranchu文件。用它替换掉镜像目录里的同名文件,再启动模拟器,就运行在你自定义的内核上了。
修改系统属性与预装应用:这是定制系统最常见需求。你可以修改 device/generic/common 或 device/generic/x86_64(针对模拟器)目录下的配置文件,比如 system.prop 来添加系统属性。预装应用则可以将APK放入 vendor 或 system 分区对应的预置目录(如 vendor/app/)下,并编写相应的 Android.mk 或 Android.bp 文件。记得修改后要重新编译 system.img 或 vendor.img,可以只编译特定模块,比如 make snod(快速重新生成system镜像)或单独编译vendor分区。
深入排错日志:当模拟器无法启动,或者启动后行为异常时,日志是你的最佳帮手。
- 模拟器日志:启动时加上
-verbose和-show-kernel参数,可以在终端看到非常详细的初始化过程和内核日志。 - Android系统日志(logcat):模拟器启动后,在另一个终端用
adb logcat命令可以查看实时的系统日志。如果系统启动失败卡在某个阶段,logcat通常会给出明确的错误原因,比如某个系统服务(Service)崩溃,或权限(SELinux)拒绝。 - 常见启动失败原因:
- 镜像架构不匹配:尝试用x86_64的镜像启动一个创建为arm64的AVD。确保
-sysdir指向的目录架构与AVD创建时选择的架构一致。 - 文件缺失或损坏:确保
-sysdir目录下包含了所有必需的镜像文件,特别是system.img,vendor.img,ramdisk.img和kernel-ranchu。 - SELinux权限问题:如果你对系统做了大量修改,可能会触发SELinux的拒绝(avc: denied)。可以在内核命令行中临时添加
androidboot.selinux=permissive来设置为宽容模式进行测试,并在日志中收集所有拒绝信息,以便后续编写正确的SELinux策略。
- 镜像架构不匹配:尝试用x86_64的镜像启动一个创建为arm64的AVD。确保
编译自己的Android模拟器固件,是一个从理解到掌控Android系统底层的好方法。虽然过程有些漫长,可能会遇到各种坑,但每一次问题的解决,都会让你对Android系统的认识加深一层。我最开始编译一个镜像要折腾好几天,现在环境搭好了,流程熟了,基本上就是喝杯咖啡等它编译完成的事。这份指南希望能帮你少走些弯路,当你第一次看到印着自己编译日期的系统启动界面时,那种感觉,值了。
882



被折叠的 条评论
为什么被折叠?



