APK 缓存

本文档介绍了 APK 缓存解决方案的设计,该解决方案用于在支持 A/B 分区的设备上快速安装预加载的应用。

原始设备制造商 (OEM) 可以将预加载的应用和热门应用放置在 APK 缓存中,该缓存存储在新的 A/B 分区设备的几乎为空的 B 分区中,而不会影响任何面向用户的数据空间。通过在设备上提供 APK 缓存,新的或最近恢复出厂设置的设备几乎可以立即使用,而无需从 Google Play 下载 APK 文件。

用例

  • 将预加载的应用存储在 B 分区中,以便更快地进行设置
  • 将热门应用存储在 B 分区中,以便更快地进行恢复

前提条件

要使用此功能,设备需要满足以下条件:

  • 已安装 Android 8.1 (O MR1) 版本
  • 已实现 A/B 分区

预加载的内容只能在首次启动期间复制。这是因为在支持 A/B 系统更新的设备上,B 分区实际上不存储系统映像文件,而是存储预加载的内容,例如零售演示资源、OAT 文件和 APK 缓存。在资源已复制到 /data 分区(这在首次启动时发生)之后,B 分区将由 无线下载 (OTA) 更新用于下载系统映像的更新版本。

因此,APK 缓存无法通过 OTA 更新;它只能在工厂预加载。恢复出厂设置只会影响 /data 分区。系统 B 分区仍然具有预加载的内容,直到 OTA 映像下载完成。恢复出厂设置后,系统将再次经历首次启动。这意味着,如果 OTA 映像下载到 B 分区,然后设备恢复出厂设置,则 APK 缓存将不可用。

实现

方法 1:系统其他分区上的内容

优点:预加载的内容在恢复出厂设置后不会丢失 - 重启后会从 B 分区复制。

缺点:需要 B 分区上的空间。恢复出厂设置后的启动需要额外的复制预加载内容的时间。

为了在首次启动期间复制预加载,系统会在 /system/bin/preloads_copy.sh 中调用脚本。该脚本使用单个参数(system_b 分区的只读挂载点路径)调用。

要实现此功能,请进行以下设备特定的更改。以下是来自 Marlin 的示例

  1. 将执行复制的脚本添加到 device-common.mk 文件(在本例中为 device/google/marlin/device-common.mk),如下所示
    # Script that copies preloads directory from system_other to data partition
    PRODUCT_COPY_FILES += \
        device/google/marlin/preloads_copy.sh:system/bin/preloads_copy.sh
    
    在以下位置查找示例脚本源代码:device/google/marlin/preloads_copy.sh
  2. 修改 init.common.rc 文件,使其创建必要的 /data/preloads 目录和子目录
    mkdir /data/preloads 0775 system system
    mkdir /data/preloads/media 0775 system system
    mkdir /data/preloads/demo 0775 system system
    
    在以下位置找到 init 文件示例源文件: device/google/marlin/init.common.rc
  3. 在文件 preloads_copy.te 中定义一个新的 SELinux 域
    type preloads_copy, domain, coredomain;
    type preloads_copy_exec, exec_type, vendor_file_type, file_type;
    
    init_daemon_domain(preloads_copy)
    
    allow preloads_copy shell_exec:file rx_file_perms;
    allow preloads_copy toolbox_exec:file rx_file_perms;
    allow preloads_copy preloads_data_file:dir create_dir_perms;
    allow preloads_copy preloads_data_file:file create_file_perms;
    allow preloads_copy preloads_media_file:dir create_dir_perms;
    allow preloads_copy preloads_media_file:file create_file_perms;
    
    # Allow to copy from /postinstall
    allow preloads_copy system_file:dir r_dir_perms;
    
    在以下位置找到 SELinux 域文件示例: /device/google/marlin/+/main/sepolicy/preloads_copy.te
  4. 在新的 /sepolicy/file_contexts 文件中注册该域
    /system/bin/preloads_copy\.sh     u:object_r:preloads_copy_exec:s0
    
    在以下位置找到 SELinux 上下文文件示例: device/google/marlin/sepolicy/preloads_copy.te
  5. 在构建时,包含预加载内容的目录必须复制到 system_other 分区
    # Copy contents of preloads directory to system_other partition
    PRODUCT_COPY_FILES += \
        $(call find-copy-subdir-files,*,vendor/google_devices/marlin/preloads,system_other/preloads)
    
    这是一个 Makefile 中更改的示例,该更改允许从供应商的 Git 存储库(在我们的例子中是 vendor/google_devices/marlin/preloads)复制 APK 缓存资源到 system_other 分区上的位置,该位置稍后将在设备首次启动时复制到 /data/preloads。此脚本在构建时运行以准备 system_other 镜像。它期望预加载内容在 vendor/google_devices/marlin/preloads 中可用。OEM 可以自由选择实际的存储库名称/路径。
  6. APK 缓存位于 /data/preloads/file_cache 中,并具有以下布局
    /data/preloads/file_cache/
        app.package.name.1/
              file1
              fileN
        app.package.name.N/
    
    这是设备上的最终目录结构。只要最终文件结构与上述结构相同,OEM 可以自由选择任何实现方法。

方法 2:工厂刷写的用户数据镜像上的内容

这种替代方法假设预加载内容已包含在 /data 分区上的 /data/preloads 目录中。

优点:开箱即用 - 无需进行设备自定义即可在首次启动时复制文件。内容已在 /data 分区上。

缺点:预加载内容在恢复出厂设置后会丢失。虽然这对某些人来说可能是可以接受的,但对于在进行质量控制检查后恢复出厂设置的 OEM 来说,可能并不总是适用。

一个新的 @SystemApi 方法 getPreloadsFileCache() 已添加到 android.content.Context 中。它返回预加载缓存中特定于应用程序的目录的绝对路径。

添加了一个新方法 IPackageManager.deletePreloadsFileCache,该方法允许删除预加载目录以回收所有空间。该方法只能由具有 SYSTEM_UID 的应用程序调用,即系统服务器或“设置”。

应用准备

只有特权应用才能访问预加载缓存目录。为了获得该访问权限,应用必须安装在 /system/priv-app 目录中。

验证

  • 首次启动后,设备应在 /data/preloads/file_cache 目录中包含内容。
  • 如果设备存储空间不足,则必须删除 file_cache/ 目录中的内容。

使用示例 ApkCacheTest 应用来测试 APK 缓存。

  1. 通过从根目录运行此命令来构建应用
    make ApkCacheTest
    
  2. 将应用作为特权应用安装。(请记住,只有特权应用才能访问 APK 缓存。)这需要 root 过的设备
    adb root && adb remount
    adb shell mkdir /system/priv-app/ApkCacheTest
    adb push $ANDROID_PRODUCT_OUT/data/app/ApkCacheTest/ApkCacheTest.apk /system/priv-app/ApkCacheTest/
    adb shell stop && adb shell start
    
  3. 如果需要,模拟文件缓存目录及其内容(也需要 root 权限)
    adb shell mkdir -p /data/preloads/file_cache/com.android.apkcachetest
    adb shell restorecon -r /data/preloads
    adb shell "echo "Test File" > /data/preloads/file_cache/com.android.apkcachetest/test.txt"
    
  4. 测试应用。安装应用并创建测试 file_cache 目录后,打开 ApkCacheTest 应用。它应该显示一个文件 test.txt 及其内容。请参阅此屏幕截图,以了解这些结果在用户界面中的显示方式。

    图 1. ApkCacheTest 结果。