减小 OTA 大小

本页介绍了为减少多次编译之间不必要的文件变更而对 AOSP 编译系统所做的改动。使用专有编译系统的设备实现人员可根据这项信息采取措施,减小无线下载 (OTA) 更新的大小。

有时,Android OTA 包含的变更文件并非源于代码变更,而是编译系统造成的。在不同时间、不同目录或不同机器上编译相同的代码时可能会发生上述情况,产生大量变更文件。这些多余的文件不仅会增加 OTA 的大小,还会导致难以确定 OTA 中发生变更的代码。

为了使 OTA 的内容更加透明,我们对 AOSP 编译系统做了多项改动,目的是消除多次编译之间不必要的文件变更,以此减小 OTA 的大小。这样做是为了减小 OTA 的大小,使其只包含与 OTA 中所含补丁程序相关的文件。AOSP 还包括编译 diff 工具(可过滤出常见的与编译相关的文件变更,并提供更清晰的编译文件 diff)以及块映射工具(可协助您确保块分配的一致性)。

编译系统可能会通过多种方式创建不必要的文件 diff。下文讨论了其中一些问题和解决方案,并尽可能提供了 AOSP 中的修复示例。

文件顺序

问题:文件系统在请求目录中的文件列表时,并不保证文件顺序,尽管对于同一个检出,文件顺序通常是相同的。ls 等工具在默认情况下会对结果进行排序,但 findmake 等命令使用的通配符函数却不会对结果进行排序。用户在使用这类工具之前,务必要对输出进行排序。

解决方案:用户在使用支持通配符的 findmake 等工具之前,必须对这些命令的输出进行排序。要在 Android.mk 文件中使用 $(wildcard)$(shell find),也应该进行排序。有些工具(如 Java)确实会对输入进行排序,因此有必要先对排序进行验证。

示例:很多问题在核心编译系统中通过内置的 all-*-files-under 宏(其中包括 all-cpp-files-under,因为一些定义分散在其他 makefile 中)得到了修正。有关详情,请参阅以下 CL:

编译目录

问题:变更编译内容所在的目录会导致二进制文件有所不同。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__

这些宏总是为不同的编译生成不同的输出,因此不建议使用。您可以选择通过以下方法来移除这些宏:

归档文件(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 版本字符串中移除版本号。

示例:

一致的编译工具

问题:生成安装文件的工具必须一致(相同的输入应始终生成相同的输出)。

解决方案/示例:以下编译工具需要进行变更:

使用编译 diff 工具

由于有些情况下无法避免与编译相关的文件变更,为此 AOSP 纳入了编译 diff 工具 target_files_diff.py,以用于比较两个文件包。该工具会在两个编译之间执行递归 diff,从而排除常见的编译相关文件变更,例如:

  • 编译输出中的预期变更(例如,由于版本号变更所导致)。
  • 由于当前编译系统中的已知问题所导致的变更。

要使用编译 diff 工具,请运行以下命令:

target_files_diff.py dir1 dir2

dir1dir2 是包含每个编译的提取目标文件的基础目录。

使块分配保持一致

在非 A/B OTA 中,影响时间的因素之一是块移动。对于给定的文件,尽管其内容在两个编译之间会保持不变,但实际持有数据的块可能已发生变化。因此,更新程序会在 OTA 期间执行不必要的 I/O 来四处移动块。

为了解决这个问题,我们在 Android 7.0 中扩展了 make_ext4fs 工具,该工具会尝试使块分配在各编译之间保持一致。make_ext4fs 会接受可选的 -d base_fs 标记,该标记会在生成 ext4 映像时尝试将文件分配给相同的块。您可以从上一个编译的目标文件 zip 文件(IMAGES/system.mapIMAGES/vendor.map)中提取块映射文件(即 base_fs 映射文件)。接下来,base_fs 文件便可以通过 PRODUCT_SYSTEM_BASE_FS_PATHPRODUCT_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 性能。