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 分区(此操作在首次启动期间完成)后,无线 (OTA) 更新就会将 B 分区用于下载系统映像的已更新版本。

因此,APK 缓存无法通过 OTA 进行更新;只能在出厂时预加载到设备上。恢复出厂设置只会影响 /data 分区。系统的 B 分区中仍会有预加载内容,直到系统下载 OTA 映像为止。恢复出厂设置后,系统会再次进行首次启动。这意味着,如果将 OTA 映像下载到 B 分区,然后将设备恢复出厂设置,那么 APK 缓存将无法使用。

实现

方法 1. system_other 分区上的内容

Pro:预加载内容不会在恢复出厂设置后丢失,系统会在重新启动后从 B 分区复制这些内容。

Con: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 中的更改示例,完成这项更改后,就可以将 APK 缓存资源从供应商的 Git 代码库(在本示例中是 vendor/google_devices/marlin/preloads)复制到 system_other 分区上的相应位置;稍后,在设备首次启动时,APK 缓存资源便会复制到 /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 目录中。

Pro:开箱即用,无需进行设备自定义即可在首次启动时复制文件。预加载内容已位于 /data 分区。

Con:预加载内容会在恢复出厂设置后丢失。虽然这对部分 OEM 来说是可以接受的,但对在完成质量控制检查后要将设备恢复出厂设置的 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 结果。