虚拟机其实我们并不陌生,比如vmware,virtualbox等PC平台上的虚拟机软件,相信很多人都玩过。比如在windows上跑一个ubuntu来研究一下linux,编译一下android源码什么的。使用虚拟机一般有两个目的,一个是平台模拟,一个是便于维护。
我们知道系统的底层是基于CPU设计的硬件平台,而CPU根据应用场景不同有着不同的指令集和寄存器结构,也就是架构。常见的PC平台有x86、x86_64架构,移动平台有arm架构。这些架构的差异使得不同的系统无法直接跨平台运行,因为系统源码最终要被编译成机器指令才能运行。当然有些系统支持了非常丰富的硬件平台,比如Linux,既可以在x86平台运行,也可以在arm平台上运行。但是如果我们要在x86平台上运行最初只支持arm结构的Android系统,我们就需要一个中间层,模拟出一个arm平台,将arm架构的指令和寄存器操作,使用软件功能来实现。有的虚拟机为了提高执行效率,也有将目标平台的指令,直接翻译成宿主平台指令执行的,具体一点可以参考这里,虚拟机的原理大致如下图:
我们说的平台模拟的虚拟机就属于TYPE2类型,底层的OS就是宿主系统,上层的OS就是目标系统,中间的HYPER VISOR就是虚拟机软件。Android虚拟机也是平台虚拟机的一种,为了后续方便研究,我们先总结一下PC上虚拟机的运行过程。首先我们需要下载一个虚拟机软件,然后在软件里创建虚拟机,指定虚拟的硬件配置,虚拟机数据会存储在一个虚拟磁盘文件里,最后我们需要为虚拟机安装目标系统,然后就可以启动虚拟机了。所以它包含几个主要部分:
- 虚拟机软件
- 硬件配置
- 虚拟机文件
- 目标系统
虚拟机和真机差异
我们运行自己APP,既可选择真机调试运行,也可以使用虚拟机。一般情况下,使用真机速度更快,体验更佳,但是真机也有自己的一些缺陷。比如,获取成本高,假如要测试APP的兼容性,针对不同API版本的机型都进行配置的话,将会是一笔不少的花费。其次,真机的系统是经过OEM厂家定制的,不一定是原生的API和内在逻辑,也无法针对GoogleAPI等服务进行开发,如果机器里没有的话。特别地,当你想要烧录自己编译的源码,对FrameWork或者更加底层的逻辑行调试的时候,真机则更加不方便。OEM厂家一般都会锁定刷机功能,比如FastBoot,只能用厂家自己的刷机工具进行刷机,一般也不容获取。并且每家的源码和镜像文件格式都不相同,编译的标准源码无法直接烧录。所以,如果要进行Android系统学习,学习虚拟机的使用还是非常必要的。
使用方式及差异
Android虚拟机的获取方式有两种,一种是下载AndroidStudio,使用AndroidStudio进行虚拟机的创建,下载对应的系统镜像,虚拟机运行之后就可以当做设备一样使用adb连接调试了。还有一种是下载Android sdk tool,使用avd manager命令进行安装、运行。第一种的优势是方便、直观,根据UI提示一步一步操作就可以创建、运行虚拟机,实际上它也是使用avd manager的功能。第二种的优势是可以了解adv manager的工作原理,可以使用参数进行最灵活的配置,可以定制自己的kernel、system镜像等等。我们要研究的就是第二种。
从官方的教程来看,Android虚拟机是基于QEMU开源虚拟机衍生出来的,如果想深入研究一下虚拟机原理,可以研究一下QEMU。如果要看官方教程请戳这里,QEMU官方教程请戳这里。
虚拟机的安装
从官方链接上,我们可以下载到不同平台的AndroidStudio,其中自带了整套的AndroidSdk工具。如果我们不需要AndroidStudio,比如在构建服务器上,我们也可以只下载sdk工具,通过工具在线下载build tool、不同版本的sdk、创建虚拟机等等。这里不做详细的介绍。
安装完之后,将SDK路径/tools添加到PATH环境变量中,便于使用。也可以直接到sdk路径下面运行命令,MAC平台的SDK路径是:
/Users/your_name/Library/Android/sdk/
虚拟机的运行
使用avdmanager命令行创建虚拟机比较复杂,也是理解虚拟机的核心部分,我们稍后一步一步探索。
假如我们有一个创建好的虚拟机,比如我们可以先使用AndroidStudio创建一个,就可以使用以下的命令直接运行虚拟机。
emulator -avd avd_name [ {-option [value]} … ]
或者
emulator @avd_name [ {-option [value]} … ]
可以看到这个emulator就是我们虚拟机程序正主了,我们到sdk安装目录下去看看它是个什么文件。
heavy:~/Library/Android/sdk/tools$ file emulator
emulator: Mach-O 64-bit executable x86_64
heavy:~/Library/Android/sdk/tools$ ls -alh emulator
-rwxr-xr-x 1 heavy staff 253K 1 2 2018 emulator
可以看到,emulator是一个可执行文件,但是体积很小,按照虚拟机的功能和复杂性而言,应该是不只这个大小的,所以合理推测它应该还依赖了其他的可执行文件或者库文件,这里先不研究,就把它当做虚拟机程序。
虚拟机的存储路径
根据PC虚拟机使用总结,虚拟机要运行还需要安装了目标系统的虚拟磁盘文件,这里通过-avd参数的名称指定了磁盘文件位置,我们来看看虚拟机是怎么根据名称来找到这个文件的。
我们执行以下命令:
./bin/avdmanager list avd
可以看到输出:
Parsing /Users/heavy/Library/Android/sdk/build-tools/21.1.1/package.xml
...
...
Parsing /Users/heavy/Library/Android/sdk/system-images/android-23-bak/google_apis/x86_64/package.xml
...
Name: Nexus_5X_API_23
Device: Nexus 5X (Google)
Path: /Users/heavy/.android/avd/Nexus_5X_API_23.avd
Target: Google APIs (Google Inc.)
Based on: Android 6.0 (Marshmallow) Tag/ABI: google_apis/x86_64
Skin: nexus_5x
Sdcard: 512M
avdmanager解析了很多的平台配置文件:package.xml,最后告诉我们找到一个可用的虚拟机,位置在主目录下面。我们打开文Nexus_5X_API_23.avd件查看内容:
avd.ini.encoding=UTF-8
path=/Users/heavy/.android/avd/Nexus_5X_API_23.avd
path.rel=avd/Nexus_5X_API_23.avd
target=android-23
发现它只是一个配置文件,通过path和path.rel指向虚拟机数据文件夹的绝对、相对地址。我们再查看目录的内容:
drwxr-xr-x 18 heavy staff 576B 12 4 10:24 .
drwxr-xr-x 4 heavy staff 128B 12 4 10:34 ..
-rw-r--r-- 1 heavy staff 66M 12 3 14:25 cache.img
-rw-r--r-- 1 heavy staff 4.3M 12 4 10:24 cache.img.qcow2
-rw-r--r-- 1 heavy staff 1.1K 12 3 14:25 config.ini
drwxr-xr-x 7 heavy staff 224B 12 3 14:25 data
-rw-r--r-- 1 heavy staff 51B 12 4 10:24 emulator-user.ini
-rw-r--r-- 1 heavy staff 1.0M 12 3 14:25 encryptionkey.img
-rw-r--r-- 1 heavy staff 448K 12 4 10:24 encryptionkey.img.qcow2
-rw-r--r-- 1 heavy staff 1.9K 12 4 10:24 hardware-qemu.ini
-rw-r--r-- 1 heavy staff 512M 12 3 14:25 sdcard.img
-rw-r--r-- 1 heavy staff 768K 12 4 10:24 sdcard.img.qcow2
drwxr-xr-x 3 heavy staff 96B 12 3 14:26 snapshots
-rw-r--r-- 1 heavy staff 192K 12 3 14:25 system.img.qcow2
-rw-r--r-- 1 heavy staff 2.0G 12 3 14:25 userdata-qemu.img
-rw-r--r-- 1 heavy staff 326M 12 4 10:24 userdata-qemu.img.qcow2
-rw-r--r-- 1 heavy staff 2.0G 12 3 14:25 userdata.img
-rw-r--r-- 1 heavy staff 8B 12 3 14:25 version_num.cache
我们就找到了虚拟机使用的相关数据了,具体的文件内容稍后研究。现在我们回头来看看,avdmanager是怎么找到Nexus_5X_API_23.avd文件的,为什么查找虚拟机要解析那些package.xml文件呢。
根据官网的描述,虚拟机数据路径默认规则是:
~/.android/avd/name.avd/
其中name就是虚拟机的名称, ~是主机上的android主目录,可以通过ANDROID_SDK_HOME来动态指定。
it displays a list of AVD names from your Android home directory. Note that you can override the default home directory by setting the ANDROID_SDK_HOME environment variable.
如果设置环境变量指向一个其他的路径,avdmanager就会到指定的路径下去寻找虚拟机文件,下面这个api26是手动拷贝到环境变量指定的路径下面的,但是它会被当做一个虚拟机。
heavy:~/Library/Android/sdk/tools$ echo $ANDROID_SDK_HOME
/Users/heavy/temp
heavy:~/Library/Android/sdk/tools$ ls ~/temp/.android/avd/
Nexus_5X_API_26.ini
heavy:~/Library/Android/sdk/tools$ ./bin/avdmanager list avd
Virtual Devices:
Name: Nexus_5X_API_26
Device: Nexus 5X (Google)
Path: /Users/heavy/.android/avd/Nexus_5X_API_23.avd
Target: Google APIs (Google Inc.)
Based on: Android 6.0 (Marshmallow) Tag/ABI: google_apis/x86_64
Skin: nexus_5x
Sdcard: 512M
如果我们修改了虚拟机存储路径的名称:
heavy:~/.android/avd$ mv Nexus_5X_API_23.avd/ Nexus_5X_API_23.avd-bak
heavy:~/.android/avd$ ~/Library/Android/sdk/tools/bin/avdmanager list avd
Available Android Virtual Devices:
The following Android Virtual Devices could not be loaded:
Name: Nexus_5X_API_23
Path: /Users/heavy/.android/avd/Nexus_5X_API_23.avd
Error: Failed to parse properties from /Users/heavy/.android/avd/Nexus_5X_API_23.avd/config.ini
avdmanager依然可以找到虚拟机,但是加载的时候会失败,如果我们修改了虚拟机初始化文件的路径,avdmanager直接就找不到虚拟机了。
heavy:~/.android/avd$ ls
heavy:~/.android/avd$ mv Nexus_5X_API_23.ini Nexus_5X_API_23.ini-bak
heavy:~/.android/avd$ ls
Nexus_5X_API_23.avd Nexus_5X_API_23.ini-bak
heavy:~/.android/avd$ ~/Library/Android/sdk/tools/bin/avdmanager list avd
Available Android Virtual Devices:
说明实际上avdmanager是根据路径规则,找的同名配置文件,然后根据配置文件里的路径,再寻找虚拟机的数据路径的。那为什么要解析sdk路径下面的package.xml文件呢?
这里我没有找到足够的资料,只能做一个推测。我们知道AndroidSDK工具分了很多平台和api版本,除去最基本的tools,plaform-tools等工具是sdk必备的,其他的都是可以根据需要选装的,所以google提供了sdkmanager作为一个工具,用于管理本地的sdk工具集,包括从远程仓库下载和安装不同版本的sdk、编译好的虚拟机系统镜像等等。为了方便安装使用,sdkmanager的实现,没有使用数据库这种强关系管理工具来管理平台和依赖,而只是使用环境变量、目录结构这种弱关系,比如允许ANDROID_SDK_HOME来指定android主目录,比如sdk版本默认放在sdk/platforms下面而虚拟机系统镜像放在sdk/system-imges下面。出于灵活性考虑,sdkmanager对sdk版本和系统镜像版本的目录结构不是在代码里写死的,而是通过package.xml索引文件来声明的,包括包类型,api版本,显示名称,相对路径等等。所以当我们avdmanager找到了虚拟机文件的时候,还要根据虚拟机的平台、版本,查看是否有安装对应的系统镜像,所以就有了上述的package.xml遍历。
为了验证这个推测,我们把sdk/platforms/下,android-21和android-27重命名为android-210,android-270:
heavy:~/Library/Android/sdk/platforms$ ls
android-19 android-22 android-24 android-26
android-210 android-23 android-25 android-270
然后把对应目录下package.xml中的path路径也修改为210和270,打开sdkmanager,查看安装的sdk版本:
可以看到修改后的sdk版本还是可以被正确地识别,说明我们的推测是对的。但是avdmanager为什么要搜索已安装的sdk和system-images版本还是没有看懂。实际上在虚拟机目录下的hardware-qemu.ini文件里,有关于虚拟机的所有配置,其中就包括了kernel,system等镜像文件的路径,是不需要进行搜索查找的。
我们总结一下虚拟机文件的查找路径:
- 根据$ANDROID_SDK_HOME/.android/avd/name.ini获取虚拟机列表,如果环境变量未设置,默认在用户的主目录
- 根据name.ini里的PATH,查找虚拟机数据的存放路径
- 虚拟机使用的数据文件路径、配置等由路径下的hardware-qemu.ini文件指定
虚拟机的文件内容
在上面的存储路径的研究中,我们已经看到了虚拟机的文件列表,官方对于这些也没有非常详细的说明,只说明了几个比较关键的文件,在这里尽量说明个人理解的所有内容,包括一些推测。
- cache.img
缓存分区,会挂载在虚拟机的/cache目录下,用于存储下载等一些临时文件,关机的时候会被清除,可以在虚拟机运行的时候,使用参数-cache持久化缓存。缓存分区开始是空的,并且会被清空如果使用了-wipe-data选项。 - cache.img.qcow2
cache.img的软连接 - config.ini
设备配置文件,用于Android设备相关的属性。 - data
虚拟机运行的一些数据 - emulator-user.ini
未知 - encryptionkey.img
不清楚具体作用,等研究完android源码构建之后,再行补充。 - encryptionkey.img.qcow2
encryptionkey的软连接 - hardware-qemu.ini
设备配置文件,用于虚拟机自身相关的属性,包括文件系统镜像等。 - sdcard.img
sd卡分区,用于模拟设备的sd卡。 - sdcard.img.qcow2
sdcard.img的软连接。 - snapshots
快照数据路径,用于保存虚拟机的运行状态,快速恢复虚拟机。 - system.img.qcow2
system.img的软连接,因为system.img是只读的,所以并没有被拷贝到虚拟机目录下。 - userdata.img
系统文件下拷贝过来,用于生成userdata-qemu.img文件的,不知道为啥之后没有删除。 - userdata-qemu.img
/data分区的镜像文件,每个虚拟机单独一个,在虚拟机创建或者使用-wipe-data选项的时候,根据系统目录下的userdata.img文件生成。 - userdata-qemu.img.qcow2
userdata-qemu.img的软连接 - version_num.cache
未知
以上是虚拟机创建后的数据文件,还有几个文件是放在系统镜像文件下面,直接通过配置文件索引的:
- kernel-qemu or kernel-ranchu
系统内核镜像 - ramdisk.img
/boot分区的镜像,是system.img之前挂载,用于做一些初始化工作
虚拟机的配置
上述已经介绍了虚拟机的两个配置文件hardware-qemu.ini和config.ini,用于设置虚拟机设备和Android设备属性。一般来说通过这些配置模拟市面上真机的配置,不是特殊需要,不要进行修改。但是hardware-qemu.ini中指定了虚拟机的系统分区镜像文件的路径,如果进行源码调试,可以直接修改到源码编译的目录,这样避免了编译结束的拷贝动作。具体的配置请参考配置文件,还是比较直观的。
创建虚拟机
执行以下命令可以创建一个虚拟机:
avdmanager create avd -n TEST_AVD -c 512M -k "system-images;android-23;google_apis;x86_64"
执行后avdmanger会让你输入虚拟机的各项配置,尝试一路默认选项之后,也可以运行起来,这里就不再贴了。这些参数很多,理解起来也很费劲,没弄好的话后面用起来出啥问题也了不一定,推荐还是用UI工具来创建虚拟机会更加方便友好一点。
这里重点想讲一下 -k 参数,一开始没有研究明白的时候,总看不懂这个参数的含义,也就看不懂这条命令。这个参数用分号隔开的,其实相对sdk目录的,虚拟机初始镜像的存放的路径,用avdmanager UI工具下载的系统镜像都按照如下的规则存储:
~/Library/Android/sdk/system-images/android-apiLevel/variant/arch/
当然也可以使用avdmanager命令下载系统镜像,读者自行研究哈。官方教程请戳这里
调试安卓系统
本来这里想通过查看虚拟机启动全部日志,可以查看系统开机过程,包括kernel等底层逻辑的,但是尝试了一下没有能够搞定,只能用adb来连接,那就不多介绍了。