动手之前先把话说明白这篇文章不是给你讲“AOSP是什么”的科普而是手把手带你用Pixel 5把Android 13源码拉下来、改出自己的SystemUI、编出能开机的镜像最后再把GMS这套东西想办法塞进去的完整记录。整个过程我踩过的坑、走过的弯路、花了冤枉时间的地方都会如实写出来。如果你手里正好有一台Pixel 5又对每天用的系统有那么点“不满意”那这篇文章就是给你准备的。我需要先坦白一个事实编译AOSP不是一件轻松的事它对你的电脑、网络、耐心都有要求。但当你第一次看到自己编译的系统在手机上亮起来的时候那种成就感是刷任何第三方ROM都给不了的。而且Pixel 5是AOSP官方支持设备里性价比很高的选择二手价格不高解锁BL容易社区资料也多非常适合作为入门AOSP的第一台机器。1. 准备工作的关键细节磁盘、内存、网络与工具链选型很多人编译AOSP失败不是代码有问题而是环境没弄好。这一节我把最基础的准备事项讲透每一项都是我实际验证过的照着做能省下大量排查时间。1.1 硬件配置的底线与推荐先给一个最低配置和推荐配置的对照你可以根据自己的情况对号入座项目最低要求推荐配置说明CPU8核16核及以上编译时间长核越多越快内存16GB32GB或64GB内存不够会直接OOM编到一半报错最痛苦磁盘300GB可用空间500GB以上建议整块SSD单独给AOSP使用系统Ubuntu 20.04 LTSUbuntu 22.04 LTS我用的是22.04官方文档目前推荐这个版本磁盘这块特别说一下。AOSP完整源码checkout下来大约40GB左右但是编译产物极其占空间一次完整编译后整个目录轻松超过200GB。再加上你可能还会尝试不同分支、做备份、缓存ccache容量规划不够的话中途磁盘写满编译直接失败前功尽弃。我第一台编译机器就是被这个坑了后来专门挂了一块500GB的NVMe SSD才安心。1.2 Docker Ubuntu还是双系统这是个老生常谈的问题。我的建议很简单如果你只是想试试AOSP编译那就用Docker如果你确定自己会长期折腾直接装Ubuntu双系统或纯Ubuntu。我最终选择的是Docker方案理由也很实际AOSP的环境依赖偶尔会有冲突比如不同分支可能要求不同的JDK版本、不同的Python版本。用Docker可以把环境完全隔离搞坏了大不了删掉容器重建不会影响宿主机。而且我平时主力机还要用不想为了编译就切换系统。如果你也打算用Docker我的实践路径是宿主机Ubuntu 22.04Docker里跑一个Ubuntu 22.04容器挂载一个宿主机目录作为工作区。下面是我初始化容器时的核心命令docker pull ubuntu:22.04 docker run -it --name aosp-build -v /mnt/aosp:/aosp ubuntu:22.04 /bin/bash进入容器后先安装基础依赖apt update apt install -y git-core gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 libncurses5 libncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig python3 python3-pip openjdk-11-jdk注意Android 13用的是OpenJDK 11不要装17版本不匹配编译会报错。这个细节我当时就栽过跟头后面编译阶段会展开讲。1.3 网络准备源码下载和镜像加速的取舍这一步我不展开讲太多网络细节只说明一个关键点repo sync拉取AOSP全量代码需要从Google的Gerrit服务器获取数十GB的数据访问稳定性直接决定了你能否顺利拿到源码。常见做法是配置国内镜像加速很多高校和云厂商都提供AOSP镜像。我的实际经验是优先尝试官方源如果同步中途频繁断开或速度极慢果断切换到镜像不要死磕。镜像切换不会影响后续编译只是repo manifest里的URL指向不同而已。哪怕你是用Docker方案这一步也完全一致镜像地址替换即可。2. 源码获取与分支选择为什么锁定Android 13既然标题是Android 13这一节我讲讲分支选择的逻辑和具体的源码下载操作。你也可以借此理解AOSP分支管理的思路方便以后切到其他版本。2.1 AOSP分支与设备支持的匹配关系AOSP官方对Pixel 5代号redfin的正式支持版本是Android 11到Android 13。也就是说你的Pixel 5最高只支持刷Android 13的AOSP版本再往上的Android 14就没有官方驱动包了强行编译需要自己适配内核和vendor blobs难度会上升好几个数量级。所以我锁定android-13.0.0_rX分支这个分支是有对应Pixel 5官方二进制驱动包的。获取方式在官网的“二进制驱动下载”页面需要按你的设备代号redfin去匹配对应的版本号。这里有个细节驱动包是大版本匹配的Android 13的驱动不能用在Android 12上反之亦然别混用。2.2 repo init与首次同步的完整命令首先确保你的环境里有repo工具mkdir ~/bin curl https://storage.googleapis.com/git-repo-downloads/repo ~/bin/repo chmod ax ~/bin/repo export PATH~/bin:$PATH然后创建工作目录并初始化仓库mkdir -p ~/aosp/redfin cd ~/aosp/redfin repo init -u https://android.googlesource.com/platform/manifest -b android-13.0.0_r41 repo sync -c -j8这里的-c参数表示只同步当前分支的代码而不是所有分支能省下不少时间和存储空间。-j8表示并发任务数如果你的CPU核数多可以调高一些比如-j16。首次同步耗时主要看网络。如果用的是国内镜像通常需要半天时间。中途如果卡住不动先别急等一会儿超过半小时没反应再中断重试repo支持断点续传重新执行repo sync会继续未完成的部分。同步完成后你会看到工作目录下的源码结构比较核心的几个目录包括frameworks/系统框架层、packages/系统应用、vendor/厂商定制内容等。2.3 同步完成后的目录结构认知我刚接触AOSP时面对几十个目录完全不知道从哪下手。这里给你快速梳理几个关键目录的作用frameworks/base系统框架核心按键处理、应用生命周期管理、SystemUI都在这一层是定制系统最常改的地方frameworks/av音视频相关的框架层媒体栈的核心packages/apps系统自带应用源码比如Settings、Launcher3、Messaging等vendor存放厂商二进制文件和后编译脚本刷机包的生成逻辑也在这里device设备相关配置、BoardConfig、产品定义文件kernel内核源码目录需要单独拉取后面细说了解这些目录后定制时就知道改哪里、去哪里搜代码不会像无头苍蝇一样乱撞。3. 设备层准备Pixel 5的驱动、内核与vendor分区AOSP源码默认不包含任何硬件相关的驱动你拉下来的代码只是一个“通用Android系统”没有Pixel 5的Wi-Fi、摄像头、GPU等硬件的支持代码。这也就解释了为什么很多人编完刷进去发现连触摸屏都没反应——不是编错了是设备层的东西还没放进去。3.1 谷歌官方二进制驱动的下载与解压去官方的二进制驱动页面选择Android 13对应的redfin驱动包。你通常能看到两组包一组是设备驱动包含Wi-Fi、蓝牙、摄像头、GPU等二进制模块解压后是一个sh脚本加一堆blob一组是vendor驱动包含了HAL实现和vendor分区的内容同样以sh脚本形式提供下载好后把sh脚本放到AOSP源码根目录逐个执行chmod x extract_google_devices_redfin.sh ./extract_google_devices_redfin.sh chmod x extract_google_redfin.sh ./extract_google_redfin.sh执行过程中会提示你接受协议并选择解压位置一路输入I ACCEPT即可。解压完成后源码根目录的vendor/google和device/google这两个目录会被填充完整的专有文件。这个过程中如果提示某些文件不存在大概率是你下载的驱动版本号和你选择的AOSP分支版本不匹配重新核对版本号再下载。3.2 内核源码的获取与编译时机Pixel 5的内核不是AOSP主仓库的一部分需要单独拉取。这一步也容易劝退新人因为内核编译的时间不比系统编译短多少。官方内核仓库地址是https://android.googlesource.com/kernel/msmPixel 5对应的是android-msm-redfin-4.19-android13分支mkdir -p ~/aosp/kernel/redfin cd ~/aosp/kernel/redfin repo init -u https://android.googlesource.com/kernel/manifest -b android-msm-redfin-4.19-android13 repo sync -c -j8注意这里用的是另一个manifest仓库不是系统源码那个。内核源码拉完以后先不用急着编因为系统编译的集成逻辑会引导你完成。你只需要确保内核源码在工作区准备好后续执行编译命令时会自动关联。如果你的目标是快速跑通一个能开机的系统其实可以暂时跳过内核编译这一步直接使用预编译内核镜像。但如果你改了内核配置或者想加自己的内核模块那就必须完整编译内核。我建议第一次先用预编译内核跑通流程之后再逐步深入内核定制。3.3 为什么不建议修改设备定义文件AOSP的设备定义文件device/google/redfin下的BoardConfig.mk、device.mk等已经写好了Pixel 5的完整硬件配置分区大小、内核镜像路径、selinux策略、依赖的HAL服务等。第一次编译时这些文件不是给你改的而是给你用的。我见过不少新手上来就想调整分区大小或者修改selinux结果编译出一堆莫名其妙的问题。正确做法是第一次编译时保持所有设备定义文件原样只改动你想定制的那部分内容比如SystemUI、开机动画等。等整套流程跑通了再考虑更底层的修改。4. SystemUI定制实战从改时间格式到自定义下拉栏布局SystemUI是Android系统界面中最重要的一个进程——你看到的沉浸式状态栏、快捷开关面板、通知栏、锁屏界面全部由它绘制。这块也是AOSP定制中最容易出效果、最有成就感的部分。这一节我会拿一个具体案例拆解整个开发循环找到源码、修改、编译、验证。4.1 SystemUI源码结构速览SystemUI的源码在frameworks/base/packages/SystemUI下。它的内部结构大致如下src/com/android/systemui/statusbar状态栏和通知栏相关逻辑src/com/android/systemui/qs快捷设置面板Quick Settingssrc/com/android/systemui/screenshot截屏相关src/com/android/systemui/volume音量控制res/layout各种布局XML定义对于第一次定制的人来说优先关注res/layout和res/values。改布局XML和改字符串资源的门槛很低稍微有点Android基础就能上手而且效果立竿见影。改Java/Kotlin代码的编译和调试成本更高可以放在后面慢慢来。4.2 实操案例修改状态栏时间样式我们先做一个相对简单但非常直观的改动把状态栏左侧的时间改成“HH:mm:ss”格式并且在时间前面加一个“⏱”符号虽然默认状态栏左侧通常没有时间但Pixel 5上可以开启显示秒数的选项这里以显示格式为切入点讲解资源修改的思路。在SystemUI的res/layout/status_bar.xml中找到Clock控件相关的布局你会看到类似这样的代码com.android.systemui.statusbar.policy.Clock android:idid/clock android:layout_widthwrap_content android:layout_heightmatch_parent android:paddingStart4dp android:paddingEnd4dp android:textAppearancestyle/TextAppearance.StatusBar.Clock android:textColorcolor/status_bar_clock_color android:textSize14sp /如果状态栏默认没有显示时间你需要在其父布局status_bar_contents中添加这个Clock控件并确保android:visibilityvisible。这一步操作需要熟悉状态栏布局层级建议一边对照folding_icon相关的结构布局一边在res/layout下多搜索id/clock看它在哪里被引用。真正控制时间显示格式的代码在Clock.java里面// frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java private void updateClock() { if (mClockFormat null) { mClockFormat new SimpleDateFormat(mClockFormatString); } ... }其中mClockFormatString默认从R.string.status_bar_clock_format读取在res/values/strings.xml里可以看到它的值。如果我们想默认显示秒可以把默认格式改成string namestatus_bar_clock_formatHH:mm:ss/string这里有个隐藏知识点SystemUI在亮屏状态下默认每分钟刷新一次时钟如果改成带上秒的格式需要同步修改Clock.java中的刷新频率否则你会看到秒数不是实时变化的。修改方法是在Clock.java中找到mClockFormat相关的Handler刷新逻辑把updateClock()的调用频率从分钟级改到秒级。这一步对新手来说稍微有点绕但确实是理解SystemUI刷新机制的好切入点。4.3 修改快捷设置面板的行列数另一个效果明显的定制是调整快捷设置面板的网格布局。默认情况下Pixel 5的快捷设置面板是4列你可以通过修改res/values/config.xml里的配置字段来改变行列数string namequick_settings_tiles_defaultwifi,bt,dnd,rotation,flashlight,airplane/string string namequick_settings_tiles_stockwifi,bt,dnd,rotation,flashlight,airplane/string这两个字符串定义了默认显示的快捷开关和“重置为默认布局”时的开关列表。你可以把需要的开关加进去也可以调整顺序。比如我想把“省电模式”加到默认列表里string namequick_settings_tiles_defaultwifi,bt,dnd,battery,rotation,flashlight,airplane/string行列数本身在QuickSettingsController.java中计算通过resources.getInteger(R.integer.quick_settings_num_columns)控制默认值在res/values/integers.xml中integer namequick_settings_num_columns4/integer想改成3列就把4改成3重新编译SystemUI模块后效果立刻可见。4.4 系统应用的预置与删除除了SystemUI你还可以直接增删系统应用。AOSP源码里包含了Launcher3、Messaging、Calendar等基础应用但很多国产ROM会把这套换成自己的。定制时你可以在vendor/google/redfin/device.mk或你自己新建的产品mk文件中通过PRODUCT_PACKAGES变量控制哪些应用要被编译进去。比如想把Messaging移除就在对应的mk文件中注释掉PRODUCT_PACKAGES \ Launcher3 \ # Messaging \ # 注释掉即可移除 Settings \ ...同步的如果你想加入自己的预置APK可以把它放到一个固定目录然后在mk文件里用PRODUCT_COPY_FILES拷贝到系统分区PRODUCT_COPY_FILES \ device/google/redfin/prebuilts/MyApp/MyApp.apk:system/app/MyApp/MyApp.apk这种方式的优点是简单直接不需要把APK做成系统源码的一部分适合快速集成第三方应用。缺点是在系统分区空间紧张时需要谨慎不过Pixel 5的system分区容量还算宽裕一般够用。5. 编译刷机的完整流程从source到开机画面所有的源码准备、定制修改完成后终于到了最激动人心的编译环节。这一节我把从输入第一个编译命令到手机屏幕上出现自己系统的全过程写清楚同时整理几个最容易翻车的细节。5.1 初始化编译环境AOSP用的是build/envsetup.sh来初始化编译环境然后通过lunch选择目标产品cd ~/aosp/redfin source build/envsetup.sh lunch执行lunch会列出所有可用的编译目标配置你需要找到aosp_redfin-userdebug这一项。这里解释一下三种构建类型的区别user用于正式发布的版本没有root权限默认关闭adbuserdebug介于user和eng之间有root权限但受限默认开启adb调试常用eng开发版完全root性能相对差一些我推荐第一次编译选userdebug它和日常刷机使用的体验最接近同时保留调试能力。选好之后lunch会输出当前配置信息包括TARGET_PRODUCT、TARGET_BUILD_VARIANT等。5.2 正式编译与ccache优化执行编译命令make -j16如果你是第一次完整编译可能需要两三个小时甚至更久。如果机器内存不够建议在make前面加上export USE_CCACHE1并设置ccache大小缓存可以大幅加速后续的增量编译export USE_CCACHE1 export CCACHE_EXEC$(which ccache) ccache -M 100G make -j16注意-j后面的数字一般设为CPU核心数的两倍左右。如果编译中途因为内存不足被杀掉可以调低并发数再试比如-j8。编译成功的标志是最后出现类似### make completed successfully ###的提示。编译产物主要生成在out/target/product/redfin/目录下重点关注下面几个文件boot.img内核镜像system.img系统分区镜像vendor.img厂商分区镜像dtbo.img设备树镜像5.3 刷机前必须做的事刷机之前先把手机解锁Bootloader。注意这一步会清除手机上的所有数据一定要提前备份。Pixel系列的解锁命令很简单adb reboot bootloader fastboot flashing unlock在bootloader界面确认解锁后手机会重启并恢复出厂设置。然后继续进入bootloader模式开始刷入你编译好的镜像fastboot flash boot out/target/product/redfin/boot.img fastboot flash dtbo out/target/product/redfin/dtbo.img fastboot flash system out/target/product/redfin/system.img fastboot flash vendor out/target/product/redfin/vendor.img如果之前设备里已经刷过别的系统最好先执行fastboot erase userdata和fastboot erase cache避免数据不兼容导致的开机异常。全部刷完后执行fastboot reboot首次开机时间可能会比较长Android系统在重建ART缓存和初始化数据耐心等上几分钟是正常的。如果超过10分钟还在动画循环或者反复重启先别慌通常是你漏刷了某个分区或者驱动版本不匹配回看第3节的步骤检查一遍。5.4 我踩过的三个编译坑说三个我实际遇到过的坑现在想起来都心有余悸但每一个都有明确的解决路径。第一个是JDK版本问题。AOSP Android 13明确要求OpenJDK 11但我一开始图省事装的是系统默认的OpenJDK 17。编译到一半突然报一堆Unsupported class file major version错误排查了很久才意识到是JDK版本问题。卸载17、装好11之后重新. build/envsetup.sh问题立刻消失。第二个是ninja进程被OOM killer干掉。编译Android 13时链接系统镜像的阶段非常吃内存16GB内存很容易触顶。我后来在两个地方做了优化一是加大swap空间到32GB二是降低-j参数到8终于稳定过编译。第三个是fastboot无法识别设备。这个更多是驱动问题Linux下需要配置好udev规则把Google的USB vendor ID加入/etc/udev/rules.d/51-android.rules然后重载udev规则、拔插USB线就能正常识别了。6. GMS集成方案把Google服务装进你的AOSP最后聊最头疼的部分——GMS。AOSP本身不包含任何Google服务框架你可能连Play商店都找不到。如果你的系统只是自己玩玩不加GMS完全没问题但你如果想像日常手机那样用Google生态就必须另外处理。这一节我只聊技术方案和实际步骤不涉及任何绕过认证或付费协议的“灰色操作”。6.1 为什么AOSP不内置GMSGoogle服务框架GMS是一套闭源的二进制组件包括Google Play服务、Play商店、Google搜索、地图等。Google把GMS的授权作为其生态的一部分设备厂商需要通过兼容性测试CTS才能预装完整的GMS并获取Google的正式授权。对于个人开发者来说这条路成本极高所以大家通常走“Open GApps”或类似社区方案把现成的GMS包刷入系统。但要注意的是社区包不一定能在所有设备上完美运行。AOSP本身是去Google化的很多系统组件比如SetupWizard在缺少GMS时会走“无Google”逻辑加入GMS后部分组件的行为可能和预期不一致需要逐个适配。6.2 集成前必须评估的三个问题决定要集成GMS之前先问自己三个问题你更看重“开机即用”还是“纯净可控”GMS包体积不小会占用几百MB甚至更多的系统分区空间你能否接受潜在的不稳定性社区GMS包不是官方为你的设备单独定制的偶尔出现崩溃或耗电异常是常见现象你是否在意GMS的账户登录和通知推送功能如果不强求这些可以只装基础服务不用装全家桶如果以上考虑清楚了再继续操作。6.3 集成Open GApps包的实践步骤社区里比较流行的方案是下载对应Android版本和CPU架构的Open GApps包pico或nano版本体积最小只包含Play服务核心框架适合AOSP系统。你把下载到的zip包放到手机存储里然后在Recovery模式里刷入或者在AOSP编译时把它作为预置方案集成进去。我建议用“在编译时集成”的方式因为这样系统第一次开机就直接带GMS不用额外刷机步骤。操作方法是把Open GApps包解压将其中的system/内容拷入AOSP源码适当位置比如放在vendor/private-gms目录下然后在device/google/redfin/device.mk里用PRODUCT_COPY_FILES把这些文件刷入系统分区。举个例子如果Open GApps里包含system/priv-app/GoogleServicesFramework/GoogleServicesFramework.apk你就在mk文件里加一行PRODUCT_COPY_FILES \ vendor/private-gms/priv-app/GoogleServicesFramework/GoogleServicesFramework.apk:system/priv-app/GoogleServicesFramework/GoogleServicesFramework.apk注意priv-app目录下的应用比system/app拥有更高权限GMS必须有这个权限等级才能正常工作不要放错位置。这是很多人集成后GMS一直闪烁停止运行的一个常见原因。6.4 配套修改selinux策略和权限仅仅把APK丢进系统分区是不够的。Android的SELinux策略会阻止未定义的GMS进程执行某些操作。实战中需要检查device/google/redfin下的sepolicy目录看是否有针对Google服务框架的te规则文件。如果没有系统日志里会刷出大量的avc: denied错误。处理方式有两种一种是在sepolicy目录下手动添加对应的te规则另一种是直接放宽权限把系统设为SELinux permissive模式。前者是正道但需要理解te语法、排查工作量很大后者省事但不安全不推荐日常使用。我个人的经验是先用adb shell dmesg | grep avc把主要的denied日志抓出来优先处理会影响GMS启动的几条规则其它次要的权限报错可以先忽略。GMS的核心服务只要能正常跑起来大部分应用层面的功能就能用。这个思路可能不够“完美”但对第一次集成GMS的新手来说是很务实的。6.5 刷入后验证与常见故障排查刷入集成GMS的系统后第一次开机如果停在“正在启动”或者弹窗报“Google Play服务已停止运行”别慌按这个顺序排查检查/system/priv-app和/system/app目录下有没有对应的APK文件权限是否为644看adb logcat | grep -i gms日志找出具体报错原因确认系统分区剩余空间是否充足空间不足也可能导致GMS初始化失败如果一直崩溃尝试adb shell pm clear com.google.android.gms清掉数据后重启如果这些步骤都做了还是不行大概率是SELinux权限问题或者APK的签名校验失败。签名失败的情况更致命——AOSP的platform签名和GMS官方签名不匹配系统拒绝运行未经签名的应用。这种情况要么用userdebug版的实用性妥协处理要么尝试找对应的签名版GMS包。不过这个问题在Open GApps包中一般已经被处理过正常流程下很少遇到。整套流程走完你手机上跑的就是一个完全由你掌控的Android 13系统。它能开机、能联网、能用GMS的基础服务UI上有只有你知道的小改动。这个系统可能包体不小、也可能偶发小毛病但它是你自己的作品。我把踩坑的完整过程写在这里你按着走能比我少浪费太多时间。动手试试吧。
