想要自己 debug 系统源码,或者有定制 Android 系统的需求可以参考本文利用 android 13 在 ubuntu 尽情的折腾。
Android 13 源码全量下载与编译
重要提示:自 2021 年 6 月 22 日起,AOSP 不再支持在 MacOS 上进行平台开发。
Ubuntu 环境准备:source.android.com/docs/setup/...
arduino
sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig
DOWN 13.0 源码(时间会比较长):
bash
mkdir AOSP/android13 && cd AOSP/android13
repo init -u https://android.googlesource.com/platform/manifest -b android-13.0.0_r49 && repo sync -c -j16
指定编译产物架构 & 编译(11th Gen Intel® Core™ i7-11700K @ 3.60GHz × 16 在 两个小时左右)
编译过某个架构的源码之后,其他平台时间会减少,不同架构的产物不会覆盖,都在磁盘上
ruby
source build/envsetup.sh //初始化编译环境,包括后面的lunch和make指令都在这个脚本里面
lunch sdk_phone_x86_64 // //指定此次编译的目标设备以及编译类型,了解更多架构可以直接输入 lunch 指令
m -j16 // //开始编译,默认为编译整个系统,其中 -j16 代表的是编译的 job 数量为16。
使用模拟器运行
emulator

源码导入到 AS 查看
源码导入 AS 有两种方式:
一种查看整个 AOSP ,通过 idegen 生成整个工程文件,然后用 AS 打开。
另一种只想打开某个模块而非全量的 AOSP,例如 music,可以通过 aidegen 直接从命令行启动 AS。
下面是两种用法:
整个 AOSP 全部导入:
生成 IDE 相关文件
idegen 专门为 IDE 环境调试源码而设计的工具, 在 AOSP/android13 目录(个人源码路径)下依次执行如下命令:
bash
soruce build/envsetup.sh
mmm development/tools/idegen/
./development/tools/idegen/idegen.sh
以上3个步骤的含义依次如下:
vbnet
Step 1: 用于初始化环境变量
Step 2: 生成文件out/host/linux-x86/framework/idegen.jar
Step 3: 源码根目录生成文件android.ipr(工程相关设置), android.iml(模块相关配置)
之后在根目录可以看到生成的相关配置文件:
sql
➜ android13 ls
Android.bp bootstrap.bash device out test
android.iml build external packages toolchain
android.ipr BUILD frameworks pdk tools
android.iws cts hardware platform_testing WORKSPACE
art dalvik kernel prebuilts
bionic developers libcore sdk
bootable development libnativehelper system
排除部分无用目录(可选)
因为源码结构非常庞大并且有些模块是不需要导入的,这个时候可以先编辑 android.iml
文件,增加一些不需要导入模块。先搜索 excludeFolder
(表示不需要导入的文件夹),可以在后面可以加上:
ini
<excludeFolder url="file://$MODULE_DIR$/.repo"/>
<excludeFolder url="file://$MODULE_DIR$/external/bluetooth"/>
<excludeFolder url="file://$MODULE_DIR$/frameworks/base/docs"/>
<excludeFolder url="file://$MODULE_DIR$/out/host"/>
<excludeFolder url="file://$MODULE_DIR$/prebuilt"/>
然后再进行下一步
或者如果你已经打开 AS 导入工程了,也可以进入目录Project Structure
-> Modules
, 可快速去除某些模块, 其中红色代码 Exclueded 选项(即代表排出的目录),先点击某个具体文件夹,然后点击上面的 Excluded

之后点击下方 apply -> ok
AS 启动
打开Android Studio, 点击File
-> Open
,选中前面生成的 android.ipr 文件即可, 该过程 AS 会对项目进行 sync 比较耗时。
注意下面提示在 index 的时候可能会比较卡,是正常的,耐心等完即可。

在 IDE 中直接查看一些类,例如 Activity,里面的代码跳转已经 OK。
如果这一步 AS 卡死了,可以使用杀掉所有 java 进程
killall -KILL java
单独模块导入,以 Music 为例
整个 AOSP 导入比较耗时,如果我们只想单独查看某个系统模块,可以使用 aidegen 指令,例如 Music 可以通过如下步导入 AS:
将 AS 添加到命令行启动
因为 aidegen 需要从命令行启动 studio,所以需要先配置 studio 的环境变量,否则后面执行命令会报错
打开 AS 点击如下图所示位置:

之后在命令行可以直接输入 studio
指令启动某个项目,确保成功打开 AS 再进行下一步

如果是通过 jetbrain toolbox 安装的 studio 点击上面的按钮可能不会生效,这里我直接列下我的做法(不一定是最合理的)

打开这个路径可以看到一个 Shell 脚本,cat 下面的 studio 内容,把 studio.sh
这个文件的路径添加到环境变量
bash
(base) ➜ android13 cat /home/nayuta/.local/share/JetBrains/Toolbox/scripts/studio
#!/bin/bash
# Generated by JetBrains Toolbox 1.28.1.15219 at 2023-05-23T16:22:19.981325368
"/home/nayuta/.local/share/JetBrains/Toolbox/apps/AndroidStudio/ch-0/222.4459.24.2221.9971841/bin/studio.sh" "$@"
使用 aidegen 直接打开模块工程
aidegen 依然是在 envsetup
脚本中,所以需要提前配置环境:
arduino
source build/envsetup.sh //初始化编译环境,包括后面的lunch和make指令都在这个脚本里面
lunch sdk_phone_x86_64 // //指定此次编译的目标设备以及编译类型,了解更多架构可以直接输入 lunch 指令
然后执行 aidegen,添加对应想要打开的工程路径,例如 Music
bash
aidegen packages/apps/Music -i s
这里 i
是 IDE
的意思,s
代表 Android Studio。
AIDEGen 会自动帮你把对应的模块编译一遍,顺带把梳理出的依赖用 Python 生成一个个的 dependency
,最后直接帮你把 AS 拉起,项目自动打开。下面简单截取一下打开后的目录结构:

AIDEGen 会自动把 AOSP 带的库作为依赖涵盖到 dependency
里面。
现在试一下,任意打开一个类,点击头部的 import,可以顺利跳转到 framework/base
或者其它依赖的模块下。
但有些已知的问题:
- 使用 xml 写布局的时候属性报红而且不能智能提示,而 idegen 就没有这个问题
源码目录结构:
frameworks/
:包含 Android 系统框架的代码。
libcore/
:包含 Android 核心库 (Libcore) 的代码。
packages/
:包含 Android 系统内置应用程序和服务的代码。
sdk/
:包含 Android 软件开发工具包 (SDK) 的代码和工具。
system/
:包含 Android 系统服务和应用程序的代码。
hardware/
:包含 Android 硬件抽象层 ( HAL ) 的代码。
kernel/
:包含 Linux 内核 的代码。
art/
:包含 Android 运行时 (ART) 的代码。
bionic/
:包含 Android C 库 (Bionic) 的代码。
bootable/
:包含 Android 启动相关的代码。
build/
:包含 Android 编译系统的代码和配置文件。
cts/
:包含 Android 兼容性测试套件 (CTS) 的代码和测试用例。
dalvik/
:包含 Dalvik 虚拟机的代码。
development/
:包含 Android 开发工具和示例代码。
device/
:包含设备树和硬件相关的代码。
docs/
:包含 Android 系统文档和开发者指南。
external/
:包含 Android 系统使用的外部开源项目的代码。
libnativehelper/
:包含 Android JNI 帮助库的代码。
ndk/
:包含 Android Native Development Kit (NDK) 的代码和工具。
out/
:包含 Android 编译系统生成的临时文件和输出文件。
pdk/
:包含 Android 兼容性测试套件 (CTS) 的 Java API。
prebuilts/
:包含预编译的二进制文件和工具链。
tools/
:包含 Android 开发工具和实用程序的代码。
编译产物信息:
编译后的产物,都位于/``out
目录,该目录下主要关注下面几个目录:
/out/host:Android开发工具的产物,包含SDK各种工具,比如adb,dex2oat,aapt等。

/out/target/common:通用的一些编译产物,包含Java应用代码和Java库;
/out/target/product/[product_name]:针对特定设备的编译产物以及平台相关C/C++代码和二进制文件;
在/out/target/product/[product_name]目录下,有几个重量级的镜像文件:
system.img:挂载为根分区,主要包含Android OS的系统文件;
ramdisk.img:主要包含init.rc文件和配置文件等;
userdata.img:被挂载在/data,主要包含用户以及应用程序相关的数据;

/out/target/product/emulator64_x86_64/product/priv-app/ 系统相关 app apk


代码搜索指令:
可以使用下方指令分类搜索代码,其本质还是 grep 指令
搜索指令 | 解释 |
---|---|
cgrep | 所有C/C++文件执行搜索操作 |
jgrep | 所有Java文件执行搜索操作 |
ggrep | 所有Gradle文件执行搜索操作 |
mangrep [keyword] | 所有AndroidManifest.xml文件执行搜索操作 |
mgrep [keyword] | 所有Android.mk文件执行搜索操作 |
sepgrep [keyword] | 所有sepolicy文件执行搜索操作 |
resgrep [keyword] | 所有本地res/*.xml文件执行搜索操作 |
sgrep [keyword] | 所有资源文件执行搜索操作 |
单独模块编译(以 launcher 举例)
编译指令
编译指令 | 解释 |
---|---|
m | 在源码树的根目录执行编译 |
mm | 编译当前路径下所有模块,但不包含依赖 |
mmm [module_path] | 编译指定路径下所有模块,但不包含依赖 |
mma | 编译当前路径下所有模块,且包含依赖 |
mmma [module_path] | 编译指定路径下所有模块,且包含依赖 |
make [module_name] | 无参数,则表示编译整个Android代码 |
下面列举部分模块的编译指令:
模块 | make命令 | mmm命令 |
---|---|---|
init | make init | mmm system/core/init |
zygote | make app_process | mmm frameworks/base/cmds/app_process |
system_server | make services | mmm frameworks/base/services |
java framework | make framework | mmm frameworks/base |
framework资源 | make framework-res | mmm frameworks/base/core/res |
jni framework | make libandroid_runtime | mmm frameworks/base/core/jni |
binder | make libbinder | mmm frameworks/native/libs/binder |
上述mmm命令同样适用于mm/mma/mmma,编译系统采用的是增量编译,只会编译发生变化的目标文件。当需要重新编译所有的相关模块,则需要编译命令后增加参数-B
,比如make -B [module_name],或者 mm -B [module_path]。
launcher3 修改与编译安装
修改系统中的 launcher3 代码,加了一行 Log

Cd 到这个模块执行 mm
指令
bash
cd ~/AOSP/android13/packages/apps/Launcher3
mm
编译出来的产物位于 out/target/product/emulator_x86_64/system_ext/priv-app/Launcher3/Launcher3.apk
bash
➜ out find |grep Launcher3.apk
./soong/.intermediates/packages/apps/Launcher3/Launcher3/android_common/Launcher3.apk
./target/product/emulator_x86_64/system_ext/priv-app/Launcher3/Launcher3.apk
./target/product/emulator_x86_64/testcases/Launcher3Tests/Launcher3.apk
然后通过 adb 安装:
bash
➜ out adb install -r./target/product/emulator_x86_64/system_ext/priv-app/Launcher3/Launcher3.apk
观察日志:
yaml
05-19 16:28:53.297 18274 18274 V Activity: onCreate djd com.example.bruno.MainActivity@709e5b8: null
05-22 11:59:12.339 19134 19134 V Activity: onCreate djd com.android.launcher3.Launcher@c57b3d0: null
05-22 11 : 59 : 12.896 19134 19134 V Launcher3 djd: onResume
05-22 11 : 59 : 12.914 19134 19134 V Launcher3 djd: onResume
05-22 11:59:12.916 19134 19134 V Launcher3 djd: onResume
05-22 11:59:14.307 19134 19134 V Launcher3 djd: onResume
05-22 11:59:14.607 19134 19134 V Launcher3 djd: onResume
05-22 11:59:19.779 19134 19134 V Launcher3 djd: onResume
PS:对于一个应用从代码编写到点击启动,两篇内容 [WIP]一个 Apk 的安装之旅 & [WIP]一个 App 的启动之旅
系统调试
系统源码修改与编译运行
对于项目源码可以直接修改,例如在 Activity 启动的时候增加日志:
less
// frameworks/base/core/java/android/app/Activity.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
Slog.v(TAG, "onCreate djd " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
如果后面需要修改系统 App,也可以直接在对应的文件中修改例如 launcher:
packages/apps/Launcher3/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
这个时候可以按照上面的流程完整编译一次项目,或者单独编译某个模块:
- 整编流程:
然后同样的按照上面编译的步骤编译项目,这个时候是增量编译,但我的设备上还是花了 15 分钟左右
ruby
source build/envsetup.sh //初始化编译环境,包括后面的lunch和make指令都在这个脚本里面
lunch sdk_phone_x86_64 // //指定此次编译的目标设备以及编译类型,了解更多架构可以直接输入 lunch 指令
m -j16 // //开始编译,默认为编译整个系统,其中-j12代表的是编译的job数量为16。
emulator // 运行模拟器
等到系统运行之后,可以在日志中看到我们自定义的内容:
看起来系统启动就两个 Activity:QuickstepLauncher 和 FallbackHome
kotlin
➜ AOSP adb logcat |grep djd
05-18 16:02:24.066 988 988 V Activity: onCreate djd com.android.settings.FallbackHome@cce6b58: null
05-18 16:02:41.320 1256 1256 V Activity: onCreate djd com.android.launcher3.uioverrides.QuickstepLauncher@729c38b: null
断点调试系统代码(Java)
这里的断点调试仍然基于 AS 自带的 debug 工具,而这个工具是以进程为最小调试粒度(每次只能 debug 一个进程,多了还没试过)。如果想断点系统类服务,可以直接下断点,然后选择系统进程调试:
断点系统服务
- 找到需要调试的文件,添加断点

- 点击 debug 按钮(图中鼠标的位置),选择
system_process
进程(注意如果打开之后窗口是空的,需要把Show all processes
勾上 )

如果 attach 成功了之后,一般刚才的断点上会有个小勾

- 这里下的断点是启动 Activity,所以在任意 app 打开一个页面即可触发

调试系统 App
调试系统 app 和系统服务一样,只不过在选择进程的时候选择需要调试的进程,以 Settings 为例:
先在它的 BaseActivity 中下断点(注意看这个时候断点没有打勾的标记)

然后运行 Setting 应用在 IDE attach 到 setting 进程

断点显示可用:

在 Setting 中启动任意页面,触发断点:

参考:
developer.sony.com/develop/ope...