常见问题解答

Google 是否在任何设备上使用过 A/B OTA?

是。A/B 更新的营销名称是无缝更新。2016 年 10 月推出的 Pixel 和 Pixel XL 手机搭载了 A/B 更新,所有 Chromebook 都使用相同的 update_engine A/B 实现。必要的平台代码实现已在 Android 7.1 及更高版本中公开。

为什么 A/B OTA 更好?

A/B OTA 在进行更新时提供更好的用户体验。每月安全更新的衡量结果表明,此功能已被证明是成功的:截至 2017 年 5 月,95% 的 Pixel 用户在一个月后运行的是最新的安全更新,而 Nexus 用户为 87%,并且 Pixel 用户比 Nexus 用户更快地更新。OTA 期间更新块失败不再导致设备无法启动;在新系统映像成功启动之前,Android 仍保留回退到先前正常工作的系统映像的功能。

什么是 system_other?

应用存储在 .apk 文件中,这些文件实际上是 ZIP 归档文件。每个 .apk 文件内部都有一个或多个 .dex 文件,其中包含可移植 Dalvik 字节码。.odex 文件(优化的 .dex)与 .apk 文件分开存在,并且可以包含特定于设备的机器代码。如果 .odex 文件可用,Android 可以以预先编译的速度运行应用,而无需等待每次启动应用时都编译代码。.odex 文件并非绝对必要:Android 实际上可以通过解释或即时 (JIT) 编译直接运行 .dex 代码,但如果空间可用,.odex 文件可提供启动速度和运行时速度的最佳组合。

示例:对于 Nexus 6P(运行 Android 7.1,总系统映像大小为 2628MiB(2755792836 字节))中的 installed-files.txt,按文件类型划分的总体系统映像大小最大贡献者细分如下

.odex 1391770312 字节 50.5%
.apk 846878259 字节 30.7%
.so(原生 C/C++ 代码) 202162479 字节 7.3%
.oat 文件/.art 映像 163892188 字节 5.9%
字体 38952361 字节 1.4%
icu 语言区域数据 27468687 字节 0.9%

其他设备的这些数据也类似,因此在 Nexus/Pixel 设备上,.odex 文件约占系统分区的二分之一。这意味着我们可以继续使用 ext4,但在出厂时将 .odex 文件写入 B 分区,然后在首次启动时将其复制到 /data。使用 ext4 A/B 的实际存储空间与 SquashFS A/B 相同,因为如果我们使用 SquashFS,我们会将预先优化的 .odex 文件随 system_a 而不是 system_b 一起发布。

将 .odex 文件复制到 /data 是否意味着 /system 上节省的空间会在 /data 上丢失?

不完全是。在 Pixel 上,.odex 文件占用的大部分空间用于应用,这些应用通常位于 /data 中。这些应用会接收 Google Play 更新,因此系统映像上的 .apk 和 .odex 文件在设备的大部分生命周期中都未使用。可以完全排除此类文件,并在用户实际使用每个应用时替换为小的、配置文件驱动的 .odex 文件(因此对于用户不使用的应用,不需要空间)。有关详情,请参阅 Google I/O 2016 演讲Art 的演变

比较很困难,原因有几个关键点

  • 通过 Google Play 更新的应用始终在收到首次更新后将其 .odex 文件放在 /data 上。
  • 用户不运行的应用根本不需要 .odex 文件。
  • 配置文件驱动的编译生成的 .odex 文件比预先编译的文件小(因为前者仅优化性能关键型代码)。

有关 OEM 可用的调整选项的详细信息,请参阅配置 ART

/data 上是否包含 .odex 文件的两个副本?

情况有点复杂... 在写入新的系统映像后,针对新的 .dex 文件运行新版本的 dex2oat,以生成新的 .odex 文件。这在旧系统仍在运行时发生,因此旧的和新的 .odex 文件同时位于 /data 上。

OtaDexoptService 中的代码(frameworks/base/+/main/services/core/java/com/android/server/pm/OtaDexoptService.java)在优化每个软件包之前调用 getAvailableSpace,以避免过度填充 /data。请注意,此处的可用仍然是保守的:它是达到通常的系统低空间阈值(以百分比和字节计数衡量)之前剩余的空间量。因此,如果 /data 已满,则不会有每个 .odex 文件的两个副本。相同的代码还具有 BULK_DELETE_THRESHOLD:如果设备接近于填充可用空间(如前所述),则会删除不使用的应用的 .odex 文件。这是没有每个 .odex 文件的两个副本的另一种情况。

/data 完全满的最坏情况下,更新将等待设备重新启动到新系统中,并且不再需要旧系统的 .odex 文件。PackageManager 处理此问题:(frameworks/base/+/main/services/core/java/com/android/server/pm/PackageManagerService.java#7215)。在新系统成功启动后,installdframeworks/native/+/main/cmds/installd/dexopt.cpp#2422)可以删除旧系统使用的 .odex 文件,从而使设备恢复到只有一份副本的稳定状态。

因此,虽然 /data 可能包含所有 .odex 文件的两个副本,但 (a) 这是暂时的,并且 (b) 仅当您在 /data 上有大量可用空间时才会发生。除了更新期间,只有一份副本。作为 ART 一般稳健性功能的一部分,它永远不会用 .odex 文件填充 /data(因为这在非 A/B 系统上也会成为问题)。

所有这些写入/复制操作不会增加闪存损耗吗?

只有一小部分闪存被重写:完整的 Pixel 系统更新会写入约 2.3GiB。(应用也会被重新编译,但这对于非 A/B 也是如此。)传统上,基于块的完整 OTA 写入的数据量相似,因此闪存损耗率应相似。

刷新两个系统分区是否会增加工厂刷新时间?

否。Pixel 的系统映像大小没有增加(它只是将空间分配到两个分区)。

将 .odex 文件保留在 B 上是否会使恢复出厂设置后的重启速度变慢?

是。如果您实际使用过设备、进行了 OTA 更新并执行了恢复出厂设置,则首次重启将比其他情况慢(在 Pixel XL 上为 1 分 40 秒 vs 40 秒),因为在首次 OTA 后,.odex 文件将从 B 中丢失,因此无法复制到 /data。这就是权衡。

与常规启动相比,恢复出厂设置应该是一种罕见的操作,因此所花费的时间不那么重要。(这不会影响从工厂获得设备的最终用户或评测人员,因为在这种情况下,B 分区可用。)使用 JIT 编译器意味着我们不需要重新编译所有内容,因此情况并不像您想象的那么糟糕。也可以使用清单中的 coreApp="true" 将应用标记为需要预先编译:(frameworks/base/+/main/packages/SystemUI/AndroidManifest.xml#23)。这目前由 system_server 使用,因为出于安全原因,不允许它进行 JIT 编译。

将 .odex 文件保留在 /data 而不是 /system 上是否会使 OTA 更新后的重启速度变慢?

否。如上所述,新的 dex2oat 在旧系统映像仍在运行时运行,以生成新系统需要的文件。在完成该工作之前,更新不会被视为可用。

我们能否(应该)发布 32GiB A/B 设备?16GiB?8GiB?

32GiB 效果很好,因为它已在 Pixel 上得到验证,并且 16GiB 中的 320MiB 意味着减少 2%。同样,8GiB 中的 320MiB 减少 4%。显然,在具有 4GiB 的设备上,A/B 不是推荐的选择,因为 320MiB 的开销几乎占总可用空间的 10%。

AVB2.0 是否需要 A/B OTA?

否。Android Verified Boot 一直需要基于块的更新,但不一定需要 A/B 更新。

A/B OTA 是否需要 AVB2.0?

否。

A/B OTA 是否会破坏 AVB2.0 的回滚保护?

否。这里存在一些混淆,因为如果 A/B 系统无法启动到新的系统映像,它将(在启动加载程序确定的若干次重试后)自动恢复到“先前的”系统映像。但这里的关键点是,A/B 意义上的“先前”实际上仍然是“当前的”系统映像。一旦设备成功启动新映像,回滚保护就会启动,并确保您无法返回。但在您实际成功启动新映像之前,回滚保护不会将其视为当前的系统映像。

如果您在系统运行时安装更新,这是否很慢?

对于非 A/B 更新,目标是尽可能快地安装更新,因为用户正在等待并且在应用更新时无法使用他们的设备。对于 A/B 更新,情况恰恰相反;因为用户仍在使用他们的设备,所以目标是尽可能减少影响,因此更新故意放慢速度。通过 Java 系统更新客户端(对于 Google 而言,它是 GmsCore,GMS 提供的核心软件包)中的逻辑,Android 还尝试选择用户根本不使用其设备的时间。该平台支持暂停/恢复更新,并且客户端可以使用它来暂停更新(如果用户开始使用设备)并在设备再次空闲时恢复更新。

在进行 OTA 时,有两个阶段,在 UI 中清楚地显示为进度条下方的步骤 1(共 2 步)步骤 2(共 2 步)。步骤 1 对应于写入数据块,而步骤 2 是预编译 .dex 文件。这两个阶段在性能影响方面截然不同。第一阶段是简单的 I/O。这在资源(RAM、CPU、I/O)方面几乎不需要什么,因为它只是缓慢地复制块。

第二阶段运行 dex2oat 以预编译新的系统映像。这显然对其要求没有明确的界限,因为它编译的是实际的应用。并且编译大型而复杂的应用显然比编译小型而简单的应用涉及更多的工作;而在第 1 阶段中,没有比其他块更大或更复杂的磁盘块。

该过程类似于 Google Play 在后台安装应用更新,然后在显示已更新 5 个应用通知之前所做的操作,多年来一直如此。

如果用户实际上正在等待更新怎么办?

GmsCore 中的当前实现不区分后台更新和用户发起的更新,但将来可能会这样做。如果用户明确要求安装更新或正在观看更新进度屏幕,我们将优先处理更新工作,并假设他们正在积极等待更新完成。

如果应用更新失败会发生什么情况?

对于非 A/B 更新,如果更新应用失败,用户通常会得到一个无法使用的设备。唯一的例外是故障发生在应用程序甚至开始之前(因为软件包未能通过验证,等等)。对于 A/B 更新,应用更新失败不会影响当前正在运行的系统。可以稍后简单地重试更新。