现在学习 Android 不像早些年,会写个 App 就能找到饭碗,如今不看看 Framework 源码有种分分钟掉队的感觉。虽然现在有一些比较优秀的站点提供源码阅读,但是毕竟在浏览器里面阅读,还是会遇到很多限制,最好还是自己下载一份。说道下载问题就来了,AOSP 的仓库被墙了,所以直接不能直接下载,或者说直接下载网速会很慢,还好国内有一些镜像服务可以避免这一问题。

之前用 Ubuntu 直接挂个 SS 就能以 2M/s 速度同步源码,但是最近在 Mac 就死活没得办法了。这里我们使用 清华 的源,不过它给的帮助文档似乎并不能直接照搬。

准备工作

准备一块至少 100G 空闲的磁盘作为下载盘(大一点准没错,强烈建议下载到移动硬盘里)。然后 AOSP 编译环境要求 大小写敏感(Case-Sensitive) ,所以先格式化:

然后在这个磁盘(分区)上执行:

1
2
$ mkdir AOSP && cd AOSP
$ mkdir android-9.0.0_r1

我们这次下载的是 Android 9.0.0 的源码,所以目录就命名成这样,你也可以选择你喜欢的名字!

下载 repo 工具

整个 AOSP 还是非常巨大的,所以就会把整个项目拆分成许多的版本管理仓库,这么多仓库需要通过工具来进行管理,也就是这里要下载的 repo 工具。执行命令:

1
2
3
$ cd AOSP
$ curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o repo
$ chmod a+x repo # 添加可执行权限

因为不太喜欢入侵性太强的配置,就不按照官网的配置来弄了,等会直接通过相对路径来引用 repo

这个时候 repo 还是从 Google 的站点进行下载,所以还要简单修改一下。使用文本编辑器打开 repo,然后将开头的 REPO_URL 字段值替换成 https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/

初始化 repo

默认情况下,通过 repo 同步的将会是整个 AOSP 源码,即各种版本的源码都有,这里我们通过 -b 来指定 Android 9.0.0 分支。其他可选分支

然后执行:

1
2
$ cd android-9.0.0_r1
$ ../repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-9.0.0_r1

终端可能会输出一两句警告信息,忽略就好了。

开始下载

初始化工作已经完成,可以直接开始下载源码了:

1
2
$ cd android-9.0.0_r1
$ ../repo sync

一般来讲,接下来的几小时至一整晚的时间内可以不鸟它了,但是下载可能会因为某些问题而中断,所以还要时不时瞄一眼,所以我们写个重试的脚本 sync.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
time=1
while :
do
../repo sync
if [ $? == 0 ]
then
break
else
time=`expr $time + 1`
echo 'sync failed retry!!'
fi
done
echo 'tried '${time}' times'

$? 中保存的是上一条名命令的执行结果状态,0 表示成功,not 0 表示失败,所以直接简单粗暴地开个死循环,每次执行 ../repo sync 之后判断状态,然后决定是否要重试。不过这并不能完美解决我们的问题,因为有时失败后需要删除一个 *.lock 文件,否则还会在相同的地方失败;并且这个脚本一旦开启只有下载成功才会终止,Ctrl+C 也不能终止,只能杀死进程。
如果网络条件不是很好的话,建议挂晚上下载。

导入 AS

源码下载下来后,因为我们只是想阅读源码,所以不用按照官网的上的指引直接执行 make ,那样会导致编译整个工程,等不起。

执行下列命令:

1
2
3
$ cd android-9.0.0_r1
$ make idegen
$ development/tools/idegen/idegen.sh

执行第一条命令会有如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=9
TARGET_PRODUCT=aosp_arm
TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE=release
TARGET_ARCH=arm
TARGET_ARCH_VARIANT=armv7-a-neon
TARGET_CPU_VARIANT=generic
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=darwin
HOST_OS_EXTRA=Darwin-18.0.0-x86_64-10.14
HOST_BUILD_TYPE=release
BUILD_ID=PPR1.180610.009
OUT_DIR=out
============================================
[1/1] out/soong/.minibootstrap/minibp out/soong/.bootstrap/build.ninja
[55/56] glob prebuilts/ndk/stl.bp
[77/77] out/soong/.bootstrap/bin/soong_build out/soong/build.ninja
out/build-aosp_arm-cleanspec.ninja is missing, regenerating...
out/build-aosp_arm.ninja is missing, regenerating...
[2/934] including art/Android.mk ...
art/build/Android.common.mk:50: warning: unsupported HOST_ARCH=x86_64
[564/934] including system/sepolicy/Android.mk ...
system/sepolicy/Android.mk:79: warning: BOARD_SEPOLICY_VERS not specified, assuming current platform version
[934/934] including tools/tradefederation/core/Android.mk ...
art/build/Android.gtest.mk:121: warning: overriding commands for target `Uncompressed'
art/build/Android.gtest.mk:101: warning: ignoring old commands for target `Uncompressed'
[ 99% 919/920] glob tools/tradefederation/core/python-lib/tradefed_py/*.py
[100% 38/38] Install: out/host/darwin-x86/framework/idegen.jar

这里输出了一些配置信息,然后后面做了一些预处理,这里有个新词 ninja ,这是一个新的构建工具,关于它和 Makefile 的区别,可以看 这里

执行第二条命令会输出:

1
2
Read excludes: 77ms
Traversed tree: 146534ms

执行完之后,会在工程根目录产生 android.iprandroid.iml 文件,然后就可以使用 AS 打开 android.ipr 文件来导入工程了。

如果 macOS 出现了下面的错误:

1
2
internal error: Could not find a supported mac sdk: ["10.10" "10.11" "10.12" "10.13" "10.14"]
internal error: Could not find a supported mac sdk: ["10.10" "10.11" "10.12" "10.13" "10.14"]

其实只是编译工具里没有明确支持说明 macOS 版本:

1
2
3
ls /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs
# macOS 10.15 上有如下输出
# DriverKit19.0.sdk MacOSX.sdk MacOSX10.15.sdk

查看 build/soong/cc/config/x86_darwin_host.go#darwinSupportedSdkVersions 定义发现没有 10.15:

1
2
3
4
5
6
7
darwinSupportedSdkVersions = []string{
"10.10",
"10.11",
"10.12",
"10.13",
"10.14",
}

在后面加上 10.15 就可以了。

如果并不是想看整个 AOSP 的代码,可以在执行这两条命令之前把自己想要排除的目录或者文件添加到 development/tools/idegen/excluded-paths 中,比如我就把 packages 文件夹忽略了,更多的信息可以看一下 development/tools/idegen/README 文件。
编译环境方面,一般来讲新版本 AOSP 和最新的 Mac/Linux 系统版本都不会出现环境问题,但是在出现新系统编译旧 AOSP 代码就极有可能出现一些环境错误,比如需要安装 JDK6。这个时候莫要慌,如果不介意在电脑上装一些环境可以按照错误来配置,介意的话,可以装个 Ubuntu 虚拟机来编译,或者更轻量的 Docker

如果是 4.4 的源码,可以使用我构建好的镜像进行编译: docker run --rm -v /path/to/aosp/source:/android-4.4 dashmrl/aosp-builder-4.4

AS 阅读体验

AS 首次打开这么一个工程真的非常耗时,主要是在做一些 index 操作,我的 MBP15 花了 20min 才完成,所以没有下定决定看源码之前,就不要打开这个工程了,然后也不要轻易关闭这个工程QAQ…

还有可能会遇到 OOM 的问题,可以看看 这里

工程导入后还会遇上一些其他问题,如果发现 IDE 提示文件行数太大、大小写,可以配置一下 IDE 属性,选择 Help 顶部菜单,然后选择 Edit Custom Properties,然后填入:

1
2
3
# 不够就 100000
idea.max.intellisense.filesize=10000
idea.case.sensitive.fs=true

这时候还有可能会出现点击某个类,但是跳转到类 SDK 代码里,而不是 AOSP 的代码,所以这里在配置一下 SDK。
选择 File->Project Structure,然后选中 Module:

选择对应 Module SDK,这里 Android P 就选 28,然后按照图中删除其余的包,保存。

这时候有一些类或方法还是红的不能跳转,大部分原因是因为有 @hide  注释,不过影响也不大。