减小 OTA 大小
本页介绍了为减少多次编译之间不必要的文件变更而对 AOSP 编译系统所做的改动。使用专有编译系统的设备实现人员可根据这项信息采取措施,减小无线下载 (OTA) 更新的大小。
有时,Android OTA 包含的变更文件并非源于代码变更,而是编译系统造成的。在不同时间、不同目录或不同机器上编译相同的代码时可能会发生上述情况,产生大量变更文件。这些多余的文件不仅会增加 OTA 的大小,还会导致难以确定 OTA 中发生变更的代码。
为了使 OTA 的内容更加透明,我们对 AOSP 编译系统做了多项改动,目的是消除多次编译之间不必要的文件变更,以此减小 OTA 的大小。这样做是为了减小 OTA 的大小,使其只包含与 OTA 中所含补丁程序相关的文件。AOSP 还包括编译 diff 工具(可过滤出常见的编译相关文件变更,并提供更清晰的编译文件 diff)以及块映射工具(可协助您确保块分配的一致性)。
编译系统可能会通过多种方式创建不必要的文件 diff。下文讨论了其中一些问题和解决方案,并尽可能提供了 AOSP 中的修复示例。
文件顺序
问题:文件系统在请求目录中的文件列表时,并不保证文件顺序,尽管对于同一个检出,文件顺序通常是相同的。ls
等工具在默认情况下会对结果进行排序,但 find
和 make
等命令使用的通配符函数却不会对结果进行排序。用户在使用这类工具之前,务必要对输出进行排序。
解决方案:用户在使用支持通配符的 find
和 make
等工具之前,务必要对这些命令的输出进行排序。要在 Android.mk
文件中使用 $(wildcard)
或 $(shell find)
,也应该进行排序。有些工具(如 Java)确实会对输入进行排序,因此有必要先对排序进行验证。
示例:多处问题在核心编译系统中通过内置的 all-*-files-under
宏得到修正,其中包括 all-cpp-files-under
(一些定义分散在其他 makefile 中)。有关详情,请参阅以下 CL:
- https://android.googlesource.com/platform/build/+/4d66adfd0e6d599d8502007e4ea9aaf82e95569f
- https://android.googlesource.com/platform/build/+/379f9f9cec4fe1c66b6d60a6c19fecb81b9eb410
- https://android.googlesource.com/platform/build/+/7c3e3f8314eec2c053012dd97d2ae649ebeb5653
- https://android.googlesource.com/platform/build/+/5c64b4e81c1331cab56d8a8c201f26bb263b630c
编译目录
问题:变更编译内容所在的目录会导致二进制文件有所不同。Android 编译系统中的大多数路径都是相对路径,因此 C/C++ 中的 __FILE__
不是问题。不过,默认情况下调试符号会对完整的路径名进行编码,而对预剥离二进制文件进行哈希处理会生成 .note.gnu.build-id
,因此调试符号发生变更会致使二进制文件发生变化。
解决方案:AOSP 现在会使调试路径变成相对路径。有关详情,请参阅 CL:https://android.googlesource.com/platform/build/+/6a66a887baadc9eb3d0d60e26f748b8453e27a02。
时间戳
问题:编译输出中的时间戳会导致不必要的文件变更。这可能会发生在以下位置:
- C 或 C++ 代码中的
__DATE__/__TIME__/__TIMESTAMP__
宏。 - 基于 ZIP 的归档中嵌入的时间戳。
解决方案/示例:要从编译输出中移除时间戳,请遵循下文中的说明操作。
C/C++ 中的 __DATE__/__TIME__/__TIMESTAMP__
这些宏总是为不同的编译生成不同的输出,因此不建议使用。您可以选择通过以下方法来移除这些宏:
- 直接将其移除(这些宏通常不是必需的)。要查看示例,请参阅:https://android.googlesource.com/platform/system/core/+/30622bbb209db187f6851e4cf0cdaa147c2fca9f。
- 要对运行中的二进制文件进行唯一标识,请从 ELF 标头中读取 build-id。
- 要了解操作系统的编译时间,请读取
ro.build.date
(应该会对除增量编译之外的所有内容都适用;增量编译可能不会更新此日期)。要查看示例,请参阅:https://android.googlesource.com/platform/external/libchrome/+/8b7977eccc94f6b3a3896cd13b4aeacbfa1e0f84。
注意:Android 7.0 开启了 -Werror=date-time
,因此使用时间戳会导致编译错误。
归档文件(zip、jar)中的嵌入时间戳
Android 7.0 通过将 -X
添加到 zip
命令的所有用例中,解决了 zip 归档文件中嵌入时间戳的问题,因此编译工具的 UID/GID 和扩展的 Unix 时间戳不会嵌入到 ZIP 文件中。
新工具 ziptime
(位于 /platform/build/+/master/tools/ziptime/
下)会重置 zip 标头中的正常时间戳。有关详情,请参阅 README 文件。
signapk
工具为 APK 文件设置的时间戳可能会因服务器所在的时区而异。有关详情,请参阅 CL:https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028。
版本字符串
问题:APK 版本字符串的硬编码版本通常附加了 BUILD_NUMBER
。即使 APK 中并未发生任何其他变更,APK 也仍然会有所不同。
解决方案:从 APK 版本字符串中移除版本号。
示例:
- https://android.googlesource.com/platform/packages/apps/Camera2/+/5e0f4cf699a4c7c95e2c38ae3babe6f20c258d27
- https://android.googlesource.com/platform/build/+/d75d893da8f97a5c7781142aaa7a16cf1dbb669c
一致的编译工具
问题:生成安装文件的工具必须一致(相同的输入应始终生成相同的输出)。
解决方案/示例:以下编译工具需要进行变更:
- NOTICE 文件创建工具。NOTICE 文件创建工具需要变更。请参阅 CL:https://android.googlesource.com/platform/build/+/8ae4984c2c8009e7a08e2a76b1762c2837ad4f64。
- Java Android 编译器套件 (Jack)。Jack 工具链需要更新才能处理生成的构造函数排序的偶然性变更。请参阅 CL:https://android.googlesource.com/toolchain/jack/+/056a5425b3ef57935206c19ecb198a89221ca64b。
- ART AOT 编译器 (dex2oat)。ART 编译器二进制文件需要更新才能创建确定性映像。请参阅 CL:https://android.googlesource.com/platform/art/+/ace0dc1dd5480ad458e622085e51583653853fb9。
- libpac.so 文件 (V8)。每项编译会创建不同的
/system/lib/libpac.so
文件,因为 V8 快照会针对每项编译发生变更。解决方案是移除该快照。请参阅 CL:https://android.googlesource.com/platform/external/v8/+/e537f38c36600fd0f3026adba6b3f4cbcee1fb29。 - 预先经过 dexopt 处理的 (.odex) 应用文件。预先经过 dexopt 处理的 (.odex) 文件在 64 位系统上包含未初始化填充。请参阅 CL:https://android.googlesource.com/platform/art/+/34ed3afc41820c72a3c0ab9770be66b6668aa029。
使用编译 diff 工具
对于无法消除编译相关文件变更的情况,AOSP 纳入了编译 diff 工具 target_files_diff.py
,以用于比较两个文件包。该工具会在两个编译之间执行递归 diff,从而排除常见的编译相关文件变更,例如:
- 编译输出中的预期变更(例如,由于版本号变更所导致)。
- 由于当前编译系统中的已知问题所导致的变更。
要使用编译 diff 工具,请运行以下命令:
target_files_diff.py dir1 dir2
dir1
和 dir2
是包含每个编译的提取目标文件的基础目录。
使块分配保持一致
在非 A/B OTA 中,影响时间的因素之一是块移动。对于给定的文件,尽管其内容在两个编译之间会保持不变,但实际持有数据的块可能已发生变化。因此,更新程序会在 OTA 期间执行不必要的 I/O 来四处移动块。
为了解决这个问题,我们在 Android 7.0 中扩展了 make_ext4fs
工具,该工具会尝试使块分配在各编译之间保持一致。make_ext4fs
会接受可选的 -d base_fs
标记,该标记会在生成 ext4
映像时尝试将文件分配给相同的块。您可以从上一个编译的目标文件 zip 文件(IMAGES/system.map
和 IMAGES/vendor.map
)中提取块映射文件(即 base_fs
映射文件)。接下来,base_fs
文件便可以通过 PRODUCT_SYSTEM_BASE_FS_PATH
和 PRODUCT_VENDOR_BASE_FS_PATH
进行记录并指定。例如,
PRODUCT_SYSTEM_BASE_FS_PATH := path/to/base_fs_files/base_system.map PRODUCT_VENDOR_BASE_FS_PATH := path/to/base_fs_files/base_vendor.map
虽然这对减小整体的 OTA 更新包大小来说并无帮助,但它确实可以通过减少 I/O 量来改善 OTA 性能。