SDK 运行时的向后兼容性

本文档提出了一个新的 Jetpack 库,以帮助开发者迁移到 SDK 运行时。其中阐述了以前的 Android 平台版本(从构建到执行)如何支持 SDK 运行时,以及开发者可以预见运行时环境中有哪些差异或限制。借助此库,开发者可以为其应用或 SDK 创建单个版本,并使其能够在支持或不支持 SDK 运行时的设备上运行。

向后兼容性是通过以下组件实现的:

  • Android Gradle 插件 (AGP) + Bundletool 通过将 SDK 运行时捆绑到 APK 中,为不支持 SDK 运行时的设备构建应用变体。

  • SDK 运行时客户端库 (androidx.privacysandbox.sdkruntime:sdkruntime-client) 从应用资源加载捆绑的 SDK,并在不支持 SDK 运行时的设备上模拟 SDK 运行时。

  • SDK 运行时提供方库 (androidx.privacysandbox.sdkruntime:sdkruntime-provider) 为 SDK 提供了一个允许从 SDK 运行时客户端库加载的 API。

使用 Bundletool 分发 SDK

在支持 SDK 运行时的设备上,SDK 将作为单独的软件包提供和安装。

为了支持缺少 SDK 运行时支持的平台版本,bundletool 将构建应用 APK 集的一个或多个变体,其中包含应用所依赖的所有 SDK。每个 SDK 都打包为单独的 APK 拆分。此外,还会执行以下转换:

  1. 将 SDK 字节码 (DEX) 文件作为资源复制到 SDK 拆分中。
  2. 将 SDK Java 资源作为资源复制到 SDK 拆分中。
  3. 重新映射 SDK 资源,并将其与应用资源合并。
  4. 为 SDK 运行时客户端库生成配置。

使用 SDK 运行时客户端库加载 SDK

SDK 运行时客户端库提供与平台 API 类似的 API,但支持 SDK 运行时环境中的 SDK 以及与变体应用捆绑的 SDK。

如需使用 SDK 运行时客户端库,请添加依赖项 androidx.privacysandbox.sdkruntime:sdkruntime-client,并使用 SdkSandboxManagerCompat 而非 SdkSandboxManager

当应用尝试加载 SDK 时,该库会先检查 SDK 在构建期间是否与应用捆绑在一起。如果是捆绑的 SDK,该库会从 SDK 拆分项中提取 SDK,并将其加载到应用进程中。如果 SDK 未与应用捆绑,则库会委托平台 API 加载 SDK。

从资源中提取 SDK

当应用尝试加载捆绑的 SDK 时,SDK 运行时客户端库会检查 SDK 的 DEX 文件是否已提取到设备存储空间 (code_cache),如果没有,则从资源中提取这些文件。

在应用安装或更新后,该库通常只会解压缩一次文件。

如果可用存储空间小于允许的阈值(目前为 100 MB),并且未提取任何 DEX 文件,该库会尝试直接从受支持设备(API 27 及更高级别)上的资源加载 SDK。这会导致内存占用量较大。

适用于 SDK 类的类加载器

为避免 SDK 和应用类之间发生冲突,所有 SDK 类均使用完全独立于主应用类加载器的单独类加载器进行加载。

在当前的 SDK 运行时设计中,应用与 SDK 之间的所有通信均使用 Binder IPC 调用进行。捆绑的 SDK 也使用相同的 SDK Binder 对象,而 Binder 事务序列化使应用开发者能够将 SDK Binder 对象转换为应用端的 SDK Binder 接口。

对于其他内部交互(例如初始化 SDK、向 SDK 提供控制器 API 等),该库使用反射和动态代理跨不同的类加载器工作。

SDK 环境

SDKRuntime 提供程序库为 SDK 开发者提供 API。这些 API 与平台 API 类似,但允许 SDK 运行时环境和 SDKRuntime 客户端库加载 SDK。

为了能够使用库 SDK,您需要添加 androidx.privacysandbox.sdkruntime:sdkruntime-provider 依赖项并扩展 SandboxedSdkProviderCompat,而不是 SandboxedSdkProvider

您还需要使用 SandboxedSdkProviderAdapter 作为 SDK 提供程序,以便在 SDK 运行时环境中加载兼容型提供程序。

在 SDK 运行时中加载 SDK 时,SdkSandboxControllerCompat 会委托给平台 API;当 SDK 作为捆绑的 SDK 加载时,会委托给 SDKRuntime 客户端库。

对于捆绑的 SDK,该库会以模拟与 SDK 运行时环境类似的行为的方式修改 SDK 环境。

以下部分将介绍 SDKRuntime 客户端库加载 SDK 时的预期行为。

SDK 资源

在应用进程中加载 SDK 后,系统支持 SDK 资源 (res/)。bundletool 会将所有 SDK 的资源与应用资源合并。

为避免冲突,系统会通过更改所有资源 ID 中的 packageId 前缀来重新映射 SDK 资源。

当 SDKRuntime 客户端库加载 SDK 时,运行时中的 packageId 会更新,以允许使用 R 类处理重新映射的资源。

Java 资源

在应用进程中加载 SDK 时,支持 Java 资源。bundletool 会将所有 SDK Java 资源复制到应用资源中的一个特殊目录中。 SDKRuntime 客户端库使用中间类加载器将所有与 Java 资源相关的调用重定向到新的根目录。

SDK 资源

SDK 资源与应用资源合并,而不重新映射。

SDK 存储

为了支持 SDK 存储,SDK 运行时客户端库会为应用存储空间中的每个捆绑的 SDK 创建一个专用根目录,并提供将此目录用作存储根目录的特殊上下文。

可以从 SandboxedSdkProviderCompat#getContext 检索此上下文。

支持的存储相关方法:

  • getDataDir
  • getCacheDir
  • getCodeCacheDir
  • getNoBackupFilesDir
  • getDir
  • getFilesDir
  • openFileInput
  • openFileOutput
  • deleteFile
  • getFileStreamPath
  • fileList
  • getDatabasePath
  • openOrCreateDatabase
  • moveDatabaseFrom - 仅在 SDK 上下文之间
  • deleteDatabase
  • databaseList
  • getSharedPreferences
  • moveSharedPreferencesFrom - 仅在 SDK 上下文之间
  • deleteSharedPreferences

可以通过对该上下文调用 createDeviceProtectedStorageContext() 来创建设备保护存储上下文。

SdkSandboxControllerCompat

SDKRuntime 客户端库可为应用进程中加载的捆绑 SDK 提供 SdkSandboxControllerCompat 实现。

如果客户端库不支持 API(例如,使用比应用版本更新版本的库构建的 SDK),系统将使用最合适的回退(空操作或异常)。

版本控制

当 SDKRuntime 客户端库加载捆绑的 SDK 时,它会与 SDK 内的 SDKRuntime 提供程序库执行握手。在握手期间,库会交换其版本并调整行为,以将不可用的 API 替换为最合适的回退(空操作或异常)。

强烈建议应用开发者和 SDK 开发者都使用最新版本的库,否则两部分都需要支持的功能可能无法使用。

任何版本的 SDKRuntime 客户端库都可以使用任何版本的 SDKRuntime 提供程序库加载 SDK,反之亦然。

将来,它将更改为使用特定版本的提供程序库加载 SDK 所需的最低客户端库版本。

这样可以最大限度地减少碎片,并确保在捆绑的 SDK 成功加载后,大多数 API 都受支持。