时区规则

Android 8.1 为 OEM 提供了一种新机制:无需进行系统更新就可以将更新的时区规则数据推送至设备中。此机制使用户能够及时获得更新(从而延长 Android 设备的使用期限),并且使 OEM 能够独立于系统映像更新来测试时区更新。

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 来暂存时区更新操作。它也可以替换或删除已经暂存的操作。
  • Time Zone Updater,一个不可更新的系统应用(又名 Updater App)。OEM 必须将此应用包含在要使用该功能的设备的系统映像中。
  • OEM Time Zone Data,一个可更新的系统应用(又名 Data App),它将发行版文件传输到设备中并使其可供 Updater App 使用。OEM 必须将此应用包含在要使用该功能的设备的系统映像中。
  • tzdatacheck,一个启动时二进制文件,用于正确、安全地执行时区更新。

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

发行版安装

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

  1. 通过从应用商店下载或旁加载来更新 Data App。系统服务器进程(通过 timezone.RulesManagerServer/timezone.PackageTracker 类)监控对已配置的 OEM 专属 Data App 软件包名称的更改。
    Data App 更新
    图 1. Data App 更新。
  2. 系统服务器进程触发更新检查,方法是向 Updater App 广播一个包含独一无二的一次性令牌的定向 Intent。系统服务器会持续跟踪自己生成的最新令牌,从而稍后能够确定最近一次触发的检查操作完成的时间;其他令牌一概会被忽略。
    触发更新
    图 2. 触发更新检查。
  3. 更新检查。在此期间,Updater App 会执行以下任务:
    • 通过调用 RulesManagerService 来查询当前的设备状态。
      调用 RulesManagerService
      图 3. Data App 更新,调用 RulesManagerService。
    • 通过查询明确定义的 ContentProvider URL 和列规范来查询 Data App,从而获取有关发行版的信息:
      获取发行版信息
      图 4. Data App 更新,获取有关发行版的信息。
  4. Updater App 执行适当的操作(根据其获得的信息)。可用的操作包括:
    • 请求安装。从 Data App 中读取发行版数据并将此数据传递给系统服务器中的 RulesManagerService。RulesManagerService 重新确认发行版格式版本和内容是否适用于设备,然后准备安装。
    • 请求卸载(这种情况很少出现)。例如,如果要停用或卸载 /data 中已更新的 .apk,并且设备将退回到 /system 中的现有版本。
    • 不执行任何操作。在 Data App 发行版无效时会发生此情况。
    在所有情况下,Updater App 都会使用检查令牌调用 RulesManagerService,以便系统服务器知道检查已成功完成。
    检查完成
    图 5. 检查完成。
  5. 重新启动并运行 tzdatacheck。当设备下次启动时,tzdatacheck 二进制文件将执行所有暂存操作。tzdatacheck 二进制文件可以执行以下任务:
    • 在其他系统组件打开并开始使用 /data/misc/zoneinfo/current 文件之前,根据暂存操作相应地创建、替换和/或删除这些文件。
    • 检查 /data 中的文件是否适合当前平台版本。如果设备刚刚收到系统更新并且发行版格式版本已更改,则可能不适合。
    • 确保 IANA 规则版本不低于 /system 中的版本。这样可防止在系统更新后设备拥有的时区规则数据版本低于 /system 映像中的时区规则数据版本。

可靠性

端到端安装过程是异步进行的,可以分为三个操作系统进程。在安装过程中的任何时刻,如果出现设备断电、磁盘空间不足等情况,都将导致安装检查无法成功完成。在情况最好的失败案例中,Updater App 能够通知系统服务器检查失败;在情况最坏的失败案例中,RulesManagerService 不会收到任何调用。

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

安全性

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

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

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

集成时区更新

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

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

准备工作

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

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

创建 Data App

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

OEM 可以使用自己的图标、名称、翻译等内容自定义 Data App。但是,由于 Data App 无法启动,图标仅会显示在“设置 > 应用”屏幕中。

Data App 按计划应采用 Tapas 编译进程进行编译。该编译进程生成的 .apk 适合添加到系统映像(针对初始版本)并适合通过应用商店进行签名和分发(针对后续更新)。要详细了解如何使用 Tapas,请参阅使用 Tapas 编译 Data App

OEM 必须在 /system/priv-app 中安装设备的系统映像中预编译的 Data App。要在系统映像中包含预编译的 .apk(由 Tapas 编译进程生成),OEM 可以复制 packages/apps/TimeZoneData/oem_template/data_app_prebuilt 中的示例文件。示例模板还包括编译目标,以便在测试套件中包含 Data App 的测试版本。

在系统映像中加入 Updater App 和 Data App

OEM 必须将 Updater App 和 Data App .apk 文件放在系统映像的 /system/priv-app 目录中。为此,系统映像编译必须明确包含 Updater App 和 Data App 预编译目标。

Updater App 应使用平台密钥进行签名,并与其他系统应用一样包含在映像内。目标在 packages/apps/TimeZoneUpdater 中定义为 TimeZoneUpdater。将 Data App 加入映像的方式因 OEM 而异,并取决于为预编译选择的目标名称。

配置系统服务器

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

属性 需要替换?

config_enableUpdateableTimeZoneRules
必须设置为 true 才能启用 RulesManagerService。

config_timeZoneRulesUpdateTrackingEnabled
必须设置为 true 才能让系统监听对 Data App 的更改。

config_timeZoneRulesDataPackage
OEM 专属 Data App 的软件包名称。

config_timeZoneRulesUpdaterPackage
针对默认 Updater App 的配置。仅在提供不同的 Updater App 实现时才更改。

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 App,然后发布新版本来更新其正式版设备。

由于 Data App 包含与 Android 版本紧密关联的发行版文件,OEM 必须为其要更新的每个受支持的 Android 版本创建一个新的 Data App 版本。例如,如果 OEM 想要更新 O-MR1、P 和 Q 设备,则必须完成该过程三次。

第 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 App .apk 时,会自动包含该文件。

第 2 步:更新 Data App 的版本号

在此步骤中,OEM 将更新 Data App 的版本号。编译进程会自动选取 distro.zip,但新版 Data App 必须具有新的版本号,以便被识别为新版本并用于替换预加载的 Data App 或之前的设备更新所安装的 Data App。

使用从 package/apps/TimeZoneData/oem_template/data_app 复制的文件编译 Data App 时,可以在 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. 版本号策略示例。
示例 用途
Y 保留 用于在将来指定替代方案和/或测试 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 App 软件包名称并重新开始编号。

版本名称将采用相关详情的可读表示法,例如: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 版本的两个具有不同 Major 格式版本的 .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 App

OEM 负责管理时区 Data App 的大部分内容,并负责正确配置系统映像。Data App 按计划应采用 Tapas 编译进程进行编译。该编译进程生成的 .apk 适合添加到系统映像(针对初始版本)并适合通过应用商店进行签名和分发(针对后续更新)。

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

创建清单

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

  • 一个 Git 项目中包含应用文件,例如创建应用 .apk 文件(如 vendor/oem/apps/TimeZoneData)时所需的清单和编译文件。此项目还包含可供 xTS 测试使用的测试 APK 的编译规则。
  • 一个 Git 项目中包含由应用编译生成的已签名 .apk 文件,以将其包含在系统映像编译和 xTS 测试中。

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

以下清单代码段包含时区 Data App 的 O-MR1 编译在最低限度下所需的一组 Git 项目(将来的 Android 版本将采用不同的列表,甚至可能会更改编译系统)。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="master"
        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 目录中生成用于测试的文件。可以将这些文件放在预编译目录中,以便加入到系统映像中,和/或通过应用商店分发到兼容设备。