本文档介绍了如何设计 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 分区上的内容
优点:预加载内容不会在恢复出厂设置后丢失,系统会在重新启动后从 B 分区复制这些内容。
缺点:B 分区上需要有可用空间。在恢复出厂设置后进行启动时,需要额外时间来复制预加载内容。
为了在首次启动期间复制预加载内容,系统会调用 /system/bin/preloads_copy.sh
中的脚本。系统会通过单个参数(system_b
分区的只读装载点的路径)调用该脚本:
如需实现此功能,请完成以下特定于设备的更改。以下是 Marlin 设备的示例:
- 将执行复制操作的脚本添加到
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 - 修改
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 - 在文件
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/+/master/sepolicy/preloads_copy.te - 在新的
文件中注册该域:/sepolicy/file_contexts /system/bin/preloads_copy\.sh u:object_r:preloads_copy_exec:s0
在以下位置查找示例 SELinux 上下文描述文件:device/google/marlin/sepolicy/preloads_copy.te - 在构建时,您必须将具有预加载内容的目录复制到
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 可以自由选择实际的代码库名称/路径。 - 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 来说是可以接受的,但对于在完成质量控制检查后要对设备恢复出厂设置的 OEM 来说,这种方法并不总是行得通。
将一种新的 @SystemApi 方法 getPreloadsFileCache()
添加到了 android.content.Context
。该方法会返回预加载缓存中某个应用专属目录的绝对路径。
添加了新方法 IPackageManager.deletePreloadsFileCache
,它允许删除预加载目录以回收所有空间。此方法只能由具有 SYSTEM_UID 的应用(即系统服务器或设置)进行调用。
应用准备
只有特权应用才可以访问预加载缓存目录。如需获得访问权限,应用必须安装在 /system/priv-app
目录中。
验证
- 首次启动后,设备的
/data/preloads/file_cache
目录中应该包含相关内容。 - 如果设备的存储空间不足,必须删除
file_cache/
目录中的内容。
使用示例 ApkCacheTest 应用测试 APK 缓存。
- 通过在根目录下运行以下命令来编译应用:
make ApkCacheTest
- 将应用安装为特权应用(请注意,只有特权应用可以访问 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
- 如果需要,模拟文件缓存目录及其内容(此操作也需要 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"
- 测试应用。安装应用并创建测试
file_cache
目录之后,打开 ApkCacheTest 应用。该应用中应该会显示一个test.txt
文件及其内容。请参见以下屏幕截图,了解这些结果在界面上的显示方式。图 1. ApkCacheTest 结果