Android系统开发入门-14.Android studio 导入系统源码

目前流行的阅读 Android 系统源码的工具主要有以下几种: source insight, understand, vscode, vim 等。也有同学不用导工程直接 find + grep + 文本编辑器的, 这些工具我也都用过很长一段时间, 但总有不满意的地方。后来 Android studio 发布了 3.0 版本, 开始支持 NDK 开发了,体验了一下之后,就觉得这功能不就是我想要的嘛,同时写 java 和 c++ 代码。于是开始各种折腾,折腾了几个星期后,终于找到一种将 Android系统源码导入到 Android studio 的方法,使用 gradle + cmake + NDK 支持同时解析 java 和 c++ 代码。理论上来说支持任何 java, c++ 工程或者混合工程。这种方法是我个人原创的,跟官方的使用 ideagen 生成工程不一样。使用 ideagen 只能导入 java 代码,c++ 代码没法解析。但这种方法对电脑的性能要求比较高,最好满足以下条件:

    1. 代码要下载到本机,而不是通过 samba 挂载。
    1. 代码最好是下载到 ssd,而不是机械硬件。
    1. 内存最好 32G 以上。
    1. Linux 系统, Windows 总会有各种乱七八糟的问题。

如果不满足以上条件,可能在查看,修改代码过程中会有卡顿出现。满足条件的话,则是飞一般的感觉。
下面就来演示一下源码导入过程,基于 Android studio 版本 3.4.1。

1. 准备工作

如果需要解析C/C++代码的话,则需要安装 cmake, ndk。
打开 SDKManager 选择 SDK Tools, 勾选 cmake 和 NDK 然后 install 即可。
SDKManager

2. 新建顶层 build.gradle

在 Android 根目录新建一个 build.gradle 文件:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.2'
}
}

allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
google()
jcenter()
}
}

apply plugin: "idea"
idea {
module {
excludeDirs = [
file(".repo"),
file("abi"),
file("art"),
file("bionic"),
file("bootable"),
file("build"),
file("cts"),
file("dalvik"),
file("developers"),
file("development"),
file("docs"),
file("external"),
file("hardware"),
file("kernel"),
file("libcore"),
file("libnativehelper"),
file("ndk"),
file("out"),
file("packages"),
file("pdk"),
file("platform_testing"),
file("prebuilts"),
file("sdk"),
file("system"),
file("test"),
file("toolchain"),
file("tools")
]
}
}

这个文件与普通的 Android 应用的顶层 build.gradle 文件的差别就是多加了一个 apply plugin: “idea”, 然后在配置 idea 插件的 excludeDirs。即忽略哪些不目录。这些目录被忽略之后,Android studio 就不会扫描分析里面的文件了。因为 Android 源码非常庞大, 如果没有配置 excludeDirs 的话,导入工程可能得导一整天了。一般来说我们只需要关注很少的一部分代码而已,我们只需要保留我们要关注的代码目录即可,其他目录都加到 excludeDirs 里面。

3. 导入 java 模块

一般情况下我们关注的是 frameworks/base 目录下的源码而已, 因此我们在 frameworks/base 目录下创建一个 build.gradle 文件

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
33
34
35
36
37
38
39
apply plugin: 'java'
sourceSets {
main.java.srcDirs += 'core/java'
main.java.srcDirs += 'graphics/java'
main.java.srcDirs += 'keystore/java'
main.java.srcDirs += 'location/java'
main.java.srcDirs += 'lowpan/java'
main.java.srcDirs += 'media/java'
main.java.srcDirs += 'media/apex/java'
main.java.srcDirs += 'opengl/java'
main.java.srcDirs += 'sax/java'
main.java.srcDirs += 'services/accessibility/java'
main.java.srcDirs += 'services/appprediction/java'
main.java.srcDirs += 'services/appwidget/java'
main.java.srcDirs += 'services/autofill/java'
main.java.srcDirs += 'services/backup/java'
main.java.srcDirs += 'services/companion/java'
main.java.srcDirs += 'services/contentcapture/java'
main.java.srcDirs += 'services/contentsuggestions/java'
main.java.srcDirs += 'services/core/java'
main.java.srcDirs += 'services/coverage/java'
main.java.srcDirs += 'services/devicepolicy/java'
main.java.srcDirs += 'services/midi/java'
main.java.srcDirs += 'services/net/java'
main.java.srcDirs += 'services/print/java'
main.java.srcDirs += 'services/restrictions/java'
main.java.srcDirs += 'services/robotests/java'
main.java.srcDirs += 'services/startop/java'
main.java.srcDirs += 'services/systemcaptions/java'
main.java.srcDirs += 'services/usage/java'
main.java.srcDirs += 'services/usb/java'
main.java.srcDirs += 'services/voiceinteraction/java'
main.java.srcDirs += 'services/java'
main.java.srcDirs += 'telecomm/java'
main.java.srcDirs += 'telephony/java'
main.java.srcDirs += 'wifi/java'
main.java.srcDirs += '../../out/soong/.intermediates/frameworks/base/framework/android_common/gen/aidl/frameworks/base/core/java'
main.java.srcDirs += '../../out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/gen/aapt2/R'
}

4. 导入 c++ 模块

上面的方法只能解析 java 代码而已, 做系统层的开发经常是要接触到 c++ 代码的, 若是有一个强大的 ide 的辅助便能省心不少。下面就演示一下导入 c++ 模块的方法。以 recovery 模块为例, 在 bootable目录下新建一个 build.gradle 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apply plugin: 'com.android.application'

android {
compileSdkVersion 29
defaultConfig {
applicationId "com.android.recovery"
minSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version "3.10.2"
}
}
}

再新建一个 CMakeLists.txt 文件:

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
cmake_minimum_required(VERSION 2.6)
project(recovery)
set(CMAKE_CXX_STANDARD 11)

include_directories(../system/core/base/include)
include_directories(../system/core/libcutils/include)
include_directories(../hardware/interfaces/health/2.0/utils/libhealthhalutils/include)
include_directories(../system/core/libziparchive/include)
include_directories(../system/core/liblog/include)
include_directories(../external/selinux/libselinux/include)

include_directories(recovery/applypatch/include)
include_directories(recovery/bootloader_message/include)
include_directories(recovery/edify/include)
include_directories(recovery/fuse_sideload/include)
include_directories(recovery/install/include)
include_directories(recovery/minui/include)
include_directories(recovery/otautil/include)
include_directories(recovery/recovery_ui/include)
include_directories(recovery/update_verifier/include)
include_directories(recovery/updater/include)

FILE(GLOB_RECURSE recovery_cpp_list "./*.cpp")
add_library(recovery ${recovery_cpp_list})
set_target_properties(recovery PROPERTIES LINKER_LANGUAGE CXX)

在 bootable 目录下新建 src/main 目录, 在此目录下新建 AndroidManifest.xml 文件:

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.recovery">
</manifest>

这个文件对于我们来说并没有什么用, 但是 gradle 的 android 插件要求要有这么一个文件,不然会报错, 我们创建这么个文件,简单声明一下 package 信息即可,其他内容都不需要。

5. settings.gradle 配置子模块

在 Android 源码根目录下新建 settings.gradle 文件

1
2
include ":frameworks:base"
include ":bootable"

把我们前面添加的两个模块加进来。

6. 使用 Android studio 打开工程

使用 Android studio file –> open 打开 build.gradle 文件,选择 open as a project。如果网络好的话,这一步应该几分钟就OK了。网络慢的话,可能要卡在下载 gradle 依赖这里很久,耐心等待便是。第一次打开耐心等待很长时间,才能解析完代码。这个得看网络速度,电脑性能,我自己的电脑十几分钟就解析完了,公司的电脑得半个多小时。待解析完代码后,打开 framework/base 下面的代码和 recovery 下面的代码,发现都可以跳转了。而且那些被配置为 excludeDirs 的目录应该变成了黄色的,表示这些目录下的文件不会被索引,下次再打开工程时速度就快很多了。