2023-01-08 19:21:04 +08:00
..
2023-01-08 19:17:42 +08:00
2023-01-08 19:21:04 +08:00

第二章 系统开发环境与工具

2.1 重新看待系统定制

经过第一章的学习对AOSP定制进行了简略的了解后如果你是完全不懂的小白相信这时在你的心中已经对系统定制开发有了大致的理解。是的简单来说所谓的系统定制就是相当于在一款成熟的产品上进行二次开发。和我们二次开发其他项目的学习步骤也不会有太大的出入细节的区别就在于Android源码相比其他项目要更加庞大。

尽管Android源码非常庞大但是我们并不需要完整的吃透所有代码。我们要学会的是分析的思路学会看代码了解Android的部分运行原理然后思考如何达到自己的目的最后去尝试实现。

学习的流程就和我们二次开发其他应用一样第一步我们要了解如何将项目成功编译并运行。这一章将详细讲解在各种不同的环境下我们应该如何编译Android源码并将其刷入手机中。

2.2 环境准备

2.2.1 Windows

由于在Windows中缺少了各种底层支持所以一般情况我们不会直接在Windows环境中编译而是选择在Windows中创建一个Linux的虚拟环境然后在虚拟环境中安装编译所需要用到的底层依赖。而Windows的虚拟机环境我们有多种选择例如Docker、WSL2、Vmware。

三种虚拟环境我都尝试过编译其中Docker在Windows的体验并不怎么好特别是在我们编译需要较大体积硬盘的情况下完整编译后下次开机耗时明显变高。如果不擅长折腾的话不太建议在Windows下采用Docker来编译源码。

WSL2是Windows下内置的Linux子系统是一个非常轻量化的Linux系统如果你是属于那种又想在Windows中编译但是又不想打开虚拟机那选择WSL2就没错了。使用起来的感觉就好似直接使用命令行一样。并且编译性能相比Vmware要更加高效。在我的笔记本环境中WSL2完整编译的耗时为130分钟而Vmware的耗时是170分钟这是因为它是完全直连计算机硬件无需虚拟化硬件的。所以性能是有较为显著的提升。

如果你是Windows10的环境那么你需要先查询当前系统版本必须是18917或更高的版本才支持WSL2。在cmd命令行中输入winver查看当前版本

image-20230102183339463

由于是系统自带的,所以安装起来非常方便,可以直接在控制面版->程序->启动或关闭Window功能中开启支持即可如下图

img

或者是采用命令的方式开启虚拟机平台和Linux子系统使用管理员权限启动PowerShell。

image-20230102183708998

执行下面的命令开启功能

//启用虚拟机平台
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
//启用Linux子系统
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

启动完成后重启计算机然后我们就可以开始安装一个ubuntu了。直接打开Microsoft Store应用商店搜索ubuntu然后选择自己需要的版本即可例如我安装的是22.04版本,如下图。

image-20230102184626538

成功获取ubuntu后从应用中启动ubuntu即开始正式安装。安装完成后我们点细节需要处理子系统需要迁移由于应用商店默认会给我们安装在C盘中而我们编译系统会占用相当大的空间所以必须将子系统迁移到其他硬盘中。首先打开命令行工具查询当前的子系统名称。

wsl -l -v

  NAME        STATE           VERSION
* ubuntu22    Running         2

然后我们将这个子系统导出,并且注销掉,然后再重新导入放在其他磁盘的子系统。

//导出子系统
wsl --export ubuntu22 E:\wsl2\ubuntu22.tar
//注销之前的虚拟机
wsl --unregister ubuntu22
//重新导入虚拟机,并且指定新的虚拟机存放位置
wsl --import ubuntu22 E:\wsl2\ubuntu22_wsl E:\wsl2\ubuntu22.tar

这时我们在直接在命令行执行wsl即可进入子系统。

使用WSL2主要是在于轻量级和更优的高性能一般都是命令模式的linux即使我们在WSL2中安装了图形界面也会存在一些依赖缺陷所以使用WSL2开发时一般是在Windows使用插件来远程代码管理进行开发。例如使用vscode就直接用wsl插件可以快速的远程访问代码或者是安装ssh服务后使用remote ssh插件进行代码修改。

如果我们需要完整的Linux虚拟机使用VMware会更加的省事。步骤也非常简单流程如下。

1、下载并安装VMware虚拟机然后下载ubuntu22.04镜像。

2、VWware创建虚拟机选择指定镜像

image-20230102194041709

3、设置初始账号密码

image-20230102194243774

4、选择虚拟机保存位置这里不要保存在c盘记得磁盘要有至少300G的空间

image-20230102194331141

5、虚拟硬件CPU核心根据你的电脑配置进行调整尽量多分点给虚拟机。

image-20230102194543812

6、虚拟内存分配至少保证16G以上的内存否则可能会碰到内存不足编译失败的情况。

image-20230102194722427

7、虚拟硬盘分配这里至少分配300G的空间考虑到性能我选择的是单文件吗这里如果选择立即分配所有磁盘空间能提高一定的性能。如果你的电脑配置不是很高建议你选择立即分配。

image-20230102194952517

虚拟机开机后将默认进入Ubuntu安装界面按照提示进行选择语言区域等待安装完成即可。

2.2.2 Linux

Linux系统的选择非常多一般情况首选最新的Ubuntu LTS稳定版即可。首先是安装我们必备的开发工具。

1、Android Studio下载并安装下载地址https://developer.android.google.cn/studio/

2、Clion下载并安装下载地址https://www.jetbrains.com/zh-cn/clion/

3、vscode下载并安装下载地址https://code.visualstudio.com/

然后用命令更新一下软件,并安装一下基本的工具

// 更新软件列表
sudo apt update -y && sudo apt upgrade -y

// 安装python和apt-utils
sudo apt-get install -y apt-utils python3 python3-pip python2

// 安装pip
pip install -U pip

// 设置pip使用国内源
mkdir ~/.pip
touch ~/.pip/pip.conf
echo -e '\n[install]\ntrusted-host=pypi.douban.com\n[global]\nindex-url=http://pypi.douban.com/simple' > ~/.pip/pip.conf
cat ~/.pip/pip.conf
pip install pytest

2.3 如何选择源码版本

在开始拉取代码前,我们首选需要了解自己需要的是哪个分支版本,所以我们先看官网对版本的说明https://source.android.com/docs/setup/about/build-numbers?hl=zh-cn

然后根据我们的需求比如我想要在Android10的基础上进行二次开发那么我就找到对应的版本描述根据下图可以看到各个版本号关联的代码分支Android版本支持哪些设备。

image-20230103220519836

这么多版本,我们需要选一个最适合我们的版本,我的选择规律如下:

1、优先找对你的测试机支持的对应版本。

2、然后再找除了支持你的这个设备外还支持了更多设备的版本。

3、满足上面两个条件的最高分支版本也就是尽量找最新的代码。

如果你是直接选择使用虚拟机的那么直接选择支持版本最多的分支即可。这里我的测试设备是pixel 3所以我选择了版本SP1A.210812.016.A1,对应的分支代码是android-12.0.0_r3,如下图。

image-20230103220838404

2.3.1 编译

上面知道了我们需要的目标分支接下来要拉取代码。repo是一个以git为基础包装的代码版本管理工具内部是由python脚本构成的对git命令进行包装主要为了方便管理大型的项目使用repo可以非常方便的拉取对应的分支节点。下面我们开始拉取代码。

// 安装git
sudo apt-get install git

//设置git身份
git config --global user.email "xxxx@qq.com"
git config --global user.name "xxxx"

// 安装curl
sudo apt-get install curl

// 创建bin目录并加入PATH
mkdir ~/bin
PATH=~/bin:$PATH

// 下载repo并设置权限
curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo > ~/bin/repo
chmod a+x ~/bin/repo

// 设置使用国内源拉取代码
export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'

// 创建源码存放的目录
mkdir aosp_12 && cd aosp_12

// 初始化仓库
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest

// 指定分支版本
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-12.0.0_r3

// 同步代码 
repo sync -c -j8

同步代码repo sync -c -j8的命令其中-c表示只同步当前分支代码,可以提高同步速度,而-j是设置同步使用的线程数这里我使用了8条线程但并不是线程越多速度越快而是根据cpu的核心使用最合理的线程数才能达到最佳的并发效果。

// 查看可用cpu数量我的环境显示为16
nproc --all

// 直接使用最佳线程数
repo sync -c -j16

//也可以直接省略成一句
repo sync -c -j$(nproc --all)

代码同步完成后,会提示Success,如果失败了,就重新拉取即可,多拉取几次后,基本都能同步成功。接下来我们开始安装编译的底层依赖。

// AOSP编译的相关依赖安装
sudo apt-get install -y git-core gnupg flex bison build-essential \
	zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev \
	x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip \
	fontconfig libncurses5 procps rsync libsqlite3-0

依赖安装完成后,我们再进行一个细节调整,由于我们经常需要备份代码,将整个源码进行打包备份,但是编译出来的输出目录out的体积非常庞大,所以我备份时会选择移走out目录,或者干脆删除掉,这样非常的麻烦,所以我会选择直接修改编译输出的目录。通过设置环境变量OUT_DIR就可以调整编译结果的输出目录了。

vim ./build/envsetup.sh
// 在底部加上环境变量设置为和源码同一级的目录,我当前源码路径为~/android_src/aosp12
export OUT_DIR=~/android_src/aosp12_out

在开始编译前,我们还需要准备对应设备的驱动,根据我们前面选择的版本号SP1A.210812.016.A1,在官网地址:https://developers.google.com/android/drivers中找到对应的版本号,并且可以看到Pixel 3的手机对应的代号是blueline

image-20230103232052738

第一个文件Vendor是用来存储厂商特定的文件比如设备驱动程序。Android穷的那个时会根据提供的这些设备驱动来正确的加载硬件。这个文件通常由设备厂商提供。如果你成功编译Android后输出目录缺少vendor.img文件那么你就需要检查下是否忘记导入对应型号的设备驱动了。

第二个文件是高通提供的相关设备驱动程序比如GPS摄像头传感器等等。

点击Link下载然后将下载的文件拷贝到Android源码根目录下。然后解压并导出相关驱动文件。

// 解压驱动文件
tar -xvf qcom-blueline-sp1a.210812.016.a1-33e668b9.tgz
tar -xvf google_devices-blueline-sp1a.210812.016.a1-d10754e0.tgz

// 解压会得到两个文件extract-google_devices-blueline.sh和extract-qcom-blueline.sh
// 依次运行两个文件运行后会提示许可说明按回车键然后按q跳过最后手动输入I ACCEPT后回车即可
./extract-google_devices-blueline.sh
./extract-qcom-blueline.sh

导入设备驱动完成后,准备工作基本完成,可以开始编译源码了。

// 初始化环境,执行后会导入多个命令,辅助我们进行编译。
// 这里也可以使用. build/envsetup.sh 是同样的效果
source ./build/envsetup.sh

// 选择编译的版本
lunch

//下面是我这边显示的结果
Lunch menu... pick a combo:
     1. aosp_arm-eng
     2. aosp_arm64-eng
     3. aosp_barbet-userdebug
     4. aosp_blueline-userdebug
     5. aosp_blueline_car-userdebug
     6. aosp_bonito-userdebug
     7. aosp_bonito_car-userdebug
     8. aosp_bramble-userdebug
     9. aosp_bramble_car-userdebug
Which would you like? [aosp_arm-eng]

// 选择版本可以填写直接填写aosp_blueline-userdebug或者是填写编号4
// 同样我们可以省略成一句直接lunch 4或者是lunch aosp_blueline-userdebug
4

// 和上面一样。直接使用当前cpu的核心数作为编译的并发线程
make -j$(nproc --all)

在上面选择版本中可以看到aosp_arm-engaosp_arm64-eng的选项,这两个是模拟器使用的版本。而模拟器使用的版本是可以不需要导入设备驱动文件的。如果在lunch的菜单中没有看到你要编译的版本,并且直接lunch aosp_blueline-userdebug 也提示错误,可能是没有成功导入驱动文件,或者下载的驱动文件不对。

同一个代号的编译有三种编译版本选择。分别如下:

1、aosp_blueline-user 一般是默认的编译环境,主要是作为发布版本,这种版本编译的环境会默认开启大多数的安全机制,比如ro.secure值为1ro.debuggable值为0需要我们自行用第三方工具获取root权限。我们日常使用的机子就是属于user环境的。

2、aosp_blueline-userdebug 通常用于测试和调试Android系统会启动一些调试工具例如默认开启adb调试,ro.debuggable值为1系统自带root权限等等。

3、aosp_blueline-eng 同样也是用于测试和调试的环境,但是比userdebug要更加极端,会禁用一些安全机制,比如签名验证,关闭一些编译优化等等。

第一次完整编译非常的漫长我的电脑耗时2个小时成功编译。编译成功后我们检查一下输出的文件。

// 查看输出目录的所有镜像文件
ls /root/android_src/aosp12_out/target/product/blueline | grep img

// 输出结果如下
boot-debug.img
boot.img
bootloader.img
boot-test-harness.img
dtb.img
dtbo.img
persist.img
radio.img
ramdisk-debug.img
ramdisk.img
ramdisk-recovery.img
ramdisk-test-harness.img
super_empty.img
system_ext.img
system.img
system_other.img
userdata.img
vbmeta.img
vendor.img

确定有编译出vendor.img、system.img、boot.img等等镜像文件,就说明编译成功了。

2.4 模块编译

前文在编译的过程中介绍到,我们使用source ./build/envsetup.sh初始化环境的时候,导入了多个命令来帮助我们进行编译。我们可以通过命令hmm查看提供的命令帮助。

hmm

Run "m help" for help with the build system itself.

Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch:      lunch <product_name>-<build_variant>
              Selects <product_name> as the product to build, and <build_variant> as the variant to
              build, and stores those selections in the environment to be read by subsequent
              invocations of 'm' etc.
- tapas:      tapas [<App1> <App2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
              Sets up the build environment for building unbundled apps (APKs).
- banchan:    banchan <module1> [<module2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
              Sets up the build environment for building unbundled modules (APEXes).
- croot:      Changes directory to the top of the tree, or a subdirectory thereof.
- m:          Makes from the top of the tree.
- mm:         Builds and installs all of the modules in the current directory, and their
              dependencies.
- mmm:        Builds and installs all of the modules in the supplied directories, and their
              dependencies.
              To limit the modules being built use the syntax: mmm 
// 省略            
......  

croot 命令可以跳转根目录,或者是根目录下的任意子目录

m 命令会直接在根目录运行编译,即使我们当前目录是在子目录也是相当于在根目录编译。也可以指定名称来编译单独的目标,例如m droid

mm 编译当前目录中的所有模块及依赖项

mmm 编译指定目录中的所有模块及依赖项

clean 清除编译的结果相当于删掉out目录中的内容

我们可以通过m help查看可以单独编译哪些选项

m help

Common goals are:

    clean                   (aka clobber) equivalent to rm -rf out/
    checkbuild              Build every module defined in the source tree
    droid                   Default target
    nothing                 Do not build anything, just parse and validate the build structure

    java                    Build all the java code in the source tree
    native                  Build all the native code in the source tree

    host                    Build all the host code (not to be run on a device) in the source tree
    target                  Build all the target code (to be run on the device) in the source tree

    (java|native)-(host|target)
    (host|target)-(java|native)
                            Build the intersection of the two given arguments

    snod                    Quickly rebuild the system image from built packages
                            Stands for "System, NO Dependencies"
    vnod                    Quickly rebuild the vendor image from built packages
                            Stands for "Vendor, NO Dependencies"
    pnod                    Quickly rebuild the product image from built packages
                            Stands for "Product, NO Dependencies"
    senod                   Quickly rebuild the system_ext image from built packages
                            Stands for "SystemExt, NO Dependencies"
    onod                    Quickly rebuild the odm image from built packages
                            Stands for "Odm, NO Dependencies"
    vdnod                   Quickly rebuild the vendor_dlkm image from built packages
                            Stands for "VendorDlkm, NO Dependencies"
    odnod                   Quickly rebuild the odm_dlkm image from built packages
                            Stands for "OdmDlkm, NO Dependencies"

通过帮助命令的提示,我们可以看到m snod就是单独编译System,命令m vnod就是单独编译Vendor。大多数时候我们修改的内容都是在System中。我们可以根据自己的变动情况,模块编译即可。

2.5 内核编译

在前面我们编译完成后,可以在编译的镜像结果中看到文件boot.img这个文件就是内核镜像文件。但是这个内核是Android源码树中已经编译好的内核文件并不是我们编译出来的如果我们想要修改内核就需要拉取内核的对应分支编译内核将编译结果放入Android源码中的指定路径然后再重新编译Android。刷入手机后使用的内核就是我们自己编译的了。

首先第一步是找到对应我们当前手机的内核分支,官网提供了详细的说明https://source.android.com/docs/setup/build/building-kernels。根据下图可以看到对应Pixel 3测试机分支是android-msm-crosshatch-4.9-android12

image-20230105221730348

接下来我们按照官网的说明拉取代码并编译。

// 内核编译的相关依赖安装
sudo apt install p7zip-full wget curl git tree -y
sudo apt-get install dialog file python3 python3-pip python2 libelf-dev gpg gpg-agent tree flex bison libssl-dev zip unzip curl wget tree build-essential bc software-properties-common libstdc++6 libpulse0 libglu1-mesa locales lcov --no-install-recommends -y
sudo apt-get install pahole libreadline-dev -y

// 创建内核的源码目录不用放再Android源码目录下
mkdir android-kernel && cd android-kernel

// 初始化指定分支
repo init -u https://android.googlesource.com/kernel/manifest -b android-msm-crosshatch-4.9-android12

// 同步分支代码
repo sync -j$(nproc --all)

// 编译内核
build/build.sh

// 编译完成后查看编译结果最后输出显示Image.lz4文件就表示内核编译是成功的。
ls /root/android_src/android-kernel/out/android-msm-pixel-4.9/dist |grep Image

编译成功后我们还需要指定Android源码编译时使用这个内核文件。只需要设置环境变量指定路径即可。方式如下。

// 为了以后方便,环境路径相关的,我们都写在这个初始化导入环境命令的地方
vim ./build/envsetup.sh

// 在最底部添加
export TARGET_PREBUILT_KERNEL=/root/android_src/android-kernel/out/android-msm-pixel-4.9/dist/Image.lz4

// 保存配置后,重新加载一下
source ./build/envsetup.sh

// 选择编译版本
lunch aosp_blueline-userdebug

// 单独编译内核镜像
make bootimage

2.6 刷机

大多数情况下非技术的Android爱好者通常会使用傻瓜式一键刷机工具例如刷机大师、刷机精灵、奇兔等等。这种刷机方式就是属于软刷软件刷机除此之外还有我们第一章中简单介绍到的线刷和卡刷。不论刷机的方式是什么他们最终的原理都是相同的都是对刷机包进行处理然后将ROM文件写入对应的分区替换掉原始文件。下面我们将简单介绍如何进行线刷和卡刷。

2.6.1 线刷

通过上面编译中的步骤,在目录aosp12_out/target/product/blueline/中能看到若干个后缀为img的镜像文件。我的输出路径aosp12_out是由于我手动指定了输出目录,如果你没有设置,那么默认是在aosp12/out/target/product/blueline/目录下。最后的目录blueline是对应编译的版本,如果你是其他版本,就在对应的目录下查看。

首先我们要进入刷机模式,然后环境变量设置编译结果的路径,然后使用命令完整刷机即可。详细流程如下

// 进入刷机模式
adb reboot bootloader

// 设置刷机包的路径到环境变量
export ANDROID_PRODUCT_OUT=/home/king/android_src/mikrom_out/target/product/blueline

// 查询fastboot是否能成功看到设备
fastboot devices
	
// 上面的查看命令显示的结果
8ARX0Y7EP	fastboot

// 完整刷机
fastboot flashall -w

等待刷机结束即可刷机结束后会自动进入Android系统。如果我们只想刷单个分区镜像也是可以的。流程如下

// 进入刷机模式
adb reboot bootloader

// 进入编译结果的目录
cd /home/king/android_src/mikrom_out/target/product/blueline

// 单独刷入内核
fastboot flash boot ./boot.img

// 单独刷入系统
fastboot flash system ./system.img

// 部分机型可能会出现如下错误提示
fastboot: error: The partition you are trying to flash is dynamic, and should be flashed via fastbootd. Please run:

    fastboot reboot fastboot

And try again. If you are intentionally trying to overwrite a fixed partition, use --force.

// 这种情况我们按照他的提示操作即可执行下面的命令后发现进入了fastbootd模式
fastboot reboot fastboot

// 重新刷入系统
fastboot flash system ./system.img

// 刷入共享系统
fastboot flash system_ext ./system_ext.img

// 刷入硬件驱动
fastboot flash vendor ./vendor.img

// 重启
fastboot reboot

2.6.2 卡刷

我们前面编译出来的是线刷包,如果我们需要卡刷包,就需要使用下面的方式进行编译

// 下面是简单的编译卡刷包
cd aosp12
source ./build/envsetup.sh
lunch aosp_blueline-userdebug
make otapackage

编译完成后,我们可以在前面线刷包的路径下看到卡刷包文件,我这里的文件名是aosp_blueline-ota-eng.king.zip。除了上面的方式,我们还可以完整编译卡刷包,编译方式如下

//下面是完整编译卡刷包
cd aosp12
source ./build/envsetup.sh
lunch aosp_blueline-userdebug
mkdir dist_output
make dist DIST_DIR=dist_output

编译完成后,可以在目录dist_output中看到完整卡刷包结果。

接下来是如何刷入卡刷包,有两种刷入方式,一种是使用adb sideload命令刷入另一种方式是使用twrp刷入。下面演示两种不同方式的刷机流程。

1、adb sideload这里没写完你补充一下我这边环境没跑通

首先进入fastbootd

adb reboot bootloader
fastboot reboot fastboot

这时的界面如下图,使用音量键减,切换到Enter recovery,然后按电源键进入recovery模式

image-20230108190236615

接下来进入下面的界面,选择Apply update from ADB

image-20230108190631803

2、twrp这里没写完你补充一下我这边环境没跑通