时区规则

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)

注意:从 Android 14 版本开始,已将基于 APK 的时区数据更新机制功能完全移除,无法在源代码中找到。合作伙伴应完全迁移到 Time Zone 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,为本地时间系统调用)使用 tzdata 文件。
  • external/icu/ 中的 ICU4J/ICU4C 库代码使用 icu .dat 文件。

这些库会跟踪 /data/misc/zoneinfo/current 目录中可能存在的叠加层文件。叠加层文件中应包含优化后的时区规则数据,从而能够在不更改 /system 的情况下更新设备。

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

  • libcore/bionic/ 代码使用 tzdatatzlookup.xml 文件的 /data 副本。
  • ICU4J/ICU4C 代码使用 /data 中的文件,如果在其中找不到特定数据(针对格式、本地化的字符串等),将会转而在 /system 文件中查找。

发行版文件

发行版 .zip 文件包含填充 /data/misc/zoneinfo/current 目录时所需的数据文件。发行版文件还包含供设备检测版本问题的元数据。

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

时区更新组件

时区规则更新包括将发行版文件传输到设备,以及安全安装其中所含的文件。传输和安装过程需用到以下组件:

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

Android 源代码树中包含上述组件的通用源代码,OEM 可以选择使用这些代码,无需做出变更。为使 OEM 能够自动检查是否已正确启用该功能,我们专门提供了测试代码

发行版安装

发行版安装过程包括以下步骤:

  1. 通过应用商店下载或旁加载来更新 Data 应用。系统服务器进程(通过 timezone.RulesManagerServer/timezone.PackageTracker 类)监控配置的 OEM 专属 Data 应用软件包名称是否更改。

    Data 应用更新

    图 1. Data 应用更新

  2. 系统服务器进程通过以下方式触发更新检查:向 Updater 应用广播包含独一无二的一次性令牌的定向 intent。系统服务器会持续跟踪自己生成的最新令牌,以便能够确定它最近一次触发的检查完成的时间;而其他令牌则一概忽略。

    触发更新

    图 2. 触发更新检查。

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

      调用 RulesManagerService

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

    • 通过查询明确定义的 ContentProvider 网址和列规范来查询 Data 应用,从而获取有关发行版的信息。

      获取发行版信息

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

  4. Updater 应用会根据其获得的信息执行相应的操作。可用的操作包括:
    • 请求安装。从 Data 应用中读取发行版数据并将这些数据传递给系统服务器中的 RulesManagerService。RulesManagerService 重新确认发行版格式版本和内容是否适用于设备,然后准备安装。
    • 请求卸载(这种情况很少出现)。例如,如果停用或卸载 /data 中更新后的 APK,设备退回到 /system 中的现有版本,Updater 应用就会执行此操作。
    • 不执行任何操作。在 Data 应用发行版无效时会发生此情况。
    在所有情况下,Updater 应用都会使用检查令牌调用 RulesManagerService,以便系统服务器能够知道检查已成功完成。

    检查完成

    图 5. 检查完成。

  5. 重新启动并运行 tzdatacheck。当设备下次启动时,tzdatacheck 二进制文件将执行所有暂存操作。tzdatacheck 二进制文件可以执行以下任务:
    • 在其他系统组件打开并开始使用 /data/misc/zoneinfo/current 文件之前,根据暂存操作创建、替换和/或删除这些文件。
    • 检查 /data 中的文件是否适合当前平台版本。如果设备刚刚收到系统更新并且发行版格式版本已更改,则可能就不适合。
    • 确保 IANA 规则版本不低于 /system 中的版本。这样可防止在系统更新后,设备拥有的时区规则数据版本低于 /system 映像中的时区规则数据版本。

可靠性

端到端安装过程是异步进行的,可以分为三个操作系统进程。在安装过程中的任何时刻,如果出现设备断电、磁盘空间不足或其他问题,都将导致安装检查无法完成。如果安装失败,最好的情况是 Updater 应用告知系统服务器安装失败,而最坏的情况是 RulesManagerService 收不到任何调用。

为了解决此问题,系统服务器代码会持续跟踪所触发的更新检查的完成状态以及最后一次检查到的 Data 应用版本号。当设备空闲并在充电时,系统服务器代码可以检查当前状态。如果发现更新检查未完成或发现非预期的 Data 应用版本,它会自发地触发更新检查。

安全性

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

  • 如果检查到的问题表明系统映像配置有误(例如 Updater 应用或 Data 应用配置错误,或者 Updater 应用或 Data 应用不在 /system/priv-app 中),设备将无法正常启动。
  • 如果检查到的问题表明所安装的 Data 应用有误(例如缺少必要的系统权限,或者 Data 应用没有在预期的 URI 上提供 ContentProvider),此类问题不会阻碍设备启动,但会阻碍触发更新检查。

/data/misc/zoneinfo 目录的文件权限通过 SELinux 规则强制执行。与任何 APK 一样,Data 应用签名的密钥必须与 /system/priv-app 版本签名所用的密钥相同。Data 应用应采用专属的 OEM 软件包名称和密钥。

集成时区更新

为了启用时区更新功能,OEM 通常要执行以下操作:

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

准备

开始操作前,OEM 应查看以下政策、质量保证和安全注意事项:

  • 为其 Data App 创建该应用专用的签名密钥。
  • 针对时区更新拟订版本发布和版本控制策略,以了解将更新哪些设备以及如何确保只在需要的设备上安装更新。例如,OEM 可能希望为所有设备使用相同的 Data App,或者为各个设备使用不同的 Data App。相关决定会影响所选择的软件包名称和质量保证策略,还有可能影响所使用的版本号。
  • 明确自己是要使用 AOSP 的原生 Android 时区数据,还是要创建自己的 Android 时区数据。

创建 Data 应用

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

OEM 可以使用自己的图标、名称、翻译和其他详细信息自定义 Data 应用。不过,由于 Data App 无法启动,相应图标仅会显示在“设置 > 应用”屏幕中。

Data App 按计划应采用 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 目录中。为此,系统映像 build 必须明确包含 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 或 other 目录)中,因为配置错误的设备可能会无法启动。如果配置替换文件位于 /vendor 中,则更新到不含 Data App(或所含的 Data App/Updater App 具有不同的软件包名称)的系统映像将被视为配置错误。

xTS 测试

xTS 是指任何 OEM 专属测试套件,类似于采用 Tradefed 的标准 Android 测试套件(例如 CTS 和 VTS)。具有此类测试套件的 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 系统和发行版文件的 OEM 可提取这些变更,用它们创建新版 Data 应用,然后发布新版本来更新其正式版设备。

由于 Data 应用包含与 Android 版本紧密关联的发行版文件,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 应用的版本号。该 build 会自动选取 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 App 预编译目录中提交新的 APK,以确保系统映像和 xTS 测试具有最新的 APK。OEM 应测试新文件能否正常工作(即通过 CTS 和任何 OEM 专属的自动和手动测试)。
  • 对于不再接收系统更新的已发布设备,可能只能通过应用商店发布已签名的 APK。

OEM 负责质量保证以及在发布之前在其设备上测试更新的 Data App。

Data App 版本号策略

Data App 必须具有适当的版本控制策略,以确保设备接收正确的 APK 文件。例如,如果收到的系统更新中包含的 APK 版本低于从应用商店下载的版本,则应保留应用商店版本。

APK 版本号应包含以下信息:

  • 发行版格式版本 (Major + Minor)
  • 递增(不透明)版本号

目前,平台 API 级别与发行版格式版本密切相关,因为每个 API 级别通常都与新版本的 ICU 相关联(这会造成发行版文件不兼容)。将来的 Android 版本可能会改变这一情况,使得一个发行版文件适用于多个 Android 平台版本(并且不在 Data App 版本号方案中使用 API 级别)。

版本号策略示例

此版本号方案示例可确保较高的发行版格式版本优先于较低的发行版格式版本。AndroidManifest.xml 使用 android:minSdkVersion 来确保旧版设备收到的发行版格式版本不会高于其可处理的版本。

版本检查

图 6. 版本号策略示例。

示例 用途
保留 用于在将来指定替代方案/测试 APK。该值最初(隐式)为 0。由于基础类型是一个带符号的 32 位整数类型,因此该方案未来最多支持两种编号方案修订版本。
01 Major 格式版本 用于跟踪 3 位十进制数字 Major 格式版本。发行版格式支持 3 位十进制数字,但在此处仅使用 2 位。根据每个 API 级别的 Major 版本号预期递增情况,此数字不太可能达到 100。Major 版本 1 = API 级别 27。
1 Minor 格式版本 用于跟踪 3 位十进制数字 Minor 格式版本。发行版格式支持 3 位十进制数字,但在此处仅使用 1 位。此数字不太可能达到 10。
X 保留 正式版为 0(对于测试 APK,可为其他值)。
ZZZZZ 不透明版本号 按需分配的十进制数字。可存在间隙,以便在需要时执行间隙式更新。

如果采用二进制而非十进制,则可以更好地打包该方案,但在人类可读方面,十进制方案更具优势。如果整个范围的数字已用尽,则可以更改 Data 应用软件包名称。

版本名称采用人类可读的方式表示版本详细信息,例如:major=001,minor=001,iana=2017a, revision=1,respin=2。 具体示例如下表所示。

# 版本号 minSdkVersion {Major 格式版本},{Minor 格式版本},{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 版本,它们具有不同的 Major 格式版本。示例 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 App 的大部分内容,并负责正确配置系统映像。Data App 按计划应采用 tapas 构建流程进行构建。该构建流程生成的 APK 适合添加到系统映像(针对初始版本)并适合通过应用商店进行签名和分发(针对后续更新)。

Tapas 是 Android 构建系统的精简版本,它使用简化的源代码树来生成可分发的应用版本。熟悉常规 Android 构建系统的 OEM 可认出其构建文件与常规 Android 平台 build 的不同之处。

创建清单

简化的源代码树通常可通过自定义清单文件来实现,该文件仅会引用构建系统及构建应用所需的 Git 项目。按照创建 Data 应用中的说明操作后,OEM 应至少有两个使用 packages/apps/TimeZoneData/oem_template 下的模板文件创建的 OEM 专属 Git 项目:

  • 一个 Git 项目包含应用文件,例如创建应用 APK 文件(如 vendor/oem/apps/TimeZoneData)时所需的清单和构建文件。此项目还包含可供 xTS 测试使用的测试 APK 的构建规则。
  • 另一个 Git 项目包含由应用 build 生成的已签名 APK 文件,该文件可纳入系统映像 build 和 xTS 测试。

应用 build 会用到其他几个 Git 项目,这些 Git 项目要么与平台 build 共享,要么包含独立于 OEM 的代码库。

以下清单代码段包含时区 Data 应用的 O-MR1 build 在最低限度下所需的一组 Git 项目。OEM 必须将其 OEM 专属 Git 项目(通常包括一个含签名证书的项目)添加到此清单,并可以相应地配置不同的分支。

   <!-- 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 build

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

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

如果成功,该 build 将在 out/dist 目录中生成用于测试的文件。可以将这些文件放在预构建目录中,以便加入到系统映像中,和/或通过应用商店分发到兼容设备。