qemu 源码编译安装-自己编译Android源码(超详细)

在Android Studio代码调试文章中,简单介绍了一些代码调试的方法。 下面我们来说说android源码编译的一些事情。 (我认为,作为一名android开发者,每个人都应该拥有一份自己的Android源代码,这样我们就可以在有疑问的地方随时通过自己调试来加强自己的理解)。

本文使用最新的Ubuntu 16.04。 请确保您已经安装了 Git。 没有安装的朋友可以通过以下命令安装:

sudo apt-get install git 
git config –global user.email “test@test.com” 
git config –global user.name “test”

其中 test@test.com 是您自己的电子邮件。

简要说明

Android源码编译的四个过程: 1.源码下载; 2、搭建编译环境; 3、编译源代码; 4. 跑步。 下面也按照这个过程进行描述。

源码下载

由于某墙的激励,这里我们使用国外镜像源进行下载。

目前可用的图像来源通常是交通大学和北京大学,具体用法类似。 这里我选择清华大学的形象来进行说明。 (参考资料:科技大学来源、清华来源)

repo工具下载和安装

通过执行以下命令下载并安装 repo 工具

mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

补充说明

这里我简单介绍一下repo工具。 我们知道AOSP项目是由不同的子项目组成的。 为了方便管理,Google使用Git来管理AOSP项目的多个仓库。 在说repo工具之前,我先和大家聊聊。 谈论多仓库项目:

我们有一个非常大的项目Pre,它由很多子项目R1,R2,...Rn等组成,为了方便管理和协同开发,我们为每个子项目建立了自己的仓库。 整个项目的结构如下:

在这里写下图片描述

将一个项目Pre细分到库后,我们会遇到这样一个问题:如果我们想创建一个Pre分支进行功能开发,这意味着我们需要在每个子项目中创建一个对应的分支。 如果这个过程纯粹是通过手动来完成的话,那简直就是一场灾难。 事实上,我们会想写一个自动处理程序(我们假设这个工具叫RepoUtil)来帮助我们解决这个问题。 这个RepoUtil也会有版本管理等需求,所以我们也使用Git对其进行管理,并为其创建相应的仓库。 至此,整个项目的结构如下:

在这里写下图片描述

这里RepoUtil知道整个项目Pre下的各个子项目(即维护子项目列表),需要为此类子项目提供管理功能,比如统一创建分支等。 但从“单一责任”的角度来看,工具 RepoUitl 的功能过于复杂,我们完全可以将维护子项目列表的功能提取出来作为新项目 sub_projects ,因为子项目也会发生变化,因此,为其创建对应的仓库,并使用Gi​​t进行管理。 这样,RepoUtil只需要简单依赖ub_projects即可,整个项目的结构如下:

在这里写下图片描述

AOSP项目结构与我上面的描述非常相似。 repo工具对应RepoUtil,mainfest对应sub_projects。

总结一下:repo就是这样一个工具,它由一系列python脚本组成,通过调用Git命令来管理AOSP项目。

创建源文件夹

熟悉Git的朋友应该知道,我们需要在本地为项目创建一个对应的仓库。 同样,为了方便代码管理,我们为其创建一个文件夹。 这里我在当前用户目录下创建了一个source文件夹,所有下载的源码和编译好的产品也都放在这里,命令如下:

mkdir source
cd source

初始化仓库

我们使用之前的源文件夹作为仓库,现在我们需要初始化仓库。 通过执行初始化仓库命令,可以获取AOSP项目master上的最新代码并初始化仓库。 命令如下:

repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest

或使用:

repo init -u git://aosp.tuna.tsinghua.edu.cn/aosp/platform/manifest

两者达到的疗效是一样的,只是合同不同。

如果在执行这个命令的过程中,如果提示无法连接gerrit.googlesource.com,那么我们只需要编辑~/bin/repo文件,找到REPO_URL这一行,然后将其内容改为:

REPO_URL = 'https://gerrit-google.tuna.tsinghua.edu.cn/git-repo'

然后再次执行上面的命令。

补充说明

不带参数的manifest命令用于获取master上的最新代码,但可以通过-b参数指定具体的android版本。 例如我们要获取android-4.0.1_r1分支,命令如下:

repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-4.0.1_r1

(AOSP项目当前所有分支列表请参考:)

同步源码到本地

初始化仓库后,就可以开始同步代码到本地了,命令如下:

repo sync

以后如果需要将远程最新代码同步到本地,只需要执行该命令即可。 同步过程中,如果由于网络原因中断,可以使用该命令继续同步。 不出意外,5个小时之内就能完成所有源代码同步到本地。 所以,这个过程可以在晚上睡觉的时候完成。

(Tips:确保代码完全同步,否则下面编译过程中的错误会让你惨不忍睹,如果不确定,可以使用reposync同步几次)

搭建编译环境

下载源码后,即可搭建编译环境。 在开始之前,我们先来看看一些编译要求:

1、硬件要求:

64位操作系统只能编译2.3.x以上版本。 如果要编译2.3.x以下的版本,则需要32位操作系统。

磁盘空间越多越好,至少100GB以上。 这意味着,您可以购买更大的硬盘。

如果你想在虚拟机中运行linux,你至少需要16GB RAM/swap。

(实际上,我不建议在虚拟机中编译2.3.x以上的代码。)

2、软件要求:

1. 操作系统要求

在AOSP开源中,主分支是使用常年版本的Ubuntu进行开发和测试的,所以也建议大家使用Ubuntu进行编译。 下面我们列出了不同版本的Ubuntu来编译这些android版本:

Android版本编译所需的Ubuntu最低版本

Android 6.0到AOSP大师

乌班图14.04

Android 2.3.x 至 Android 5.x

乌班图12.04

Android 1.5 至 Android 2.2.x

乌班图10.04

2.JDK版本要求

除了操作系统版本的问题之外,我们还需要关注JDK版本的问题。 为了方便,我们还列出了不同Android版本源码所需的JDK版本:

Android版本编译所需的JDK版本

AOSP Android 主线

OpenJDK 8

安卓 5.x 至安卓 6.0

OpenJDK 7

Android 2.3.x 至 Android 4.4.x

甲骨文JDK 6

Android 1.5 至 Android 2.2.x

甲骨文JDK 5

更多详情请参考:Google源码编译要求

我现在在Ubuntu 16.04下编译AOSP主线代码,所以需要安装OpenJDK 8,执行命令如下:

sudo apt-get install openjdk-8-jdk

如果需要在Ubuntu 14.04下编译AOSP主线代码,还需要安装OpenJDK 8,此时需要执行以下命令:

sudo apt-get update
sudo apt-get install openjdk-8-jdk

如果要编译Android 5.x到android 6.0之间的系统版本,需要使用openjdk7。 但Ubuntu 15.04及以后版本的在线安装库仅支持openjdk8和openjdk9。 因此,如果要安装openjdk 7,需要先设置ppa:

sudo add-apt-repository ppa:openjdk-r/ppa 
sudo apt-get update

然后执行安装命令:

sudo apt-get install openjdk-7-jdk 

有时候,我们需要编译不同版本的android系统,那么我们可能会使用不同版本的jdk。 对于jdk版本切换,可以使用以下命令:

sudo update-alternative --config java
sudo update-alternative --config javac

3、其他要求

Google官方的构建编译环境手册已经解释了Ubuntu14.04、Ubuntu 12.04、Ubuntu 10.04需要添加的依赖项,这里不再介绍。 我曾经以为Ubuntu16.04的设置和Ubuntu14.04的依赖应该是类似的,但我只能说too young too simple。

以下是Ubuntu16.04中的依赖设置:

sudo apt-get install libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-dev g++-multilib 
sudo apt-get install -y git flex bison gperf build-essential libncurses5-dev:i386 
sudo apt-get install tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386 
sudo apt-get install dpkg-dev libsdl1.2-dev libesd0-dev
sudo apt-get install git-core gnupg flex bison gperf build-essential  
sudo apt-get install zip curl zlib1g-dev gcc-multilib g++-multilib 
sudo apt-get install libc6-dev-i386 
sudo apt-get install lib32ncurses5-dev x11proto-core-dev libx11-dev 
sudo apt-get install libgl1-mesa-dev libxml2-utils xsltproc unzip m4
sudo apt-get install lib32z-dev ccache

(其中几个命令中的参数重复,但这并不妨碍我们)

初始化编译环境

确保上述过程完成后qemu 源码编译安装,我们需要初始化编译环境。 命令如下:

source build/envsetup.sh

执行该命令的结果如下:

在这里写下图片描述

不难发现,这条命令只是引入了其他执行脚本。 至于那些脚本是做什么的,我在本文中不会详细阐述。

命令执行成功后,我们会得到一些有用的命令,比如下面要使用的lunch命令。

编译源代码

编译环境初始化完成后,进入源码编译阶段。 该阶段包括选择编译目标和执行编译两个阶段。

选择编译目标

通过lunch命令设置编译目标。 所谓编译目标就是生成的镜像将运行在什么样的设备上。 这里我们设置编译目标为aosp_arm64-eng,所以执行命令:

lunch aosp_arm64-eng

编译目标格式规范

编译目标的格式:BUILD-BUILDTYPE,例如前面的aosp_arm-eng的BUILD是aosp_arm,BUILDTYPE是eng。

什么是构建

BUILD是指特定功能组合的具体名称,即编译后的镜像可以运行在哪个环境中。其中,aosp(Android Open Source Project)代表Android开源项目; arm表示系统运行在arm架构的处理器上,arm64指64位arm架构; 处理器,x86是指x86架构的处理器; 另外,还有一些代表具体Nexus设备的短语,以下是常用的设备代码和编译目标,更多参考官方文档

|接收者类型|设备代码|编译目标|

|---|----|---|

|Nexus 6P|钓鱼器|aosp_angler-userdebug|

|Nexus 5X|牛头|aosp_bullhead-userdebug|

|Nexus 6|shamu|aosp_shamu-userdebug|

|Nexus 5|hammerhead|aosp_hammerhead-userdebug|

提示:如果你没有Nexus设备,那么一般选择arm或x86

什么是构建类型

BUILD TYPE是指编译类型,通常有以下三种:

-user:表示编译出来的系统镜像是即将发布到市场的版本,其权限受到限制(例如无root权限、无年份dedug等)

-userdebug:根据用户版本开启root权限和debug权限。

-eng:代表engineer,也就是所谓的开发工程师的版本,拥有最大的权限(root等),还自带很多调试工具

了解了编译目标的构成之后,我们就可以根据自己的实际情况进行选择了。 那么如果我们不知道编译目标怎么办呢?

我们只需要执行不带参数的lunch命令,稍后控制台就会列出所有的编译目标,如下:

在这里写下图片描述

然后我们只需要输入相应的数字即可。

举个反例:你没有Nexus设备,只想编译后运行,那么你可以选择aosp_arm-eng。

(在ubuntu 16.04(64位)编译后启动虚拟机时,卡在蓝屏,尝试编译aosp_arm64-eng解决,所以这里使用aosp_arm64-eng)

开始编译

通过make命令来编译代码,该命令使用-j参数来设置参与编译的线程数,以提升编译速度。 比如这里我们设置8个线程同时编译:

make -j8

需要注意的是,参与编译的线程越多越好。 一般是根据你的机杯核心来确定:core*2,是当前CPU核心的两倍。 比如我现在的电脑是四核四线程,那么根据公式,最快的编译可以是make -j8。

(通过cat /proc/cpuinfo查看相关cpu信息)

如果一切顺利的话,几个小时后就可以编译完成。 看到 ### make Completed successfully (01:18:45(hh:mm:ss)) ### 表示编译成功。

运行模拟器

编译完成后,可以通过以下命令运行Android虚拟机,命令如下:

source build/envsetup.sh
lunch(选择刚才你设置的目标版本,比如这里了我选择的是2)
emulator

如果编译后立即运行虚拟机,因为我们之前已经执行了source和lunch命令qemu 源码编译安装,现在只需要执行命令即可运行虚拟机:

emulator

不出意外,等待一段时间后,就会看到运行界面:

在这里写下图片描述

补充

既然提到了模拟器,这里就介绍一下模拟器运行所需的四个文件:

Linux 内核系统.imguserdate.imgramdisk.img

如果使用lunch命令时选择aosp_arm-eng,那么当执行不带参数的emualtor命令时,Linux Kernel默认为kernel-qemu文件; 而android镜像文件默认使用source/out/target/product/generic目录下的system.img、userdata.img和ramdisk.img,也就是我们刚刚编译的镜像文件。

上面,我在使用lunch命令时选择了aosp_arm64-eng,所以Linux默认使用/source/prebuilds/qemu-kernel/arm64/kernel-qemu下的kernel-qemu,而其他文件则使用source/out/system.img、userdata .img 和 ramdisk.img 位于 target/product/generic64 目录中。

当然,模拟器命令允许您通过参数使用不同的文件。 具体用法可以通过emulator --help查看

模块编译

除了通过make命令编译整个android源代码之外,Google还为我们提供了相应的命令来支持各个模块的编译。

编译环境初始化后(即执行source build/envsetup.sh),我们可以得到一些有用的指令,除了上面用到的lunch之外,还有以下这些:

  - croot: Changes directory to the top of the tree.
  - m: Makes from the top of the tree.
  - mm: Builds all of the modules in the current directory.
  - mmm: Builds all of the modules in the supplied directories.
  - cgrep: Greps on all local C/C++ files.
  - jgrep: Greps on all local Java files.
  - resgrep: Greps on all local res/*.xml files.
  - godir: Go to the directory containing a file.

mmm命令用于编译指定目录。 一般来说,每个目录只包含一个模块。 例如,这里我们要编译Launcher2模块并执行命令:

mmm packages/apps/Launcher2/

过了一会儿,如果出现提示:

### make 完全成功完成###

则表示编译完成,可以在out/target/product/gereric/system/app中看到编译好的Launcher2.apk文件。

重新打包系统镜像

编译完指定的模块后,如果我们想将该模块对应的apk集成到系统镜像中,就需要依靠make snod命令重新打包系统镜像,这样我们新生成的system.img就包含了新编译的Launcher2模块,重启模拟器后生效。

单独安装模块

我们不断地改变各个模块,所以总不能每次编译完都重新打包system.img,然后重启手机吧? 有什么简单的办法吗?

编译完成后,使用adb install命令直接将生成的apk文件安装到设备上。 与使用make snod相比,会省去很多麻烦。

补充

我们简单介绍一下out/target/product/generic/system目录下的常用目录:

Android系统自带的apk文件都在out/target/product/generic/system/apk目录下;

一些可执行文件(如C编译的执行)放在out/target/product/generic/system/bin目录下;

动态链接库放在out/target/product/generic/system/lib目录下;

硬件抽象层文件放置在out/targer/product/generic/system/lib/hw目录中。

SDK编译

如果需要自己编译SDK,很简单,执行命令make sdk即可。

错误集合

编译过程中遇到的错误大部分都可以在Google中找到解决方案。 以下是一些常见错误:

错误 1:您尝试使用不正确的版本进行构建。 具体错误如下:

在这里写下图片描述

如果你仔细阅读了搭建环境的要求,那么这个错误是可以避免的。 当然,这个问题也很容易解决:安装openjdk 8,并且不要忘记使用sudo update-alternative命令切换jdk版本。

错误2:内存不足错误。 具体错误如下:

在这里写下图片描述

这个错误比较常见,尤其是编译AOSP主线代码时,往往是由于JVM堆大小太小导致的。

此时有两种解决方案:

方法一:

编译命令之前,修改prebuilts/sdk/tools/jack-admin文件,在文件中找到这一行:

JACK_SERVER_COMMAND="java -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -cp $LAUNCHER_JAR $LAUNCHER_NAME"

然后将 -Xmx4096m 添加到该行,例如:

JACK_SERVER_COMMAND="java -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -Xmx4096m -cp $LAUNCHER_JAR $LAUNCHER_NAME"

然后执行time make -8j

方法二:

在控制台执行以下命令:

export JACK_SERVER_VM_ARGUMENTS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4096m"
out/host/linux-x86/bin/jack-admin kill-server
out/host/linux-x86/bin/jack-admin start-server

如图所示:

在这里写下图片描述

执行该命令后,使用make命令继续编译。 某些情况下,执行jack-adminkill-server时,可能会提示该命令不存在。 这时,进入out/host/linux-x86/bin/目录,你会发现目录中没有jack-admin文件。 如果我是你,我会重新同步并从头开始。

错误三:使用模拟器时,虚拟机停在死界面,点击无反应。 这个时候就可能是内核的问题了。 解决方法如下:

执行以下命令:

./out/host/linux-x86/bin/emulator -partition-size 1024 -kernel ./prebuilts/qemu-kernel/arm/kernel-qemu-armv7 

通过使用kernel-qemu-armv7内核来解决等待模拟器崩溃的问题。 而-partition-size 1024是为了解决警告:系统分区大小调整为匹配图像文件(163 MB > 66 MB)

如果你一开始编译的版本是aosp_arm-eng,使用上述命令仍然无法解决等待崩溃的问题,不妨尝试编译aosp_arm64-eng。

结束它

到现在为止,你已经了解了android编译的整个流程。 另外我还简单讲解一下android源码的多仓库管理机制。 接下来,你不妨自己尝试一下。