| <html devsite><head> |
| <title>APK 缓存</title> |
| <meta name="project_path" value="/_project.yaml"/> |
| <meta name="book_path" value="/_book.yaml"/> |
| </head> |
| <body> |
| <!-- |
| Copyright 2017 The Android Open Source Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <p> |
| 本文档介绍了如何设计 APK 缓存解决方案,以在支持 A/B 分区的设备上快速安装预加载的应用。 |
| </p> |
| |
| <p> |
| 原始设备制造商 (OEM) 可以将预加载应用和热门应用放置在 APK 缓存中(对于<a href="/devices/tech/ota/ab_updates">采用 A/B 分区</a>的新设备而言,这种缓存会存储在通常为空的 B 分区中),而且这样不会影响面向用户的任何数据空间。新设备或最近恢复出厂设置的设备上有 APK 缓存时,用户基本上可以立即开始使用,而无需从 Google Play 下载 APK 文件。 |
| </p> |
| |
| <h2 id="use-cases">用例</h2> |
| |
| <ul> |
| <li>将预加载应用存储在 B 分区,以实现更快捷的设置 |
| </li><li>将热门应用存储在 B 分区,以实现更快速的恢复 |
| </li></ul> |
| |
| <h2 id="prerequisites">前提条件</h2> |
| |
| <p> |
| 要使用此功能,设备需要满足以下条件: |
| </p> |
| |
| <ul> |
| <li>安装了 Android 8.1 (O MR1) 版本 |
| </li><li>实现了 A/B 分区</li> |
| </ul> |
| |
| <p> |
| 您只能在首次启动期间复制预加载内容。这是因为,在支持 A/B 系统更新的设备上,B 分区不会真正存储系统映像文件,而是存储预加载内容(例如零售演示模式资源、OAT 文件和 APK 缓存)。将资源复制到 /data 分区(此操作在首次启动期间完成)后,<a href="/devices/tech/ota/">无线 (OTA) 更新</a>就会将 B 分区用于下载系统映像的已更新版本。 |
| </p> |
| |
| <p> |
| 因此,APK 缓存无法通过 OTA 进行更新;只能在出厂时预加载到设备上。恢复出厂设置只会影响 /data 分区。系统的 B 分区中仍会有预加载内容,直到系统下载 OTA 映像为止。恢复出厂设置后,系统会再次进行首次启动。这意味着,如果将 OTA 映像下载到 B 分区,然后将设备恢复出厂设置,那么 APK 缓存将无法使用。 |
| </p> |
| |
| <h2 id="implementation">实现</h2> |
| |
| <h3 id="approach-1-content-on-system_other-partition">方法 1. system_other 分区上的内容</h3> |
| |
| <p> |
| <strong>优点</strong>:预加载内容不会在恢复出厂设置后丢失,系统会在重新启动后从 B 分区复制这些内容。 |
| </p> |
| |
| <p> |
| <strong>缺点</strong>:B 分区上需要有可用空间。在恢复出厂设置后进行启动时,需要额外时间来复制预加载内容。 |
| </p> |
| |
| <p> |
| 为了在首次启动期间复制预加载内容,系统会调用 <code>/system/bin/preloads_copy.sh</code> 中的脚本。系统会通过单个参数(<code>system_b</code> 分区的只读装载点的路径)调用该脚本: |
| </p> |
| |
| <p> |
| 要实现此功能,请完成以下特定于设备的更改。以下是 Marlin 设备的示例:</p> |
| |
| <ol> |
| <li>将执行复制操作的脚本添加到 <code>device-common.mk</code> 文件(在本例中是 <code>device/google/marlin/device-common.mk</code>),如下所示: |
| |
| <pre class="devsite-click-to-copy"> |
| # 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 |
| </pre> |
| |
| 在以下位置查找示例脚本源代码:<a href="https://android.googlesource.com/device/google/marlin/+/master/preloads_copy.sh">device/google/marlin/preloads_copy.sh</a> |
| </li> |
| |
| <li>修改 <code>init.common.rc</code> 文件,以让其创建必要的<code> /data/preloads</code> 目录和子目录: |
| <pre class="devsite-click-to-copy"> |
| <code class="devsite-terminal">mkdir /data/preloads 0775 system system</code> |
| <code class="devsite-terminal">mkdir /data/preloads/media 0775 system system</code> |
| <code class="devsite-terminal">mkdir /data/preloads/demo 0775 system system</code> |
| </pre> |
| |
| 在以下位置查找示例 <code>init</code> 文件源代码:<a href="https://android.googlesource.com/device/google/marlin/+/master/init.common.rc">device/google/marlin/init.common.rc</a> |
| </li> |
| |
| <li>在文件 <code>preloads_copy.te</code> 中定义新的 SELinux 域: |
| |
| <pre class="devsite-click-to-copy"> |
| 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; |
| </pre> |
| |
| 在以下位置查找示例 SELinux 域文件:<a href="https://android.googlesource.com/device/google/marlin/+/master/sepolicy/preloads_copy.te">/device/google/marlin/+/master/sepolicy/preloads_copy.te</a> |
| </li> |
| |
| <li>在新的 <code><device>/sepolicy/file_contexts</device></code> 文件中注册该域: |
| |
| <pre class="devsite-click-to-copy"> |
| /system/bin/preloads_copy\.sh u:object_r:preloads_copy_exec:s0 |
| </pre> |
| |
| 在以下位置查找示例 SELinux 上下文描述文件:<a href="https://android.googlesource.com/device/google/marlin/+/master/sepolicy/preloads_copy.te">device/google/marlin/sepolicy/preloads_copy.te</a> |
| </li> |
| |
| <li>在编译时,您必须将具有预加载内容的目录复制到 <code>system_other</code> 分区: |
| <pre class="devsite-click-to-copy"> |
| # 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) |
| </pre> |
| |
| 这是 Makefile 中的更改示例,完成这项更改后,就可以将 APK 缓存资源从供应商的 Git 代码库(在本例中是 vendor/google_devices/marlin/preloads)复制到 system_other 分区上的相应位置;稍后,在设备首次启动时,APK 缓存资源便会复制到 /data/preloads。此脚本会在编译时运行,以准备 system_other 映像。它希望将预加载内容放置到 vendor/google_devices/marlin/preloads 中。OEM 可以自由选择实际的代码库名称/路径。 |
| </li> |
| |
| <li>APK 缓存位于 <code>/data/preloads/file_cache</code>,布局如下: |
| |
| <pre> |
| /data/preloads/file_cache/ |
| app.package.name.1/ |
| file1 |
| fileN |
| app.package.name.N/ |
| </pre> |
| |
| 这是设备上的最终目录结构。只要最终文件结构与上述结构相同,OEM 就可以自由选择任何实现方法。 |
| </li> |
| </ol> |
| |
| <h3 id="approach-2-content-on-user-data-image">方法 2. 在设备出厂时刷写的用户数据映像上的内容</h3> |
| |
| <p> |
| 此替代方式假设预加载内容已包含在 <code>/data</code> 分区上的 <code>/data/preloads</code> 目录中。 |
| </p> |
| |
| <p> |
| <strong>优点</strong>:开箱即用,无需进行设备自定义即可在首次启动时复制文件。预加载内容已位于 <code>/data</code> 分区。 |
| </p> |
| |
| <p> |
| <strong>缺点</strong>:预加载内容会在恢复出厂设置后丢失。虽然这对部分 OEM 来说是可以接受的,但对于在完成质量控制检查后要对设备恢复出厂设置的 OEM 来说,这种方法并不总是行得通。 |
| </p> |
| |
| <p> |
| 将一种新的 @SystemApi 方法 <code>getPreloadsFileCache()</code> 添加到了 <code>android.content.Context</code>。该方法会返回预加载缓存中某个应用专属目录的绝对路径。 |
| </p> |
| |
| <p> |
| 添加了新方法 <code>IPackageManager.deletePreloadsFileCache</code>,它允许删除预加载目录以回收所有空间。此方法只能由具有 SYSTEM_UID 的应用(即系统服务器或设置)进行调用。 |
| </p> |
| |
| <h2 id="app-preparation">应用准备</h2> |
| |
| <p> |
| 只有特权应用才可以访问预加载缓存目录。要获得访问权限,应用必须安装在<code> /system/priv-app</code> 目录中。 |
| </p> |
| |
| <h2 id="validation">验证</h2> |
| |
| <ul> |
| <li>首次启动后,设备的 <code>/data/preloads/file_cache</code> 目录中应该包含相关内容。 |
| </li><li>如果设备的存储空间不足,则必须删除 <code>file_cache/</code> 目录中的内容。</li> |
| </ul> |
| |
| <p>使用示例 <a href="https://android.googlesource.com/platform/development/+/master/samples/apkcachetest/">ApkCacheTest</a> 应用测试 APK 缓存。</p> |
| |
| <ol> |
| <li>通过在根目录下运行以下命令来编译应用:<pre class="devsite-click-to-copy"> |
| <code class="devsite-terminal">make ApkCacheTest</code> |
| </pre> |
| </li> |
| |
| <li>将应用安装为特权应用(请注意,只有特权应用可以访问 APK 缓存)。这需要一台已取得 root 权限的设备: |
| <pre class="devsite-click-to-copy"> |
| <code class="devsite-terminal">adb root && adb remount</code> |
| <code class="devsite-terminal">adb shell mkdir /system/priv-app/ApkCacheTest</code> |
| <code class="devsite-terminal">adb push $ANDROID_PRODUCT_OUT/data/app/ApkCacheTest/ApkCacheTest.apk /system/priv-app/ApkCacheTest/</code> |
| <code class="devsite-terminal">adb shell stop && adb shell start</code> |
| </pre> |
| </li> |
| |
| <li>如果需要,模拟文件缓存目录及其内容(此操作也需要 Root 权限): |
| <pre class="devsite-click-to-copy"> |
| <code class="devsite-terminal">adb shell mkdir -p /data/preloads/file_cache/com.android.apkcachetest</code> |
| <code class="devsite-terminal">adb shell restorecon -r /data/preloads</code> |
| <code class="devsite-terminal">adb shell "echo "Test File" > /data/preloads/file_cache/com.android.apkcachetest/test.txt"</code> |
| </pre> |
| </li> |
| |
| <li>测试应用。安装应用并创建测试 <code>file_cache</code> 目录之后,打开 ApkCacheTest 应用。该应用中应该会显示一个 <code>test.txt</code> 文件及其内容。请参见以下屏幕截图,了解这些结果在界面上的显示方式。 |
| |
| <p><img src="/devices/tech/perf/images/apk_cache_test_results.png"/></p> |
| <figcaption><strong>图 1.</strong> ApkCacheTest 结果</figcaption> |
| </li> |
| </ol> |
| |
| </body></html> |