写在前面——2023年3月
Android更新换代频繁,编译系统经常调整,设备安全机制经常变化,且TWRP的仓库地址也会更改。
这篇首发于2018年的教程不一定适用于当下的Android源码树,尤其是Android 9开始的版本。因此,仅供参考。
本文在稀土掘金同步发表。如果需要留言,请移步掘金版本的评论区。
TWRP Recovery的强悍,使得它成为了刷机领域当之无愧的首选。很多设备刷机的第一步,正是选择一款适合的TWRP,然后刷上去。目前,多个品牌的热门机型都有官方适配了,且一些开发者也给官方未覆盖的机型适配了自己的非官方版本。
然而,开发者们并不是万能的,总有那么一些机型,并没有哪一位开发者前来适配。在这样的情境下,你是愿意痴痴地等,等到哪位大神有时间做适配,还是马上动手丰衣足食呢?
当然要自己动手啦!
事实上,TWRP的适配并没有想象中的那么难。理论上只需在Android的源代码中进行,准备好必要的文件,运行编译命令,即可完成适配。下面笔者就来结合自己的经验,一步步讲解如何适配TWRP Recovery。
配置要求
编译TWRP和编译Android一样,都是相当吃系统资源的工作,因此必须确保你电脑的配置足够。运行环境只能是Linux发行版[^1],下文以Ubuntu 18.04为例。
项目 | 要求 |
---|---|
操作系统 | 64位Linux发行版,推荐Ubuntu 18.04 |
磁盘空间 | 至少30GB。Android源码相当吃磁盘空间 |
内存 | 至少4GB,推荐8GB及以上 |
第一步:准备编译环境
(一)安装必要的软件包
TWRP的编译,需要一系列软件包支持。在Ubuntu下,使用apt
命令即可一次就安装好:
1 |
|
Arch Linux则安装以下软件包:
1 |
|
(二)配置ccache
ccache
是一个缓存工具,它通过将编译产生的中间文件(预处理得到的代码、输出文件*.o
等)缓存起来,待到下次编译同样源文件时直接复制而不是重新生成,以此来提高编译效率。最直接的好处,就是在make clean
之后,重新编译的速度能够快不少。
波浪线“~
”是Home目录的简写。我们在Bash的配置文件~/.bashrc
的尾部加上以下语句,启用ccache
。ccache
默认存放在用户目录下(~/.ccache
),可以更改环境变量CCACHE_DIR
,以设置到其他磁盘分区。
1 |
|
然后重启终端,或运行source ~/.bashrc
,或新开一个Bash会话,使上述语句生效。
另外,可以设置ccache
缓存所占磁盘空间的大小:
1 |
|
(三)对于新系统环境的向后兼容
值得留意的是,本文将要使用的OmniROM 8.1源码树,至今仍然依赖Python 2.7与ncurses 5.x(主要是Clang编译器),然而Arch Linux的官方源已经不提供。须手动添加Arch Linux CN源,然后再安装:
1 |
|
第二步:下载Android源代码
编译TWRP离不开Android源代码,因为它依赖Android源码中的组件。推荐使用OmniROM,它与TWRP的开发团队TeamWin有官方合作关系,由OmniROM持有TWRP的最新源代码。
(一)下载repo
repo
是谷歌开发的软件仓库管理工具,使用Python 2.7编写,用于批量管理由Git组织的源代码。使用以下的命令,把repo
下载到被PATH
所包含的/usr/bin
目录中:
1 |
|
(二)下载OmniROM源代码
首先在磁盘中新建一个专门的目录,用于存放OmniROM源代码,然后使用repo init
初始化源代码仓库。
1 |
|
-b
参数指定你需要的Android版本。一般编译3.0.x
系列版本用android-6.0
即可,但是更新的版本则需要android-7.0
及更高。笔者强烈建议只选择最新的TWRP版本——3.3.1-0
,因此对应地,使用android-8.1
或android-9.0
。
初始化完成后,我们开始下载:
1 |
|
根据网络状况和电脑性能,整个过程会需要几个小时甚至半天以上的时间,耐心等待即可。下载完成后,omni9
目录中就会多出包括.repo
在内的很多文件夹。
(三)小贴士
可以使用
-j
参数多开下载进程,适当提高下载效率。1
repo sync -j8
如果下载过程中发生错误,可以加上两个参数,让
repo
遇到错误仍然继续下载。-f
使得遇到网络错误时仍然继续,--force-sync
使得遇到冲突时仍然继续。1
repo sync -f --force-sync
(四)备选方案——Minimal Manifest for TWRP
如果磁盘空间不足,不妨考虑Minimal Manifest for TWRP,它只包含了编译TWRP所需的最少组件,并且也包含了TWRP源码及所需依赖。项目的地址在这里:https://github.com/minimal-manifest-twrp。
以下载Omni 8.1的Minimal Manifest为例,按照下面的步骤就可以下载到精悍、省空间、省资源的代码树:
1 |
|
注意:
实测twrp-6.0、twrp-8.1可以正常编译。而最新的twrp-9.0由于错误检测严格,会导致各种各样的编译错误出现,不建议选择!
第三步:下载TWRP源代码
注意:
如果使用上文的Minimal Manifest for TWRP,则请跳过本步,因为Minimal Manifest for TWRP的manifest包含了TWRP源码!
而它的twrp-8.1分支也包含了Busybox的源码,因此无需再添加Busybox的项目。但是twrp-9.0却因不明原因未包含,不建议选择。
一般地,OmniROM源码树并未包含TWRP的源码,默认下载的是AOSP的Recovery。因此,我们需要手动下载TWRP源码,并将其添加到repo
的仓库配置文件(manifest)中。TWRP的源码位于https://github.com/omnirom/android_bootable_recovery。
以下有两种更改OmniROM仓库配置的方案,任选其一。(Android 9.0建议选择第一种,以避免意想不到的问题。)
方案一:更改本地仓库配置文件
本地仓库配置文件位于源码树的.repo/local_manifests/
目录。它原本的作用,是将OmniROM官方支持机型所涉及到的所有源码仓库地址收集起来,存储到其中的roomservice.xml
中,以便于日后用repo sync
来一并进行更新。它不会随着Android官方仓库配置(.repo
目录下的其他XML manifest文件)的更新而被更改,因此建议在其中添加与TWRP有关的仓库地址。
假设源码树根目录为omni9
。首先,进入omni9/.repo
,检查其中是否有**local_manifests
这个目录,该目录中是否有roomservice.xml
**这个文件。如果没有,就手动创建一个,内容如下:
1 |
|
然后,对默认配置文件omni9/.repo/manifests/default.xml
进行如下修改,以删除AOSP Recovery对应的manifest项目:
1 |
|
最后回到omni9
目录,运行下面命令,将TWRP相关的源码下载下来:
1 |
|
方案二:在默认配置文件中添加TWRP相关项目
这一方案在之前的Android 8.1源码中起作用。假设源码树根目录为omni8
。
首先,进入源码树,删除原先下载的AOSP Recovery:
1 |
|
然后克隆TWRP的Recovery源码:
1 |
|
随后,删除AOSP Recovery对应的manifest项目。打开omni8/.repo/manifests/default.xml
,进行如下修改:
1 |
|
再把TWRP加入manifest。打开omni8/.repo/manifests/omni-default.xml
,在</manifest>
节的最后进行如下修改:
1 |
|
这样,我们就可以编译TWRP了;并且下一次我们也能通过repo sync
,将TWRP一并更新。
第四步:收集配置文件
(一)配置文件的组成
编译TWRP,离不开设备的配置文件。设备配置文件一般包括以下部分,以下所提及的路径均以Android源码根目录为参照:
设备配置参数
设备配置参数位于
device
目录下,定义设备的一系列基本信息。它由一系列Makefile文件(*.mk
)与设备特定的源代码组成。内核源码
内核源码位于
kernel
目录下,会在Android编译的同时一并编译。值得注意的是,并不是所有的设备都有对应的源代码,有些设备使用预编译的内核(prebuilt kernel),一般位于配置参数目录中。厂商配置参数
厂商配置参数位于
vendor
目录下,存放厂商特定的配置信息、预编译的各种文件(可执行文件、运行库等,通常不开源)等。相当一部分设备只需在编译整个Android系统时才须用到厂商配置文件,编译TWRP时不需要。
(二)在GitHub上搜索配置文件
怎样获得你设备的配置文件呢?去GitHub吧!GitHub上一般都会有各种设备的各类配置文件,善用搜索即可。例如,笔者的手机是华为P6(型号为P6-C00
),那么在GitHub中,使用以下关键字搜索上述三类配置文件:
- 配置参数:
device p6
或device huawei p6
- 内核源码:
kernel p6
或kernel huawei p6
- 厂商配置参数:
vendor p6
或vendor huawei p6
值得注意的是,很多设备都有自己的代号,而开发者在GitHub上发布配置文件时,往往只会用代号来表示设备。如,小米Max的代号是hydrogen
或helium
,三星Galaxy S5 国行双卡的代号是kltechnduo
,在这样的情况下,你就不能用mimax
、galaxy s5
或G9008W
为关键字来搜索配置文件。要查找代号与设备的对应关系,你可以去魔趣下载页面、Lineage OS下载页面、Resurrection Remix下载页面等开源ROM网站查询之。
不同的开源ROM,所适用的配置文件往往各不相同,配置文件中支持的参数也往往各异。由于我们使用OmniROM作为编译TWRP的载体,因此最好能找到适用于OmniROM的配置文件——也就是配置参数目录中带有omni_<设备名>.mk
的那一个。如果实在找不到,则请参阅下一步“修改配置文件”中的方法。
(三)把配置文件放到相应目录下
在GitHub上找到一个可用的repository之后,直接git clone
到相应位置即可。如何确认“相应位置”?其实很简单。
设备配置参数目录的规范是device/<厂商名>/<设备名>
,内核源码和厂商配置参数的路径类似。例如,笔者手上华为P6的配置文件目录如下:
- 设备配置参数:
device/huawei/hwp6_u06
- 内核源码:
kernel/huawei/hwp6_u06
- 厂商配置参数:
vendor/huawei
根据AOSP的规则,Android源码目录下的各个repository有固定的命名规范,这一规范就是将上述路径的“/
”换成“_
”,因此也很容易猜出你找到的repository该放在哪个目录。但是如果你碰到未按规范命名的repository,则必须按照上面的规则,推知你该放置的目标目录。
第五步:修改配置文件
我们获取到的现成的配置文件,不一定都是开箱即用的。它们在诞生之初,并不都是为我们现成的这套Android源代码设计的,能开箱即用的仅限于少数热门机型,大部分配置文件只适用于不同版本的OmniROM,甚至其他的ROM——典型的如CyanogenMod。因此,修改配置文件,是适配TWRP的必修课。
(一)判断配置文件是否能够直接适用
如果你的设备幸运地为TWRP官方所支持,那么在修改配置文件上,你就不必花费太多功夫,开箱使用即可。可以在TWRP官网中查找你的机型。
若不为官方所支持,也不用灰心,可能会有开发者进行非官方适配。只需在GitHub你找到的设备参数repository中,查看是否有当前OmniROM版本对应的分支(branch)。理论上,适合于Android 7.0及以上的版本可直接套用于OmniROM 8.1。
(二)修改BoardConfig.mk
BoardConfig.mk
是设备参数文件的组成部分,其中存放着不少与boot.img
与Recovery编译的参数。正确设置这些参数,是保证TWRP正常编译的前提。Recovery和boot.img
性质相同,均为Android的启动映像。
注:
- 以下所有目录均以Android源码根目录为参照。
- 由于排版限制,下面的设置未包括取值要求说明。写着“是否”的为布尔值,取值为$true$或$false$;其余为数字值或字符串值,可以不添加引号。
1. 内核打包参数
这些参数,控制着内核映像文件(kernel image)打包进入启动映像的工作。它们一般都被开发者提前设置好,不需改动。启动映像通过mkbootimg
生成,它的源代码位于system/core/mkbootimg
中。
参数名 | 说明 |
---|---|
BOARD_KERNEL_CMDLINE |
内核的运行参数 |
BOARD_KERNEL_BASE |
内核在启动映像中的基址 |
BOARD_KERNEL_PAGESIZE |
内核的页面大小 |
BOARD_MKBOOTIMG_ARGS |
需要传递给mkbootimg 工具的额外参数 |
BOARD_BOOTIMAGE_PARTITION_SIZE |
启动分区的大小 |
BOARD_RECOVERYIMAGE_PARTITION_SIZE |
Recovery分区的大小 |
BOARD_CUSTOM_MKBOOTIMG |
一些特殊的设备需要用专门的mkbootimg 工具来生成启动映像(如瑞芯微)。在这里指定该工具的路径。 |
BOARD_CUSTOM_BOOTIMG_MK |
对于一些格式特殊的启动镜像,用户可以自己编写Makefile。在这里指定自定义的Makefile文件路径。 |
注意:
BOARD_CUSTOM_MKBOOTIMG
不再适用于Android 8.0及以上版本,添加该参数会导致报错。BOARD_CUSTOM_BOOTIMG_MK
可能不受Android 8.x编译系统支持而引起报错,但在Android 9.0中不存在问题,可放心使用。
2. 内核编译参数
各类第三方开源ROM的开发者都建议你自己编译内核,而不是使用设备参数文件中预先编译好的内核映像(prebuilt kernel image,预编译内核)。这是因为已编译的内核映像无法修改,只适用于某个特定版本的系统,一旦放到一个新系统中就无法正常工作,甚至直接无法开机。如果你找到的设备参数文件提供了预编译的内核,且你能够找到内核源码,请设置下面的选项。
参数名 | 说明 |
---|---|
TARGET_KERNEL_SOURCE |
指定内核源码所在的目录 |
TARGET_KERNEL_CONFIG |
指定编译内核使用的配置文件。 配置文件位于内核源码 arch/<系统平台>/configs 中 |
BOARD_KERNEL_IMAGE_NAME |
指定内核映像名。Android编译系统根据它来查找内核映像 编译而成的内核映像位于内核源码 arch/<系统平台>/boot 中 |
KERNEL_TOOLCHAIN |
指定用于编译内核的交叉工具链。有些设备比较特殊,使用Android源码自带的编译器编译的内核无法启动,必须使用专用或旧版本的编译器 |
TARGET_KERNEL_CROSS_COMPILE_PREFIX |
与KERNEL_TOOLCHAIN 配合使用,指定交叉工具链的前缀 |
但是,如果你实在无法找到内核源码,你也可以指定下面的参数,以使用现有的内核(如从能正常运行的boot.img
与recovery.img
中提取出来的内核,或设备参数文件提供者提供的内核)。不过不能保证在新版本的系统下正常使用!
参数名 | 说明 |
---|---|
TARGET_PREBUILT_KERNEL |
指定预编译内核的路径 |
TARGET_PREBUILT_RECOVERY_KERNEL |
指定用于Recovery的预编译内核路径 |
注意:
- 内核源码与预编译内核只能二选一,不能同时设置上面两个表格中的所有参数!
- 根据不同平台,
BOARD_KERNEL_IMAGE_NAME
会有不同的取值。ARM平台为zImage
,x86平台为bzImage
,采用U-Boot的平台(如NXP、树莓派)为uImage
。
3. Recovery相关选项
BoardConfig.mk
中也包括了设置Recovery的若干选项,其中主要的参数如下所示。一般设备参数文件提供者都已经设置好了相应的参数。
参数名 | 说明 |
---|---|
TARGET_RECOVERY_PIXEL_FORMAT |
指定Recovery显示的像素格式。不同的设备有不同的像素格式,常见的有RGB_8888 、RGB_565 等,设置不当会引起花屏、黑屏等故障。 |
TARGET_RECOVERY_FSTAB |
指定Recovery分区表信息文件(fstab )的路径。该文件记载了可供挂载的分区信息,用户可在Recovery的“挂载”页面中选择是否挂载它们。 |
BOARD_RECOVERY_SWIPE |
启用滑动操作,在非触屏Recovery中可以允许用户上下滑动屏幕来移动高亮选项。一般启用。 |
DEVICE_RESOLUTION |
指定设备的分辨率,格式为宽x高 ,例如1080x1920 。 |
RECOVERY_GRAPHICS_USE_LINELENGTH |
Recovery图形显示时使用“行距”。具体作用笔者尚还不清楚,但是该选项若设置不当,会导致Recovery花屏。 |
BOARD_HAS_SDCARD_INTERNAL |
设置设备是否有内置SD卡。现阶段的新设备均拥有至少8GB的eMMC存储,都将内置存储的/data/media/0 划为内置SD卡。 |
RECOVERY_SDCARD_ON_DATA |
在Recovery中,确定SD卡位于data 分区。现阶段的新设备都将内置存储的/data/media/0 划为内置SD卡。与上面的BOARD_HAS_SDCARD_INTERNAL 呼应。 |
TARGET_RECOVERY_INITRC |
指定自己的init.rc 路径。init.rc 是Android初始化程序init 最主要的脚本,起到main() 函数的作用。该选项允许用户编写自己的init.rc ,以支持各种客制化的设备平台。注意: 该选项仅适用于AOSP官方Recovery,以及Android 6.0之前的旧版本Recovery(如TWRP 2.x、ClockworkMod)。新版本的TWRP(≥3.0)会直接忽略该选项,只使用它提供的 init.rc 。 |
4. TWRP专用选项
TWRP有专属的一些选项,部分选项如下所示。
参数名 | 说明 |
---|---|
TW_THEME |
指定TWRP的主题。不同的主题决定TWRP显示的不同样式,包括分辨率、屏幕方向等。 默认可选的主题有: portrait_hdpi 、portrait_mdpi 、landscape_hdpi 、landscape_mdpi 、watch_mdpi 。必须设置,否则编译过程中TWRP的编译规则会报错! |
TW_CUSTOM_BATTERY_PATH |
指定电池路径。电池路径为内核系统目录/sys 中电池设备所在的路径,TWRP访问它以显示电池电量。例:华为P6的路径是 /sys/devices/platform/bq_bci_battery.1/power_supply/Battery 。 |
TW_BRIGHTNESS_PATH |
指定亮度路径。亮度路径为内核系统目录/sys 中屏幕调节文件所在的路径,TWRP编辑它以更改屏幕亮度。例:华为P6的路径是 /sys/devices/platform/k3_fb.1/leds/lcd_backlight0/brightness 。 |
TW_DEFAULT_BRIGHTNESS |
指定默认亮度。取值范围为$[0,255]$。 |
TW_MAX_BRIGHTNESS |
指定最大亮度。取值范围为$[0,255]$。 |
TW_FLASH_FROM_STORAGE |
该参数作用未知,可能仅适用于2.x 版本。在3.2.3-0 版本中已经失效。 |
TW_EXTERNAL_STORAGE_PATH |
指定外部存储器的挂载路径。 |
TW_EXTERNAL_STORAGE_MOUNT_POINT |
指定外部存储器的挂载点。 |
TW_DEFAULT_EXTERNAL_STORAGE |
指定是否将默认存储器设为外置存储。在3.2.3-0 版本中已经失效。 |
TW_EXCLUDE_SUPERSU |
指定是否不包含SuperSU。包含了SuperSU的TWRP会在每次重启时提示用户Root手机。 |
TW_INCLUDE_NTFS_3G |
指定是否包含NTFS-3G模块,以支持NTFS分区。 |
TW_IGNORE_MISC_WIPE_DATA |
指定是否忽略从Bootloader传递而来的清除data 分区的指令。这里的misc 分区存放了Bootloader传递给启动映像(boot 或recovery )的指令,可以控制它们启动的行为。 |
TW_EXTRA_LANGUAGES |
指定是否增加额外的语言。额外的语言包括中文、日本语等。默认情况下TWRP只会包含英语与若干欧洲语言(如德语、法语、俄语、丹麦语等)。 |
5. 加密相关选项
现今能购买到的手机,大多都已对data
分区进行了加密,要想在系统中读取data
分区,必须有一个解密的过程。官方系统(包括Recovery)的启动就包含了解密过程;而TWRP要想读取data
分区,则必须设置好下面的选项,并包含用于解密的其他组件。
笔者知道的加密方案有两种:高通的QSEECOM加密,与华为的专用文件系统强制加密(基于F2FS)。其中只有前者受到TWRP广大开发者的支持,TWRP的很多大神都已给自己负责的机型加入了高通的加密组件。具体给你的高通处理器机型增加加密功能的方法,笔者会择日写上教程。(给小米Max的官方TWRP适配高通解密组件的开发者,就是我!)
参数名 | 说明 |
---|---|
TW_INCLUDE_CRYPTO |
指定TWRP是否包含加密组件,并启用加密解密功能 |
TARGET_HW_DISK_ENCRYPTION |
指定设备是否包含硬件加密功能。现阶段启用加密的设备,一般都是硬件加密 |
TARGET_KEYMASTER_WAIT_FOR_QSEE |
对于高通方案,指定在Recovery启动时是否等待高通加密服务程序qseecomd 完成解密。必须开启,否则TWRP的解密功能形同虚设 |
6. 调试相关选项
TWRP支持logcat
调试功能,可以如同在Android系统里一样读取logcat
日志。
参数名 | 说明 |
---|---|
TWRP_INCLUDE_LOGCAT |
指定是否在TWRP中包含logcat |
TARGET_USES_LOGD |
指定是否在TWRP中启用日志服务logd |
(三)对非OmniROM配置文件的修改
并不是所有的设备都拥有适用于OmniROM的配置文件,因此还需将适用于其他ROM的配置文件进行一番修改。这种情况通常出现在年代略微久远的老设备上,它们往往只有CyanogenMod 4.x等老版本ROM的配置文件。不过,修改过程并不复杂。
1. 修改产品Makefile(product Makefile)
每个ROM的设备参数文件都有一个ROM特定的“产品 Makefile”,它定义了设备的基本信息,起到当之无愧的“门户”作用。
(1)设备Makefile的定义
我们来分析一下这类Makefile。它可以分为三个部分——继承部分、设备定义部分、用户自定义部分。一个示例的设备Makefile如下所示(设备为华为荣耀10 View,省略开头的Apache 2.0协议内容):
1 |
|
a) 继承部分
继承部分,指的是上述源文件中调用inherit-product
或inherit-product-if-exists
函数的部分,使得当前设备配置文件继承其他的配置文件。被继承的,包括:
- ROM提供的通用(generic)配置文件(位于
build/make/target
目录中) - 设备专门的配置文件(通常在设备参数文件目录下,以
device_<设备名>.mk
或device.mk
为文件名) - 厂商公用的配置文件(通常位于
vendor/<厂商名>/<设备名或芯片型号>
目录下)
在适配TWRP时,一般只需把其他已适配TWRP设备的代码搬来用即可,删去其他ROM使用的配置文件。一般典型的继承部分代码如下:
1 |
|
b) 设备定义部分
设备定义部分是整个设备配置文件的核心,是编译系统查找设备参数文件的依据。包括以下变量,缺一不可:
变量名 | 说明 |
---|---|
PRODUCT_DEVICE |
设备名。必须填写,这是核心参数,用于编译系统对设备名的标识! |
PRODUCT_NAME |
产品名,通常格式为omni_<PRODUCT_DEVICE> 必须填写,编译系统根据这个来为你配置编译环境! |
PRODUCT_BRAND |
品牌名 |
PRODUCT_MODEL |
设备型号。会显示在系统设置的“关于设备”中 |
TARGET_VENDOR |
厂商名 |
c) 用户自定义部分
以上两类代码之外的其他代码,就属于用户自定义的代码了。可以放置其他的参数。
(2)给设备Makefile改名
根据ROM的不同,设备Makefile会有不同规格的文件名。常见的如下所示:
- OmniROM:
omni_<设备名>.mk
- CyanogenMod:
cm.mk
- 魔趣:
mokee.mk
(旧版本)、mk_<设备名>.mk
(Android 9)
尽管文件名不同,但它们实际上大同小异。只需将名字统一重命名为omni_<设备名>.mk
即可。注意文件名中的“设备名”即该文件中PRODUCT_DEVICE
的值,否则编译系统会报错找不到文件并中止。
2. 修改设备专门Makefile(device.mk)
除了有产品Makefile之外,设备树常常会另设一个设备专门配置文件,名字各异,包括但不限于:
device.mk
full_<设备名>.mk
device_<设备名>.mk
(1)如何使用
它由产品Makefile引用。例如,上一节荣耀10 View产品Makefile中,通过以下的语句来引用。
1 |
|
注意:
由于该文件可能还包含着另一组设备定义,因此引用语句必须放在产品Makefile的设备定义前,否则会导致混乱。
(2)主要内容
这类设备配置文件常常包括(但不限于)以下内容:
a) 复制文件
可以设置变量PRODUCT_COPY_FILES
,将特定的文件复制到设备ROM的目录中,通常采用以下格式:
1 |
|
其中,源文件和目标目录有明确要求:
- 源文件:采用相对目录,以Android源码根目录为参照。例如
device/<厂商名>/<设备名>
。 - 目标目录:同样采用相对目录,以最终输出目录为参照。例如
out/target/product/<设备名>/
。
一般使用PRODUCT_COPY_FILES
来复制厂商专属的文件(如刷机程序)、我们定制的配置文件(如后文会讲到的init
脚本)等。
b) 继承部分
类似于上文的产品Makefile,有些设备树作者也会在设备专门Makefile中,放置继承代码。原则上,继承部分的代码只需要写在产品Makefile开头,毕竟设备Makefile是要被前者引用的;而有的继承部分代码与新版OmniROM不兼容,需要删改。
其一,如果是厂商的继承代码,则可以保留,例如:
1 |
|
inherit-product-if-exists
函数不会因为找不到厂商配置文件而报错。
其二,如果是ROM通用配置文件,则先检查产品Makefile有没有相应的代码。若产品Makefile已有,则删除设备Makefile中的相应代码;否则就要按照上一节的方法,把继承部分补充到产品Makefile开头。
其三,有些代码已经过时,还会导致莫名其妙的问题,必须删除,例如:
1 |
|
c) 用户自定义部分
在这里也可以指定你自己的参数,例如补充BoardConfig.mk
中的一些设备参数。不过,为防止冲突,笔者不建议这么做。
2. 明确设备参数文件Makefile的调用链
Makefile(*.mk
)的调用,存在一个链的关系。这条调用链的起点是编译系统负责管理设备配置文件的Makefile程序——build/make/core/product_config.mk
,它会检测设备配置文件目录中的AndroidProducts.mk
,并根据AndroidProducts.mk
来定位到设备的“元Makefile”。
AndroidProducts.mk
AndroidProducts.mk
是设备配置文件的入口。编译系统通过查找device
目录下所有的AndroidProducts.mk
,来确定某一款设备确实有其配置文件存在。该文件用于将设备Makefile包含进来,包含的方法是将它传递给变量PRODUCT_MAKEFILES
。通常的写法如下所示:
1 |
|
product_config.mk
product_config.mk
工作逻辑的其中一步,就是在接受用户输入的设备配置名后,在device/
目录中检索配置文件,若配置文件不存在,就会报错。相关代码如下所示(保留原始注释,并增加笔者的进一步说明):
1 |
|
总的调用关系
根据上述分析,我们不难总结出下面的调用关系:
build/make/core/product_config.mk
读取设备参数文件目录device/
下所有的AndroidProducts.mk
,得到设备Makefile的列表。product_config.mk
在列表中搜索用户指定设备(如华为荣耀10 View)的设备Makefile(omni_berkeley.mk
)。- 若能搜索到,则从设备Makefile中读取设备信息,配置好编译环境;否则报错退出。
3. 移植设备初始化文件(init.rc)
Android的初始化程序init
会在启动时读取一个名为init.rc
的脚本,用于初始化系统、厂商软硬件。在早期版本的Android(6.0及更早版本,尤其是4.4.x及之前的版本),该文件需要厂商自行编写,涵盖系统启动全流程。
但在OmniROM 8.0及配套的TWRP中,init.rc
中负责初始化系统的代码由TWRP提供,不再需要自行编写。原本在OmniROM 6.0仍适用的TARGET_RECOVER_INITRC
参数也失效,官方建议采用专门的厂商初始化脚本(init.recovery.<厂商名称>.rc
)来完成厂商软硬件的初始化。
由此,如果你的设备树有原厂init.rc
,那么就要把该文件中厂商相关的代码统统移动到init.recovery.<厂商名称>.rc
中。简单来说,去掉系统初始化部分后,剩下的就是厂商自己的代码。
第六步:开始编译
配置文件修改完成后,我们就可以立刻开始编译了。
在Android源码根目录下,首先初始化编译环境:
1 |
|
如果是Minimal Manifest,则请设置下面的环境变量,否则会因找不到依赖而报错:
1 |
|
Omni 8.1的源码树仍然依赖Python 2,因此请把Python 2添加到环境变量中。笔者采用的方法是:在源码树下新建目录,在其中给Python2的可执行文件建立链接,再把它添加到PATH中。
1 |
|
然后,运行lunch
命令,选择编译目标。也可以直接运行lunch <下面菜单中的一个编译目标>
。
1 |
|
最后,开始编译TWRP:
1 |
|
编译而成的Recovery为out/target/product/<设备名>/recovery.img
,直接使用fastboot
等工具刷入即可。
第七步:调试查错,修改代码
适配TWRP永远都不会是一个一蹴而就的事情,这就意味着你不可能一次就成功。潜在的各种错误,会潜伏在编译过程、运行过程乃至启动成功后的每一个角落,你要做的,就是随时查错。
(一)编译时出错
编译过程中出错,是最容易排查的。一言以蔽之,就是——“发现一个错误,解决一个错误”。所有的错误,都会在终端输出中显示出来,你只需要观察错误的输出,然后根据它的提示解决即可。自Android 7.0起引入的ninja
构建工具,会在出错时输出产生错误的命令,以“FAILED:
”前缀标明之,只需在出错时搜索“FAILED:
”,即可快速定位出错点。
(二)运行前出错
成功通过编译后,得到的Recovery很可能不能正常启动,表现为自动重启、黑屏等。这个时候,最直接的查错办法,就是拿到内核日志。
1. 获取内核日志的方法
一般来说,内核只要出现了panic,就会“想方设法”把崩溃时的内核日志记录下来。不同的平台、不同的内核,有不同的获取内核日志的方法。大致梳理如下。
- 高通:内核日志存储在
/proc/last_kmsg
中。 - 瑞芯微(如RK3188):与高通相同。
- 海思早期芯片(如K3V2):存储在内核参数
CONFIG_APANIC_PLABEL
所制定的分区中,一般是splash
。
只需在正常启动的系统中(或将另一个正常启动的老Recovery刷入boot
分区)用cat
命令读取它们即可。
2. init
阻碍内核日志记录的坑爹设计
一些造成panic的启动故障发生于Android初始化程序init
的运行过程中。然而, init
有一个很坑爹的设计,就是在使用调试方式构建的ROM中,若遇到panic则自动重启进入Bootloader。官方说法是“有利于调试”,但是如此重启却会导致内核无法转存日志。因此有必要魔改掉这个功能。
打开init
所在的目录system/core/init
,应用如下git diff
补丁即可。
1 |
|
应用补丁的方法:将上述代码保存为system/core/init/no_reboot_into_bootloader.diff
,然后在system/core/init
目录中运行以下命令:
1 |
|
(三)功能测试与Bug修复
如果你成功地过五关斩六将,久违的TWRP启动画面出现在你设备的屏幕上了,那么衷心地祝贺你!接下来你要做的,就是更进一步,检查TWRP的各项功能,捕捉可能影响使用的Bug。
一般地,在TWRP中,你可以进行以下单元测试:
挂载是否正常
进入主菜单的“Mount”(挂载),点击每个分区的选项,检查是否能够正常挂载。
解密是否正常
对于高通设备,观察解密是否正常的最直接方法,是看日志中是否有解密失败(decryption failed)的提示,以及“Mount”菜单中的
data
分区是否无法挂载。是否能连接
adb
连接电脑,运行
adb devices
是否能检测到设备处在Recovery状态下,运行adb shell
是否能进入设备上的终端,并拥有Root权限(提示符为“#
”)。是否能连接MTP
连接电脑,检查是否有一个MTP设备出现在“此电脑”(Windows)或文件管理器边栏(Ubuntu)中。如未出现,在“Mount”中点击“启用MTP(Enable MTP)”。
是否支持OTG
2014年起的大部分主流机型都支持OTG。你可以插入一根OTG数据线,然后在TWRP终端中运行
lsusb
,看看是否检测到一个USB设备。此时再插入一个新设备,再看看lsusb
的输出里是否多出了另外一个设备。如果插入的是U盘,还可以检查/dev/block/
下是否多出了块设备sdx
(x
为任意一个小写英文字母)。是否能备份/恢复系统
测试一下系统是否能正常备份和恢复,检查可选的备份分区是否包含必要的分区(
system
、data
、boot
、recovery
)。
注意事项
编译系统对Shell的支持
原则上,操作用的Shell只能是Bash。如果使用其他Shell,那么就会在运行一些命令时出现难以预料的Bug——例如,在lunch
选单中,输序号选择了设备一,最终Shell会给我们选择设备三,很坑。对此,编译系统会在source build/envsetup.sh
的过程中,对使用Bash之外的Shell提出警告。
但是,从Android 9.0开始,编译系统就能够支持Bash之外的第三方Shell了,比如Zsh。因此,如果你的Shell是Zsh(尤其是Oh-My-Zsh),敬请放心使用。
拓展阅读
[^1]: 实际上Mac OS X也可以。
- 本文作者: 爱拼安小匠
- 本文链接: https://anclark.github.io/2018/10/03/Android_Adapting_Guide/TWRP-Recovery-编译适配教程/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0(署名-非商用-禁止演绎 3.0) 许可协议。转载请注明出处!