移动开发
深入理解Android:Java虚拟机ART
邓凡平 著
第1章
本书必读
1.1 概述
笔者写书向来是最后才写第一章。此时,全书的主体内容已完全确定,笔者在学习ART虚拟机以及编撰本书的过程中所遇到的问题、总结的经验和教训等才可以完整地汇总并分享给各位读者。所以,本章是全书的点睛之笔,为必读章节。并且,我相信随着读者阅读的深入,还会时常回顾本章。
总体来说,本书并不简单。其实,从本书的目标—Java虚拟机也可以想得到,对Java应用程序来说,虚拟机就算是操作系统了。哪一本讲操作系统的书会简单呢?
具体到Android ART虚拟机来说,本书以Android 7.0为参考,绝大部分待分析的源代码位于art目录中。
□包含C++代码1071个文件。其中,.cc文件中包含236 744有效代码行(即不算注释及空行),.h文件中包含74 710有效代码行。
□包含汇编文件1704个文件,覆盖x86、arm、mips的32位和64位6个CPU平台,有效代码共19 955行。
也就是说,我们的ART虚拟机是一个有着30多万行代码的庞然大物。针对这样一个复杂的系统,要想从一个对它略知一二的初学者成长为一个能品头论足甚至指点江山的熟练者,这一路的学习历程必然不会轻松。
接下来,笔者将介绍阅读本书时必须准备的工具。磨刀不误砍柴工,建议读者先把这些工具准备好之后再开始后面的学习。
1.2 准备环境和工具
为了更好学习ART,读者要准备好如下的环境或工具。
1.2.1 准备源代码
首先,我们需要一份Android 7.0的源代码。笔者在百度云盘上提供了本书所需的资料下载。读者也可以到清华大学开源软件镜像站按照网页里的说明下载。其官网地址为https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/。笔者总结其下载步骤如下。
源代码的量很大,读者需要有一个浏览它的工具,Source Insight是不二之选。下面我们来看看如何配置它。
1.2.2 准备Source Insight
Source Insight是阅读源码的必备工具,它是一个Windows软件,在Linux平台上可通过wine进行安装。
提示:Source Insight推出3.5版本之后,很长一段时间都没有更新。最近推出了全新的4.0版本。但经过笔者测试,4.0版本的Source Insight在Linux上表现不稳定,建议读者在Linux上使用3.5版本的Source Insight。下面的讲解也以3.5版本的Source Insight为主。
首先,打开Source Insight,通过菜单项Project→New Project新建一个源码工程。工程可建立在Android 7.0源码根目录。笔者存放的位置是~/workspace/aosp/android-7.0,工程名为android-7.0。
接下来我们要先设置源码文件的后缀名。在ART中,C++的实现文件以.cc为文件后缀名。而汇编源码存储在以.S为后缀的文件里。Source Insight默认的配置不识别.cc和.S为后缀的源码文件,所以我们需要修改它。
单击菜单项Options→Document Options,弹出图1-1所示的文件类型对话框。
图1-1用于为C++源码添加.cc结尾的文件类型。接着还要为汇编源码做类似的处理,来看图1-2。
接下来我们为android-7.0工程添加具体的源码文件。单击菜单项Project→Add and Remove Project Files,弹出工程文件选择对话框,如图1-3所示。
请读者添加如下目录到android-7.0工程中。
□art目录(通过图中的Add Tree按钮可添加整个目录):ART虚拟机源码文件。
□libcore目录:包含JDK相关源码文件。
□libnativehelper目录:包含JNI相关源码,如jni.h等。
□frameworks/base/cmds/am、frameworks/base/core、frameworks/base/include三个目录:包含Zygote相关源码文件。
另外,上述目录中还有很多用于测试的源码文件,数量非常多。由于它们对本书的学习并无影响,建议读者移除其中test目录下的源码文件—通过图1-2中的Remove Tree可移除指定目录中的源码。比如art/test包含的1800多个源码文件都可以移除。
接着要进一步配置Source Insight。ART是一个复杂系统,所以谷歌用了一些工具来辅助编写正确的源码。这些工具要求在源码函数声明、变量定义等地方使用一些特殊的宏,而Source Insight不认识这些宏,所以很多函数、变量都无法解析和识别。为此,我们需要配置Source Insight,让它忽略这些宏。配置方法下面将详细介绍。
首先,找到Source Insight的C.tom文件,它位于~/.wine/drive_c/Program Files (x86)/Source Insight 3/下。打开该文件,在文件末尾添加如下的内容。
[C.tom文件]
;C.tom是C Token Macros的意思,用于重定义C/C++文件中的宏
;下面的条目都是ART源码中出现的宏,我们将它们定义为空,这样,Source Insight碰到这些宏
;时就会忽略它们
SHARED_TRYLOCK_FUNCTION(...)
ACQUIRE_SHARED()
EXCLUSIVE_TRYLOCK_FUNCTION(...)
SCOPED_CAPABILITY
SHARED_REQUIRES(...)
REQUIRES(...)
UNLOCK_FUNCTION(...)
ASSERT_SHARED_CAPABILITY(...)
ASSERT_CAPABILITY(...)
__noreturn
__mallocfunc
EXCLUSIVE_LOCKS_REQUIRED(...)
LOCKS_EXCLUDED(...)
SHARED_LOCKS_REQUIRED(...)
SHARED_LOCK_FUNCTION(...)
DEFAULT_MUTEX_ACQUIRED_AFTER
ACQUIRE(...)
ACQUIRE()
RELEASE()
RELEASE_SHARED()
ACQUIRED_AFTER(...)
GUARDED_BY(...)
PACKED(...)
__nonnull(...)
OVERRIDE
SHARED_LOCKABLE
ATTRIBUTE_UNUSED
NO_THREAD_SAFETY_ANALYSIS
ALWAYS_INLINE
配置好C.tom后,关闭并重新打开Source Insight,单击Project→Rebuild Project,弹出图1-4所示的对话框。
图1-4中,选择Re-Create the whole project from scratch即可。
提示:图1-4所示对话框的下方展示了源码文件个数,笔者设置的工程包含源码文件8688个。
1.2.3 准备模拟器和自制系统镜像
阅读源码是学习虚拟机的主要方法。但在某些关键地方,有时候很难确定代码逻辑的走向,这时就需要在源码中加一些日志来辅助我们观察虚拟机的行为。在此,笔者推荐使用模拟器和自制系统镜像来帮助我们达到这个目标。
提示:自制系统镜像是由上文下载的Android源码文件编译而来。我们可以随心所欲地通过修改源码文件来定制Android系统。当然,这个由我们自己编译而来的系统只能跑在模拟器中。即便如此,这对我们学习ART虚拟机来说也是莫大的帮助。
1.2.3.1 准备好模拟器
读者需首先安装Android Studio。然后随便打开一个Android应用工程。单击菜单栏右边的avd manager图标,启动AVD界面,如图1-5所示。
图1-5中,笔者创建了两个虚拟设备,一个是运行Android 7.0系统的innost-7.0设备,一个是运行Android 9.0系统的innost-9.0设备。
单击图1-6中左下角的“Create Virtual Device”,出现图1-6所示的设备硬件配置界面。
读者可自定义硬件配置或者从谷歌相关手机产品中选一个手机型号。比如Pixel XL。然后单击图1-6右下角的Next。出现图1-7所示的系统镜像选择界面。
建议读者选择Nougat x86系统镜像。也就是说,我们后面要分析的ART虚拟机将以x86 CPU为平台。
为什么选择x86平台?
工作用的台式机或笔记本主要是x86平台。所以,模拟器运行x86系统镜像的速度非常快。笔者之前尝试过使用arm平台,但模拟器运行的速度较慢。另外,根据上一节笔者统计的代码量可知,6个CPU平台总汇编代码的有效代码行数/总有效代码行数大概为6.02%,平均每个CPU平台的汇编代码行数才占总代码行数的1%左右。从这一点可以看出,汇编代码虽然重要,但它不会影响虚拟机学习。值得注意的是,Android SDK从8.0开始就不再提供ARM平台的模拟器镜像文件。
虚拟设备准备就绪后,读者可以启动它。这时,这个虚拟设备运行的是官方提供的镜像。
1.2.3.2 自制系统镜像
现在,我们有了Android源码、虚拟设备和官方下载的镜像文件。接下来需要编译Android源码以生成一个系统镜像文件,然后用这个系统镜像文件来启动虚拟设备。如此,就达到了让虚拟设备运行我们定制的系统镜像的目标。
编译系统的步骤如下。
执行lunch命令后,会显示如图1-8所示的内容,里边是各种不同的目标设备。请读者选择第8项(下面将介绍第8项的来历)。它表示要编译设备类型为"innost"的设备,该设备使用的CPU为x86,编译类型为userdebug。接着看下一步。
最后,让模拟器使用我们编译得到的系统镜像文件,方法如下。
由于本书的目标是研究ART虚拟机,所以,我们自己编译的系统镜像并不需要包含太多的应用程序,只要保证系统启动必需的几个核心应用程序即可。为此,笔者在源码根目录/device下新增了一个名为innost的设备类型。图1-9展示了该目录下的文件。
图1-9展示了innost设备类型下包含的文件。当把这些准备好后,我们执行如下命令时才能出现图1-8中的第7和第8项。
提示:读者可从笔者分享的链接中下载如图1-9所示的innost设备目录文件。本书所有资源的下载说明见1.4节的内容。
如果读者下载了笔者分享的Android 7.0源码的话,device目录下已经包含了innost设备目录的文件。
1.2.4 小结
读者阅读到这个地方时,请检查下面的工作是否完成。
□有一份完整的Android 7.0的源码。读者可以从笔者提供的资源链接中下载,或者从清华大学开源镜像站下载(下载步骤见1.2.1节)。
□配置好Source Insight,包括添加.cc和.S为后缀的文件类型、修改C.tom文件。然后,导入ART虚拟机学习所需的源码目录(art、libnativehelper、libcore、frameworks/base/cmds/am、frameworks/base/core、frameworks/base/include,可以把test相关的源码去除)。
□下载Nougat x86系统镜像,创建好对应的模拟器,并启动它。
□编译7.0的源码。如果读者是自行下载的源码,请从笔者提供的资料链接中下载自制系统镜像所需的设备配置文件(存放在源码根目录/device下)。
□通过emulator命令使用自己编译出来的系统镜像文件启动模拟器。
1.3 本书的内容
本书大体上可以分为五个部分,笔者用表1-1来描述各个部分对应章节的内容和说明。请读者务必认真阅读(后续如果需要,也请经常回顾)。
提示:表1-1最后一列是笔者给各章节难度的一个主观评分。评分的目的在于提醒读者阅读各章时可能会感受到的难度。除了第6章有着超高难度之外,其他章节只要肯花时间,相信对大部分读者总能学会。另外,笔者自己在研究ART源码的时候会碰到这样一种情况,有些代码前几次阅读感觉难度比较大,但只要多读几次,总会有茅塞顿开的时候。或许这就是所谓的量变到质变吧。
笔者再次和读者强调两点:
□ART虚拟机是复杂系统,模块之间有非常强的耦合关系。读者需采用剥洋葱式的学习方法,逐步、多角度来学习它。比如,Heap模块本书有三处地方介绍了它。每一次介绍都只关注Heap模块一部分的知识。初学者切莫盯着一个知识点一头扎入,否则很难走下去。
□如果读者不是特别了解ART的话,建议严格按照本书的顺序来阅读相关章节。
1.4 本书资源下载说明
读者可通过笔者的博客blog.csdn.net/innost首页置顶文章“深入理解Android系列书籍资源分享更新”以查看本书的资源下载地址。目前本书提供的下载资料如表1-2所示。
如果说ART虚拟机是一座坚固的城堡的话,本书相当于在这个城堡上为读者们打开了好几个关键突破口。希望读者在此基础上继续研究ART虚拟机中其他有意思、有价值的领域。