时区规则

Android 10 弃用了基于 APK 的时区数据更新机制(在 Android 8.1 和 Android 9 中可用),并将其替换为基于 APEX 模块的更新机制。AOSP 8.1 到 13 仍然包含 OEM 启用基于 APK 的更新所需的平台代码,因此升级到 Android 10 的设备仍然可以通过 APK 接收合作伙伴提供的时区数据更新。但是,基于 APK 的更新机制不应在同时接收模块更新的生产设备上使用,因为基于 APK 的更新会取代基于 APEX 的更新(也就是说,接收到 APK 更新的设备将忽略基于 APEX 的更新)。

时区更新(Android 10 及更高版本)

Android 10 及更高版本中支持的时区数据模块会更新 Android 设备上的夏令时 (DST) 和时区,从而标准化可能因宗教、政治和地缘政治原因而频繁更改的数据。

更新使用以下流程

  1. IANA 发布时区数据库更新,以响应一个或多个政府更改其国家/地区的时区规则。
  2. Google 或 Android 合作伙伴准备包含更新时区的时区数据模块更新(APEX 文件)。
  3. 最终用户设备下载更新、重启,然后应用更改,之后设备的 时区数据将包含来自更新的新时区数据。

如需详细了解模块,请参阅模块化系统组件

时区更新(Android 8.1–9)

注意:基于 APK 的时区数据更新机制功能已从 Android 14 及更高版本中完全移除,并且在源代码中找不到。合作伙伴应完全迁移到时区 Mainline 模块。

在 Android 8.1 和 Android 9 中,OEM 可以使用基于 APK 的机制将更新的时区规则数据推送到设备,而无需系统更新。此机制使用户能够及时收到更新(从而延长 Android 设备的有用寿命),并使 Android 合作伙伴能够独立于系统映像更新来测试时区更新。

Android 核心库团队提供了必要的数据文件,用于更新库存 Android 设备上的时区规则。OEM 可以选择在为其设备创建时区更新时使用这些数据文件,或者如果愿意,也可以创建自己的数据文件。在所有情况下,OEM 都可以控制其支持的设备的时区规则更新的质量保证/测试、时间和发布。

Android 时区源代码和数据

所有库存 Android 设备(即使是不使用此功能的设备)也需要时区规则数据,并且必须在 /system 分区中随附一组默认时区规则数据。此数据随后由 Android 源代码树中以下库中的代码使用

  • 来自 libcore/ 的托管代码(例如,java.util.TimeZone)使用 tzdatatzlookup.xml 文件。
  • 来自 bionic/ 的原生库代码(例如,用于 mktime、localtime 系统调用)使用 tzdata 文件。
  • 来自 external/icu/ 的 ICU4J/ICU4C 库代码使用 icu .dat 文件。

这些库跟踪可能存在于 /data/misc/zoneinfo/current 目录中的覆盖文件。覆盖文件预计包含改进的时区规则数据,从而使设备能够在不更改 /system 的情况下进行更新。

需要时区规则数据的 Android 系统组件首先检查以下位置

  • libcore/bionic/ 代码使用 tzdatatzlookup.xml 文件的 /data 副本。
  • ICU4J/ICU4C 代码使用 /data 中的文件,并在数据不存在时回退到 /system 文件(用于格式、本地化字符串等)。

Distro 文件

Distro .zip 文件包含填充 /data/misc/zoneinfo/current 目录所需的数据文件。Distro 文件还包含元数据,使设备能够检测版本控制问题。

Distro 文件格式依赖于 Android 版本,因为内容会随 ICU 版本、Android 平台要求和其他版本更改而变化。Android 为每个 IANA 更新(除了更新平台系统文件之外)为受支持的 Android 版本提供 distro 文件。为了使他们的设备保持最新,OEM 可以使用这些 distro 文件,也可以使用 Android 源代码树(其中包含生成 distro 文件所需的脚本和其他文件)创建自己的文件。

时区更新组件

时区规则更新涉及将 distro 文件传输到设备以及安全安装其中包含的文件。传输和安装需要以下内容

  • 平台服务功能(timezone.RulesManagerService),默认情况下处于禁用状态。OEM 必须通过配置启用该功能。RulesManagerService 在系统服务器进程中运行,并通过写入 /data/misc/zoneinfo/staged 来暂存时区更新操作。RulesManagerService 还可以替换或删除已暂存的操作。
  • TimeZoneUpdater,一个不可更新的系统应用(也称为Updater 应用)。OEM 必须在使用该功能的设备的系统映像中包含此应用。
  • OEM TimeZoneData,一个可更新的系统应用(也称为 Data 应用),它将 distro 文件传输到设备并使其可供 Updater 应用使用。OEM 必须在使用该功能的设备的系统映像中包含此应用。
  • tzdatacheck,一个启动时二进制文件,是时区更新正确且安全运行所必需的。

Android 源代码树包含上述组件的通用源代码,OEM 可以选择直接使用,无需修改。测试代码 旨在使 OEM 能够自动检查他们是否已正确启用该功能。

Distro 安装

Distro 安装过程包括以下步骤

  1. Data 应用通过应用商店下载或侧载进行更新。系统服务器进程(通过 timezone.RulesManagerServer/timezone.PackageTracker 类)监视配置的、OEM 特定的 Data 应用软件包名称的更改。

    Data app updates

    图 1. Data 应用更新。

  2. 系统服务器进程通过向 Updater 应用广播带有唯一的一次性令牌的目标 intent 来触发更新检查。系统服务器跟踪其生成的最新令牌,以便确定其触发的最新检查何时完成;任何其他令牌都将被忽略。

    Trigger update

    图 2. 触发更新检查。

  3. 在更新检查期间,Updater 应用执行以下任务
    • 通过调用 RulesManagerService 查询当前设备状态。

      Call RulesManagerService

      图 3. Data 应用更新,调用 RulesManagerService。

    • 通过查询明确定义的 ContentProvider URL 和列规范来查询 Data 应用,以获取有关 distro 的信息。

      Get distro information

      图 4. Data 应用更新,获取有关 distro 的信息。

  4. Updater 应用根据其拥有的信息采取适当的操作。可用操作包括
    • 请求安装。 从 Data 应用读取 Distro 数据,并将其传递到系统服务器中的 RulesManagerService。RulesManagerService 重新确认 distro 格式版本和内容是否适合设备,并暂存安装。
    • 请求卸载(这种情况很少见)。例如,如果 /data 中更新的 APK 正在被禁用或卸载,并且设备正在返回到 /system 中存在的版本。
    • 不执行任何操作。 当发现 Data 应用 distro 无效时发生。
    在所有情况下,Updater 应用都会使用检查令牌调用 RulesManagerService,以便系统服务器知道检查已完成且成功。

    Check complete

    图 5. 检查完成。

  5. 重新启动和 tzdatacheck。 当设备下次启动时,tzdatacheck 二进制文件将执行任何暂存的操作。tzdatacheck 二进制文件可以执行以下任务
    • 在其他系统组件打开并开始使用文件之前,通过处理 /data/misc/zoneinfo/current 文件的创建、替换和/或删除来执行暂存的操作。
    • 检查 /data 中的文件是否适合当前平台版本,如果设备刚刚收到系统更新并且 distro 格式版本已更改,则可能并非如此。
    • 确保 IANA 规则版本与 /system 中的版本相同或更新。这可以防止系统更新使设备的时区规则数据比 /system 映像中存在的时区规则数据更旧。

可靠性

端到端安装过程是异步的,并且跨三个操作系统进程进行拆分。在安装过程中的任何时候,设备都可能断电、磁盘空间不足或遇到其他问题,从而导致安装检查不完整。在最佳的不成功情况下,Updater 应用会通知系统服务器它不成功;在最糟糕的不成功情况下,RulesManagerService 完全没有收到任何调用。

为了处理这种情况,系统服务器代码会跟踪触发的更新检查是否已完成以及 Data App 的上次检查版本代码是什么。当设备空闲并充电时,系统服务器代码可以检查当前状态。如果它发现更新检查不完整或 Data App 版本意外,它会自发地触发更新检查。

安全

启用后,系统服务器中的 RulesManagerService 代码会执行多项检查,以确保系统可以安全使用。

  • 指示系统映像配置错误的 Problems 会阻止设备启动;示例包括错误的 Updater 或 Data 应用配置,或者 Updater 或 Data 应用不在 /system/priv-app 中。
  • 指示已安装错误的 Data 应用的 Problems 不会阻止设备启动,但会阻止触发更新检查;示例包括缺少必需的系统权限,或者 Data 应用未在预期的 URI 上公开 ContentProvider。

/data/misc/zoneinfo 目录的文件权限是使用 SELinux 规则强制执行的。与任何 APK 一样,Data 应用必须使用用于签署 /system/priv-app 版本的同一密钥进行签名。Data 应用应具有专用的、OEM 特定的软件包名称和密钥。

集成时区更新

要启用时区更新功能,OEM 通常需要

  • 创建自己的 Data 应用。
  • 在系统映像构建中包含 Updater 和 Data 应用。
  • 配置系统服务器以启用 RulesManagerService。

准备工作

在开始之前,OEM 应查看以下策略、质量保证和安全注意事项

  • 为其 Data 应用创建专用的应用特定签名密钥。
  • 为时区更新创建发布和版本控制策略,以了解哪些设备将要更新以及他们如何确保更新仅安装在需要它们的设备上。例如,OEM 可能希望为其所有设备使用单个 Data 应用,或者可能选择为不同的设备使用不同的 Data 应用。该决定会影响软件包名称的选择,可能还会影响使用的版本代码以及 QA 策略。
  • 了解他们是想要使用 AOSP 中的股票 Android 时区数据还是创建自己的数据。

创建 Data 应用

AOSP 包含在 packages/apps/TimeZoneData 中创建 Data 应用所需的所有源代码和构建规则,以及位于 packages/apps/TimeZoneData/oem_template 中的 AndroidManifest.xml 和其他文件的说明和示例模板。示例模板包括真实 Data 应用 APK 的构建目标和用于创建 Data 应用测试版本的额外目标。

OEM 可以使用自己的图标、名称、翻译和其他详细信息自定义 Data 应用。但是,由于 Data 应用无法启动,因此该图标仅显示在设置 > 应用屏幕中。

Data 应用旨在通过 tapas 构建进行构建,以生成适合添加到系统映像(用于初始版本)并通过应用商店(用于后续更新)签名和分发的 APK。有关使用 tapas 的详细信息,请参阅 使用 tapas 构建 Data 应用

OEM 必须将预构建的 Data 应用安装在设备系统映像的 /system/priv-app 中。要将预构建的 APK(由 tapas 构建过程生成)包含在系统映像中,OEM 可以复制 packages/apps/TimeZoneData/oem_template/data_app_prebuilt 中的示例文件。示例模板还包括用于在测试套件中包含 Data 应用测试版本的构建目标。

在系统映像中包含 Updater 和 Data 应用

OEM 必须将 Updater 和 Data 应用 APK 放置在系统映像的 /system/priv-app 目录中。为此,系统映像构建必须显式包含 Updater 应用和 Data 应用预构建目标。

Updater 应用应使用平台密钥进行签名,并作为任何其他系统应用包含在内。目标在 packages/apps/TimeZoneUpdater 中定义为 TimeZoneUpdater。Data 应用的包含是 OEM 特定的,并且取决于为预构建选择的目标名称。

配置系统服务器

要启用时区更新,OEM 可以通过覆盖在 frameworks/base/core/res/res/values/config.xml 中定义的配置属性来配置系统服务器。

属性 描述 是否需要覆盖?
config_enableUpdateableTimeZoneRules
必须设置为 true 才能启用 RulesManagerService。
config_timeZoneRulesUpdateTrackingEnabled
必须设置为 true 才能让系统侦听 Data 应用的更改。
config_timeZoneRulesDataPackage
OEM 特定的 Data 应用的软件包名称。
config_timeZoneRulesUpdaterPackage
为默认 Updater 应用配置。仅在提供不同的 Updater 应用实现时才更改。
config_timeZoneRulesCheckTimeMillisAllowed
RulesManagerService 触发更新检查与安装、卸载或不执行任何操作响应之间允许的时间。超过此点后,可能会生成自发可靠性触发器。
config_timeZoneRulesCheckRetryCount
在 RulesManagerService 停止生成更多更新检查之前允许的连续不成功更新检查的次数。

配置覆盖应位于系统映像中(而不是 vendor 或其他映像中),因为配置错误的设备可能拒绝启动。如果配置覆盖位于 vendor 映像中,则更新到没有 Data 应用(或具有不同的 Data 应用/Updater 应用软件包名称)的系统映像将被视为配置错误。

xTS 测试

xTS 是指任何类似于使用 Tradefed 的标准 Android 测试套件(例如 CTS 和 VTS)的 OEM 特定测试套件。具有此类测试套件的 OEM 可以添加以下位置提供的 Android 时区更新测试

  • packages/apps/TimeZoneData/testing/xts 包括基本自动化功能测试所需的代码。
  • packages/apps/TimeZoneData/oem_template/xts 包含用于在类似 Tradefed 的 xTS 套件中包含测试的示例目录结构。与其他模板目录一样,OEM 应复制并根据自己的需求进行自定义。
  • packages/apps/TimeZoneData/oem_template/data_app_prebuilt 包含用于包含测试所需的预构建测试 APK 的构建时配置。

创建时区更新

当 IANA 发布一组新的时区规则时,Android 核心库团队会生成补丁以更新 AOSP 中的版本。使用股票 Android 系统和 distro 文件的 OEM 可以获取这些提交,使用它们来创建其 Data 应用的新版本,然后发布新版本以更新其生产设备。

由于 Data 应用包含与 Android 版本紧密相关的 distro 文件,因此 OEM 必须为 OEM 想要更新的每个受支持的 Android 版本创建 Data 应用的新版本。例如,如果 OEM 想要为 Android 8.1、9 和 10 设备提供更新,则他们必须完成该过程三次。

步骤 1:更新 system/timezone 和 external/icu 数据文件

在此步骤中,OEM 从 AOSP 中的 release-dev 分支获取 system/timezoneexternal/icu 的股票 Android 提交,并将这些提交应用于其 Android 源代码副本。

system/timezone AOSP 补丁包含 system/timezone/input_datasystem/timezone/output_data 中更新的文件。需要进行其他本地修复的 OEM 可以修改输入文件,然后使用 system/timezone/input_dataexternal/icu 中的文件在 output_data 中生成文件。

最重要的文件是 system/timezone/output_data/distro/distro.zip,它在构建 Data 应用 APK 时会自动包含在内。

步骤 2:更新 Data 应用的版本代码

在此步骤中,OEM 更新 Data 应用的版本代码。构建会自动获取 distro.zip,但是 Data 应用的新版本必须具有新的版本代码,以便它被识别为新的版本,并用于替换预加载的 Data 应用或先前更新在设备上安装的 Data 应用。

当使用从 package/apps/TimeZoneData/oem_template/data_app 复制的文件构建 Data 应用时,您可以在 Android.mk 中找到应用于 APK 的版本代码/版本名称

TIME_ZONE_DATA_APP_VERSION_CODE :=
TIME_ZONE_DATA_APP_VERSION_NAME :=

testing/Android.mk 中可以找到类似的条目(但是,测试版本代码必须高于系统映像版本)。有关详细信息,请参阅 示例版本代码策略方案;如果使用示例方案或类似的方案,则无需更新测试版本代码,因为它们保证高于真实版本代码。

步骤 3:重新构建、签名、测试和发布

在此步骤中,OEM 使用 tapas 重新构建 APK,对生成的 APK 进行签名,然后测试并发布 APK

  • 对于未发布的设备(或在为已发布的设备准备系统更新时),请在 Data 应用预构建目录中提交新的 APK,以确保系统映像和 xTS 测试具有最新的 APK。OEM 应测试新文件是否可以正常工作(即,它是否通过 CTS 以及任何 OEM 特定的自动化和手动测试)。
  • 对于不再接收系统更新的已发布设备,签名的 APK 可能仅通过应用商店发布。

OEM 负责质量保证,并在发布之前在其设备上测试更新的 Data 应用。

Data 应用版本代码策略

Data 应用必须具有 合适的版本控制策略,以确保设备收到正确的 APK。例如,如果收到包含比从应用商店下载的 APK 更旧的 APK 的系统更新,则应保留应用商店版本。

APK 版本代码应包括以下信息

  • Distro 格式版本(主要版本 + 次要版本)
  • 递增(不透明)版本号

目前,平台 API 级别与 distro 格式版本密切相关,因为每个 API 级别通常都与新版本的 ICU 相关联(这使得 distro 文件不兼容)。将来,Android 可能会更改此设置,以便 distro 文件可以在多个 Android 平台版本上运行(并且 API 级别不在 Data 应用版本代码方案中使用)。

示例版本代码策略

此示例版本编号方案确保较高的 distro 格式版本取代较低的 distro 格式版本。AndroidManifest.xml 使用 android:minSdkVersion 来确保旧设备不会收到高于它们可以处理的 distro 格式版本。

Version check

图 6. 示例版本代码策略。

示例 目的
Y 保留 允许未来的替代方案/测试 APK。它最初(隐式地)为 0。由于底层类型是有符号 32 位 int 类型,因此此方案最多支持两个未来的编号方案修订版。
01 主要格式版本 跟踪 3 位十进制数字主要格式版本。distro 格式支持 3 位十进制数字,但此处仅使用 2 位数字。考虑到每个 API 级别的预期主要增量,不太可能达到 100。主要版本 1 等效于 API 级别 27。
1 次要格式版本 跟踪 3 位十进制数字次要格式版本。distro 格式支持 3 位十进制数字,但此处仅使用 1 位数字。不太可能达到 10。
X 保留 对于生产版本为 0(对于测试 APK 可能不同)。
ZZZZZ 不透明版本号 按需分配的十进制数字。包括间隙,以便在需要时进行插页式更新。

如果使用二进制而不是十进制,则该方案可以更好地打包,但是此方案的优点是人类可读。如果完整数字范围用尽,则可以更改 Data 应用软件包名称。

版本名称是详细信息的人类可读表示形式,例如:major=001,minor=001,iana=2017a, revision=1,respin=2。示例显示在下表中。

# 版本代码 minSdkVersion {主要格式版本},{次要格式版本},{IANA 规则版本},{修订版}
1 11000010 O-MR1 major=001,minor=001,iana=2017a,revision=1
2 21000010 P major=002,minor=001,iana=2017a,revision=1
3 11000020 O-MR1 major=001,minor=001,iana=2017a,revision=2
4 11000030 O-MR1 major=001,minor=001,iana=2017b,revision=1
5 21000020 P major=002,minor=001,iana=2017b,revision=1
6 11000040 O-MR1 major=001,minor=001,iana=2018a,revision=1
7 21000030 P major=002,minor=001,iana=2018a,revision=1
8 1123456789 - -
9 11000021 O-MR1 major=001,minor=001,iana=2017a,revision=2,respin=2
  • 示例 1 和 2 显示了同一 2017a IANA 版本的两个 APK 版本,但主要格式版本不同。2 在数值上高于 1,这对于确保较新的设备收到较高的格式版本是必需的。minSdkVersion 确保 P 版本不会提供给 O 设备。
  • 示例 3 是 1 的修订版/修复程序,在数值上高于 1。
  • 示例 4 和 5 显示了 O-MR1 和 P 的 2017b 版本。由于数值较高,它们取代了各自前代的先前 IANA 版本/Android 修订版。
  • 示例 6 和 7 显示了 O-MR1 和 P 的 2018a 版本。
  • 示例 8 演示了使用 Y 完全替换 Y=0 方案。
  • 示例 9 演示了使用 3 和 4 之间留下的间隙来重新调整 apk。

由于每台设备都附带系统映像中默认的、适当版本控制的 APK,因此不存在在 P 设备上安装 O-MR1 版本的风险,因为它具有比 P 系统映像版本更低的版本号。在 /data 中安装了 O-MR1 版本的设备,然后接收到 P 的系统更新,将优先使用 /system 版本,而不是 /data 中的 O-MR1 版本,因为 P 版本始终高于任何旨在用于 O-MR1 的应用。

使用 tapas 构建 Data 应用

OEM 负责管理时区 Data 应用的大多数方面,并正确配置系统映像。Data 应用旨在通过 tapas 构建进行构建,以生成适合添加到系统映像(用于初始版本)并通过应用商店(用于后续更新)签名和分发的 APK。

Tapas 是 Android 构建系统的简化版本,它使用简化的源代码树来生成应用的可分发版本。熟悉普通 Android 构建系统的 OEM 应该可以从普通 Android 平台构建中识别出构建文件。

创建 manifest

简化的源代码树通常通过自定义 manifest 文件来实现,该文件仅引用构建系统和构建应用所需的 Git 项目。在按照 创建 Data 应用 中的说明进行操作后,OEM 应至少创建两个 OEM 特定的 Git 项目,方法是使用 packages/apps/TimeZoneData/oem_template 下的模板文件

  • 一个 Git 项目包含应用文件,例如 manifest 和构建应用 APK 文件所需的构建文件(例如,vendor/oem/apps/TimeZoneData)。此项目还包含可供 xTS 测试使用的测试 APK 的构建规则。
  • 一个 Git 项目包含应用构建生成的已签名 APK,用于包含在系统映像构建和 xTS 测试中。

应用构建利用其他几个 Git 项目,这些项目与平台构建共享或包含 OEM 独立的代码库。

以下 manifest 代码段包含支持时区 Data 应用的 O-MR1 构建所需的最少 Git 项目集。OEM 必须将其 OEM 特定的 Git 项目(通常包括包含签名证书的项目)添加到此 manifest,并且可以相应地配置不同的分支。

   <!-- Tapas Build -->
    <project
        path="build"
        name="platform/build">
        <copyfile src="core/root.mk" dest="Makefile" />
    </project>
    <project
        path="prebuilts/build-tools"
        name="platform/prebuilts/build-tools"
        clone-depth="1" />
    <project
        path="prebuilts/go/linux-x86"
        name="platform/prebuilts/go/linux-x86"
        clone-depth="1" />
    <project
        path="build/blueprint"
        name="platform/build/blueprint" />
    <project
        path="build/kati"
        name="platform/build/kati" />
    <project
        path="build/soong"
        name="platform/build/soong">
        <linkfile src="root.bp" dest="Android.bp" />
        <linkfile src="bootstrap.bash" dest="bootstrap.bash" />
    </project>

    <!-- SDK for system / public API stubs -->
    <project
        path="prebuilts/sdk"
        name="platform/prebuilts/sdk"
        clone-depth="1" />
    <!-- App source -->
    <project
        path="system/timezone"
        name="platform/system/timezone" />
    <project
        path="packages/apps/TimeZoneData"
        name="platform/packages/apps/TimeZoneData" />
    <!-- Enable repohooks -->
    <project
        path="tools/repohooks"
        name="platform/tools/repohooks"
        revision="main"
        clone_depth="1" />
    <repo-hooks
        in-project="platform/tools/repohooks"
        enabled-list="pre-upload" />

运行 tapas 构建

建立源代码树后,使用以下命令调用 tapas 构建

source build/envsetup.sh
tapas
make -j30 showcommands dist TARGET_BUILD_APPS='TimeZoneData TimeZoneData_test1 TimeZoneData_test2'  TARGET_BUILD_VARIANT=userdebug

成功的构建会在 out/dist 目录中生成用于测试的文件。这些文件可以放置到 prebuilts 目录中,以包含在系统映像中和/或通过应用商店分发给兼容设备。