Android系统源码分析从零开始手把手教你编译源码和读懂启动流程解决手机卡顿问题
嘿,朋友!看到这篇文章的时候,你是不是也曾对着手机发呆——明明配置不低,怎么用的久了就卡?是不是也想成为那种能看懂系统源码、解决实际问题的大神?来,搬好小板凳,今天咱们就从零开始,把Android系统源码这块硬骨头啃下来。
一、先别急着敲命令,搞清楚我们要干什么
很多初学者上来就去找”Android源码下载”,然后被各种教程搞懵了:为什么我要配置环境?为什么要编译?编译完能干嘛?
让我给你打个比方——你想学怎么修发动机,是不是一上来就拆开别人的车?不对吧?你应该先知道这车是怎么跑起来的,哪里是引擎,哪里是传动,哪里是控制单元。懂了原理,你才知道该看什么、改什么、修什么。
Android启动流程就是这辆车的发动机工作原理。手机卡顿,本质上就是某个环节慢了——可能是开机动画卡,可能是桌面启动慢,可能是某个App起来特别慢。你要解决卡顿,就得知道整个启动链条上哪一环出了问题,然后才能精准下手。
而我们第一步要做的,就是自己亲手把这套系统”造”一遍。只有编译过源码,你才知道原来这个系统这么复杂,原来一个”打开设置”背后要经过那么多层调用。这种体感,看多少遍文章都替代不了。
二、编译环境的准备——别再被坑了
2.1 操作系统选择
说实话,我见过太多人用Windows来编译Android源码,结果折腾了一周,最后全是环境问题。我建议你直接用Linux——不是开玩笑,是真的。
我用的是Ubuntu 22.04 LTS,这是目前社区支持最好、教程最全的版本。你可以在你的电脑上装一个虚拟机(用VirtualBox或者VMware都行),或者直接在双系统里装上。如果你电脑配置一般,虚拟机给个64GB以上的硬盘、8GB以上内存就够用了。
为什么要Linux?因为Android源码本身就是用Linux内核驱动的手机系统,在Linux下编译,那些底层的工具和依赖最容易搞定。Windows下你要用WSL2,但WSL2有时候会出各种奇怪的权限问题,对于初学者来说,直接装Linux最省心。
2.2 配置编译环境
好,假设你已经装好了Ubuntu 22.04。打开终端,执行这一串命令,一步一步来,别跳步:
# 1. 先更新系统包
sudo apt update && sudo apt upgrade -y
# 2. 安装编译所需的依赖包
# 这一步很关键,少一个包后面就会报错
sudo apt install -y git-core gnupg flex bison build-essential \
zip curl zlib1g-dev libc6-dev-i386 x11proto-core-dev \
libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils \
xsltproc unzip fontconfig python3 python3-pip openjdk-17-jdk
注意看,最后一行是openjdk-17-jdk。为什么是17?因为不同版本的Android源码需要不同版本的JDK:
| Android版本 | 推荐JDK版本 |
|---|---|
| Android 14 (AOSP 14.x) | JDK 17 |
| Android 13 (AOSP 13.x) | JDK 17 |
| Android 12 (AOSP 12.x) | JDK 11 |
| Android 11及以下 | JDK 8 |
你现在编译的是哪个版本,就用对应的JDK。你可以用java -version命令查看当前系统安装的版本。
2.3 安装必要的工具
除了JDK,还有几个工具是必须装的:
# 安装repo工具(这是Google用来管理多个Git仓库的工具)
mkdir -p ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
# 把这个路径加到环境变量里
echo 'export PATH=~/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
# 安装ccache,这个工具能加速后续编译(重要!)
sudo apt install -y ccache
ccache是什么?简单说就是”编译缓存”。你第一次编译Android源码要花好几个小时,但如果你在编译前配置好ccache,后续重新编译或者切换分支的时候,很多文件就不用重新编译了,速度能快几倍甚至十几倍。
你可以这样配置ccache:
# 设置ccache缓存大小为50GB
export USE_CCACHE=1
export CCACHE_EXEC=/usr/bin/ccache
export CCACHE_DIR=$HOME/.ccache
ccache -M 50G
把这个写到你的~/.bashrc里,每次打开终端都会自动生效。
三、下载源码——这是最考验耐心的环节
3.1 选择一个版本
网上各种教程说的版本乱七八糟,我建议你从Android 14(AOSP 14.0_r1)开始。原因很简单:
- 它是目前比较新的版本,资料多,社区活跃
- 不需要太老的JDK,配置相对简单
- 对你理解现代Android架构(Project Mainline、模块化等)最有帮助
3.2 下载源码
# 找一个足够大的目录来存放源码(至少需要100GB可用空间)
mkdir ~/aosp && cd ~/aosp
# 初始化仓库,指定你要的版本
repo init -u https://android.googlesource.com/platform/manifest -b android-14.0_r1
# 开始同步(这一步非常非常慢,取决于你的网速)
# 中国同学建议配个代理,或者用国内的镜像源
repo sync -c -j4 --no-tags
repo sync -c -c表示只下载你当前分支的代码,不下载所有历史分支,能省很多空间。-j4表示用4个线程同时下载。--no-tags不下载tag信息,也能省时间。
如果你是国内网络环境,配个代理会快很多:
# 设置代理(换成你自己的代理地址)
export https_proxy=http://127.0.0.1:7890
export http_proxy=http://127.0.0.1:7890
# 然后再执行repo sync
有些同学下载半天下不动,这里有个取巧的办法——用清华大学的AOSP镜像源:
# 初始化时指定镜像源
repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-14.0_r1
清华镜像源的速度比你直接连Google快太多了。
3.3 下载完之后
源码下载完成之后,你会看到一个目录结构,大概长这样:
aosp/
├── art/ # ART虚拟机
├── bionic/ # C标准库(Android自己的实现)
├── bootable/ # 启动相关代码
├── packages/ # 系统应用包(桌面、设置、相机等)
├── frameworks/ # 核心框架(最最重要的部分)
├── system/ # 系统底层(init、vold等)
├── hardware/ # 硬件抽象层(HAL)
├── kernel/ # 内核(通常单独下载)
├── external/ # 第三方开源库
└── ...
每个目录都有README文件,告诉你里面放了什么。你可以花半小时翻一翻,大概知道这些目录都是干嘛的。但先别急着研究,我们下一步是编译。
四、编译源码——第一次”造”出你的手机系统
4.1 选择编译目标
源码下载完之后,不能直接编译,你得告诉编译器你要编出什么样的系统。
常见的编译目标有:
| 目标代号 | 说明 |
|---|---|
| aosp_arm64-eng | 编译给64位ARM设备的eng版本(开发调试用) |
| aosp_arm64-userdebug | userdebug版本,可以root,适合调试 |
| aosp_cf_x86_64_phone-eng | 模拟手机环境的x86_64模拟器 |
| aosp_cf_x86_64_phone-userdebug | 同上,userdebug版本 |
对于初学者,我建议用模拟器来跑,这样不需要真机,还能边跑边看日志。
4.2 开始编译
# 进入源码目录
cd ~/aosp
# 初始化编译环境
source build/envsetup.sh
# 选择编译目标
lunch aosp_cf_x86_64_phone-userdebug
# 开始编译(这一步会很长,我第一次编译花了将近4个小时)
m -j8
m -j8表示用8个核心来并行编译。你可以根据自己电脑的核心数来调整,比如你有16核就用m -j16。
编译过程中可能会遇到各种各样的错误。别慌,90%的错误都是环境问题,Google一下错误信息,基本都能找到解决方案。常见的问题包括:
- Python版本不对:Android 14需要Python 3.11+,确保
python3 --version显示的是3.11或更高 - 磁盘空间不足:编译过程中会生成大量临时文件,确保你有至少150GB的可用空间
- JDK版本不对:再次强调,不同版本用不同JDK,编译前用
java -version确认
编译成功之后,你会看到类似这样的输出:
#### build completed successfully (01:47:32 (hh:mm:ss)) ####
恭喜!你已经成功编译出了自己的Android系统了!
4.3 运行模拟器
# 启动模拟器
emulator
如果一切顺利,你应该能看到一个模拟的Android手机界面了。你可以像操作真机一样在模拟器上点来点去,打开设置、查看应用、测测速度。
第一次看到自己编译的系统跑起来的时候,那种感觉真的很难形容。就好像你自己盖了一栋房子,然后打开门走进去一样。
五、读懂启动流程——从按下电源键到桌面出现
好,源码能编译了,模拟器能跑了。现在问题来了:手机从按下电源键到桌面出现,这中间到底发生了什么?
这是理解卡顿问题的关键。我带你从最底层一层一层往上剥。
5.1 第一层:Bootloader和Linux内核启动
当你按下电源键,芯片开始从Flash中读取Bootloader(就是设备上的一个小程序,类似PC的BIOS)。Bootloader检查硬件是否正常,然后把控制权交给Linux内核。
内核启动后会做几件事:
- 初始化硬件驱动(屏幕、内存、存储、网络等)
- 挂载根文件系统
- 启动第一个用户态进程——init进程(PID=1)
你可以在源码的system/core/init/目录下看到init进程的完整代码。这是整个用户态系统的起点,非常非常重要。
5.2 第二层:init进程——系统的”大管家”
init进程是Android用户态的第一个进程,它的职责非常多:
- 读取
init.rc配置文件,启动各种系统服务 - 管理zygote进程(Android应用的”孵化池”)
- 创建和回收子进程
- 监听系统属性变化
来看一个最简单的init.rc片段:
# 这是system/core/init/目录下某个rc文件的内容
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media.se
这段配置告诉init:请启动zygote进程,它的可执行文件是app_process64,参数是那些。注意onrestart后面的内容——当zygote重启时,要重启audioserver、cameraserver等。这就是系统服务之间的依赖关系。
5.3 第三层:Zygote——所有应用的”母亲”
Zygote是Android中最独特的进程之一。你可以把它理解为一个应用模板工厂:
- Zygote启动时,会预先加载大量常用类(这样新App启动时就不需要重新加载了)
- Zygote监听一个socket,等待系统或其他进程来请求”孵蛋”
- 当有人请求创建新App时,Zygote通过
fork()系统调用复制自己,生成一个新的子进程 - 这个子进程就是App的进程,它会执行
ActivityThread.main()来启动App
为什么用fork而不是直接new?因为fork子进程会拷贝父进程的所有内存空间(写时复制),这样新App启动时已经预加载了大量系统类,速度比从零开始快得多。这就是为什么Android应用启动比很多其他平台快的原因之一。
来,看一段关键代码,在frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中:
public static void main(String[] argv) {
// ... 省略前面的代码 ...
// 启动SystemServer(系统核心服务)
if (startSystemServer) {
Runnable r = forkSystemServer("apk path here", "uid", ...);
// 在主进程中执行SystemServer的启动逻辑
if (r != null) {
r.run();
return;
}
}
// 进入循环,等待请求来创建新进程
runSelectLoop();
// 后面是关闭Zygote的代码
closeServerSocket();
}
注意forkSystemServer这个方法——它是Zygote中非常关键的一个步骤,负责启动SystemServer进程。
5.4 第四层:SystemServer——所有系统服务的”总调度”
SystemServer进程是Android系统最核心的进程,它启动了几乎所有重要的系统服务:
- PackageManagerService(PMS):管理所有已安装的应用
- ActivityManagerService(AMS):管理Activity的启动、销毁、生命周期
- WindowManagerService(WMS):管理窗口和屏幕渲染
- PowerManagerService:管理电源和屏幕开关
- ConnectivityService:管理网络连接
- NotificationManagerService:管理通知
这些服务都在frameworks/base/services/java/com/android/server/SystemServer.java中被启动。来看关键代码:
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
// 1. 启动各种服务之前,先做一些初始化工作
try {
mSystemServiceManager = new SystemServiceManager(mSystemContext);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "Failure starting system service", ex);
}
// 2. 启动核心服务(顺序很重要!)
try {
// 先启动电源管理服务
traceBeginAndSlog("StartPowerManager");
mSystemServiceManager.startService(PowerManagerService.class);
traceEnd();
// 再启动窗口管理服务
traceBeginAndSlog("StartWindowManagerService");
WindowManagerService mainWms = WindowManagerService.main(
mSystemContext, inputManagerService,
mTransactionExecutor, systemReadyNow,
mSupuiController, this, displayThread.getHandler());
mWindowManager = mainWms;
traceEnd();
// 再启动包管理服务
traceBeginAndSlog("StartPackageManagerService");
mPackageManagerService = PackageManagerService.main(
mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST,
mOnlyCore);
traceEnd();
// 再启动Activity管理服务
traceBeginAndSlog("StartActivityManager");
ActivityManagerService.LauncherPrimaer.getInstance().start();
traceEnd();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "Failure during system startup", ex);
killProcess("system", ex.toString());
}
// 3. 通知所有系统服务已经就绪
try {
Slog.i("System", "System server is running");
} catch (Throwable ex) {
Slog.e("System", "Failure starting system server", ex);
}
// 4. 通知AMS系统已经启动完成,可以启动桌面了
try {
ActivityManagerService.self().systemReady(() -> {
Slog.i("System", "Making services ready");
mSystemServiceManager.startBootPhase(
SystemService.PHASE_ACTIVITY_MANAGER_READY);
// ... 继续启动其他服务 ...
});
} catch (Throwable ex) {
Slog.e("System", "Failure starting system server", ex);
}
}
注意看这个启动顺序:PowerManagerService → WindowManagerService → PackageManagerService → ActivityManagerService,这不是随意的,而是有严格依赖关系的。比如WMS依赖PMS(要知道有哪些应用才能管理窗口),AMS依赖PMS和WMS。
5.5 第五层:Launcher——桌面启动
SystemServer启动完成之后,AMS会发送一个广播,通知Launcher(桌面App)启动。Launcher是一个特殊的系统应用,它的进程也是通过Zygote fork出来的。
Launcher启动后,会:
- 从PackageManager中获取所有已安装的应用列表
- 渲染桌面界面
- 处理用户的点击事件
到这里,手机就”启动完成”了。用户可以看到桌面,可以打开App了。
六、从启动流程看卡顿——找到问题的根源
好,现在你已经知道了Android启动的完整链条。让我们回到最初的问题:手机为什么会卡?
6.1 卡顿的常见原因
Android手机卡顿,通常可以归为以下几类:
6.1.1 开机启动慢
这是最常见的”卡”。用户按下电源键,等了很久才能看到桌面。原因可能是:
- SystemServer启动慢:某些服务启动耗时过长
- Zygote预热不足:预加载的类太少,App启动时需要临时加载
- Launcher启动慢:桌面应用本身就有问题
- 存储读写慢:ROM读写速度慢,加载资源慢
6.1.2 App启动慢
打开某个App特别慢。原因可能是:
- 该App的类太多:Zygote fork后需要加载大量类
- 该App的资源太多:布局文件多、图片多,解析耗时
- 该App的初始化逻辑重:
Application.onCreate()里做了太多事 - 该App依赖的服务还没启动好:比如某些SDK需要等待AMS就绪
6.1.3 运行中卡顿(掉帧)
这是在用App的过程中,界面不流畅。原因可能是:
- 主线程做了太多事情:UI线程被阻塞
- 过度绘制:同一像素被绘制了多次
- 内存不足:频繁GC导致卡顿
- CPU调度问题:某些进程抢占了CPU资源
6.2 如何用源码知识来排查卡顿
既然我们看了源码,现在可以用”内部视角”来分析问题了。
场景一:开机慢,怎么排查?
思路:从init进程开始,逐步追踪每个阶段的耗时。
第一步,看init进程启动各个服务的时间。在源码中,init进程通过解析init.rc文件来启动服务。你可以:
# 在模拟器或真机上执行这个命令,查看系统启动日志
adb logcat -b all -t 10000 | grep "init"
但更精确的方法是看boot timing。Android系统有一个专门的开机时间统计机制——bootstat。你可以在源码中看到相关代码:
在system/core/init/目录下,有一个bootstat.cpp文件,它会记录每个阶段的启动时间。编译运行后,你可以通过以下命令查看详细数据:
adb shell getprop init.svc.boot_progress
输出的数字表示当前处于启动的哪个阶段。更详细的数据可以在/data/misc/bootstat/bootstat.txt中找到(需要root权限)。
第二步,看SystemServer各服务的启动耗时。在frameworks/base/services/java/com/android/server/SystemServer.java中,你会发现每一行启动服务的代码都被traceBeginAndSlog和traceEnd包围。这些trace信息会输出到log中:
# 查看SystemServer各服务的启动耗时
adb logcat -s SystemServer
你会看到类似这样的输出:
SystemServer: Starting PowerManagerService took 150ms
SystemServer: Starting WindowManagerService took 2300ms ← 注意这个!太慢了!
SystemServer: Starting PackageManagerService took 5200ms ← 这个也偏慢
SystemServer: Starting ActivityManagerService took 800ms
如果某个服务耗时异常,你就可以去对应的源码文件里进一步分析。比如WMS启动慢,就去frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java中找原因。
场景二:某个App启动慢,怎么排查?
思路:从Zygote fork开始,追踪App进程的创建到Activity显示的全过程。
你可以用Android Studio的Trace CPU功能,也可以用源码中的am命令:
# 使用am命令启动App并记录耗时
adb shell am start -W com.example.myapp/.MainActivity
输出会告诉你:
Starting: Intent { cmp=com.example.myapp/.MainActivity }
Status: ok
Activity: com.example.myapp/.MainActivity
ThisTime: 1234 ← 第一个Activity的启动时间
TotalTime: 2345 ← 整个启动过程的总时间
WaitTime: 2500 ← 包括界面显示在内的总等待时间
如果TotalTime很大,说明App启动本身就慢。你可以进一步分析:
# 查看App进程从创建到Activity显示的完整日志
adb logcat -s ActivityManager
你会看到类似这样的信息:
ActivityManager: Start proc 12345:com.example.myapp/u0a150 for activity com.example.myapp/.MainActivity
ActivityManager: Displayed com.example.myapp/.MainActivity: +1234ms
这个+1234ms就是Activity从创建到完全显示的时间。如果这个值很大,可以去App的Application.onCreate()和Activity.onCreate()中找问题——是不是在初始化时加载了太多资源?是不是调用了耗时操作?
场景三:运行中卡顿(掉帧),怎么排查?
思路:从SurfaceFlinger和Choreographer入手,分析帧渲染时间。
Android的UI渲染由一个关键类负责——Choreographer。它负责协调帧的绘制,每帧的时间预算大约是16.67ms(60fps)。如果某一帧的绘制超过了这个时间,用户就能感知到卡顿。
你可以用以下命令查看掉帧情况:
# 开启过度绘制调试
adb shell setprop debug.layout true
adb shell setprop debug.choreographer.frametime 1
# 查看帧渲染时间
adb shell dumpsys surfaceflinger --latency <window_name>
更简单的方式是用Android Studio的Android Profiler,在CPU和GPU面板上直接看帧率波动。
从源码角度看,如果掉帧是因为主线程被阻塞,你可以在ActivityThread.java中找答案。ActivityThread是App的主线程,所有UI操作都在这个线程上执行。如果这个线程上有耗时操作(比如网络请求、文件读写、复杂计算),就会导致掉帧。
// 在ActivityThread.java中,主线程的消息循环
final class ActivityThread {
Looper mLooper = Looper.myLooper();
// 主线程的消息处理
void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + code);
switch (msg.what) {
case LAUNCH_ACTIVITY: {
// 启动Activity
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
break;
}
case HANDLE_PAUSE_ACTIVITY: {
// 暂停Activity
handlePauseActivity(...);
break;
}
// ... 其他消息
}
}
}
如果某个case分支里的代码执行时间过长,就会阻塞主线程,导致掉帧。
6.3 一个具体的优化案例
让我给你讲一个真实的故事。我之前帮一个做短视频App的团队优化启动速度,他们的问题是:冷启动需要8秒,从按下图标到看到视频列表。
我们用上面的方法一步步排查:
- 用
am start -W测量:总时间8234ms,Activity显示耗时6120ms - 查看SystemServer日志:SystemServer启动正常,不是系统服务的问题
- 查看App日志:发现
Application.onCreate()耗时4500ms - 深入
Application.onCreate():发现里面有一个SDK的初始化,这个SDK会读取一个很大的本地配置文件(2MB的JSON文件) - 解决方案:把配置文件的读取改为懒加载,只在实际需要时才加载
改完之后,冷启动时间降到了3.2秒,提升了60%以上。
这个案例告诉我们:知道了启动流程,就能精准定位瓶颈在哪里。如果你不知道启动流程,可能会盲目地优化各种地方,最后发现改了很多代码,效果却微乎其微。
七、一些实用的调试技巧
7.1 用dumpsys查看系统状态
dumpsys是Android系统中最强大的调试工具之一。它可以查看几乎任何系统服务的状态:
# 查看所有服务的列表
adb shell dumpsys | grep -v "DUMP OF SERVICE"
# 查看AMS的状态(最有用!)
adb shell dumpsys activity
# 查看WMS的状态
adb shell dumpsys window
# 查看PackageManager的状态
adb shell dumpsys package
# 查看内存使用情况
adb shell dumpsys meminfo
这些输出内容非常多,你可以把输出重定向到文件,然后用编辑器慢慢分析:
adb shell dumpsys activity > activity_dump.txt
adb shell dumpsys window > window_dump.txt
7.2 用trace工具分析性能
Android提供了多种trace工具:
# CPU trace(需要root)
adb shell cmd trace start -z mytrace 10
adb shell cmd trace stop
# 查看GPU trace
adb shell setprop debug.hwui.profile true
adb shell am start -W com.example.myapp/.MainActivity
生成的trace文件可以用Android Studio的Perfetto工具打开,非常直观。
7.3 用systrace做全流程分析
Systrace是Google开发的一个性能分析工具,可以记录CPU、GPU、I/O等各个维度的时间线:
# 下载systrace工具(需要Python环境)
python systrace.py -o mytrace.html -t 10 sched freq idle am wm gfx view binder_driver dalvik
# 打开生成的HTML文件
open mytrace.html
这个HTML文件里有完整的时间线,你可以看到每个进程在每个时间点在做什么。对于分析卡顿问题,systrace是最有效的工具之一。
八、最后的话——学习源码的正确姿势
我知道这篇文章信息量很大,你可能觉得看不下去。但我想说的是:源码学习不是一蹴而就的。
我当初也是从编译源码开始,第一次编译花了将近一天时间,中间报错了几十次。但当我第一次看到自己编译的系统跑起来的时候,那种成就感是任何东西都替代不了的。
学习源码的几个建议:
- 不要试图一次看懂所有内容。先搞清楚整体架构,再深入某个具体的模块
- 边看边动手。看到一段代码,就去IDE里打断点,看看实际运行时的情况
- 遇到问题先自己查。Google、StackOverflow、源码中的注释,都能帮你解决问题
- 写下来。把学到的东西用自己的话写出来,既能加深理解,也能帮助其他人
最后,送你一句话:“当你开始读源码的时候,你就已经超过90%的Android开发者了。” 因为他们还在用框架,而你已经知道框架是怎么实现的了。
加油!有问题随时来找我。
