Backward compatibility for the SDK Runtime

This document proposes a new Jetpack library to help developers with migration to the SDK Runtime. It explains how the SDK Runtime will be supported for previous Android platform versions (from build to execution), and which differences or limitations in the runtime environment developers can expect. This library allows developers to create a single version of their app or SDK that includes the ability to run on devices with or without SDK Runtime support.

Backwards compatibility is achieved through the following components:

  • Android Gradle Plugin (AGP) + Bundletool builds an app variant for devices without SDK Runtime support by bundling the SDK Runtime into the APK.

  • SDK Runtime client library (androidx.privacysandbox.sdkruntime:sdkruntime-client) loads the bundled SDK from app assets and emulates the SDK Runtime on devices without SDK Runtime support.

  • SDK Runtime provider library (androidx.privacysandbox.sdkruntime:sdkruntime-provider) provides an API for SDKs to allow loading from the SDK Runtime client library.

SDK delivery with Bundletool

On devices with SDK Runtime support, SDKs will be delivered and installed as separate packages.

To support platform versions that lack SDK Runtime support, Bundletool will build one or more variants of the app APK set that include all SDKs the app depends on. Each SDK is packaged as a separate APK split. Moreover, the following transformations are performed:

  1. Copy SDK bytecode (DEX) files to the SDK split as assets.
  2. Copy SDK Java resources to the SDK split as assets.
  3. Remap SDK resources and merge them with app resources.
  4. Generate configs for the SDK Runtime client library.

Load SDKs with the SDK Runtime client library

The SDK Runtime client library provides APIs that are similar to platform APIs, but support both SDKs in the SDK Runtime environment and SDKs bundled with the variant app.

To use the SDK Runtime client library, add the dependency androidx.privacysandbox.sdkruntime:sdkruntime-client, and use SdkSandboxManagerCompat instead of SdkSandboxManager.

When an app tries to load an SDK, the library first checks if the SDK was bundled with the app during build. If it was bundled, the library extracts the SDK from the SDK split and loads it into the app process. If the SDK was not bundled with the app, the library delegates the platform API to load the SDK.

Extract an SDK from assets

When an app tries to load a bundled SDK, the SDK Runtime client library checks if the SDK's DEX files have already been extracted to device storage (code_cache), and if not, extracts them from assets.

The library will normally extract files only once after an app installation or update.

If the available storage space is less than the allowed threshold (currently 100 MB) and no DEX files are extracted, the library tries to load the SDK directly from assets on supported devices (API 27+). This results in a larger memory footprint.

Classloader for SDK classes

To avoid conflicts between SDKs and app classes, all SDK classes are loaded using a separate classloader completely independent from the main app classloader.

In current SDK Runtime design, all communications between an app and SDKs happen using Binder IPC calls. The same SDK Binder objects are used for bundled SDKs, and Binder transaction serialization allows app developers to cast SDK Binder objects to SDK Binder Interfaces on the app side.

For other internal interactions (such as initializing an SDK, providing a controller API to an SDK, and so on) the library uses Reflection and Dynamic Proxies to work across different classloaders.

SDK environment

The SDKRuntime Provider library provides APIs to SDK developers. These APIs are similar to platform APIs, but allow SDKs to be loaded by both the SDK Runtime environment and SDKRuntime Client library.

To be able to use the library SDK, you need to add the androidx.privacysandbox.sdkruntime:sdkruntime-provider dependency and extend SandboxedSdkProviderCompat instead of SandboxedSdkProvider.

You also need to use SandboxedSdkProviderAdapter as the SDK provider, to allow the compat provider to be loaded in the SDK Runtime environment.

SdkSandboxControllerCompat delegates to the platform API when the SDK is loaded in the SDK Runtime or delegates to the SDKRuntime Client library when the SDK is loaded as a bundled SDK.

For bundled SDKs, the library modifies the SDK environment in ways that emulate behavior similar to the SDK Runtime environment.

The next sections describe expected behaviors when the SDK is loaded by the SDKRuntime Client library.

SDK Resources

SDK Resources (res/) are supported when the SDK is loaded in the app process. Bundletool merges all SDKs' resources with app resources.

To avoid conflicts, SDK resources are remapped by changing the packageId prefix in all resource IDs.

When the SDK is loaded by the SDKRuntime Client library, packageId is updated in the runtime to allow addressing remapped resources using the R class.

Java resources

Java resources are supported when the SDK is loaded in the app process. Bundletool copies all SDK Java resources to a special directory in app assets. The SDKRuntime Client library uses an intermediate classloader to redirect all Java resource-related calls to the new root directory.

SDK Assets

SDK assets are merged with app assets without remapping.

SDK Storage

To support SDK Storage, the SDK Runtime Client library creates a dedicated root directory for each bundled SDK in app storage and provides a special context that uses this directory as the storage root.

This context can be retrieved from SandboxedSdkProviderCompat#getContext.

Supported storage-related methods:

  • getDataDir
  • getCacheDir
  • getCodeCacheDir
  • getNoBackupFilesDir
  • getDir
  • getFilesDir
  • openFileInput
  • openFileOutput
  • deleteFile
  • getFileStreamPath
  • fileList
  • getDatabasePath
  • openOrCreateDatabase
  • moveDatabaseFrom - only between SDK contexts
  • deleteDatabase
  • databaseList
  • getSharedPreferences
  • moveSharedPreferencesFrom - only between SDK contexts
  • deleteSharedPreferences

A device protected storage context can be created by calling createDeviceProtectedStorageContext() on that context.

SdkSandboxControllerCompat

The SDKRuntime Client library provides the SdkSandboxControllerCompat implementation to bundled SDKs loaded in the app process.

If APIs are not supported by the client library (for example with an SDK built with a version of the library more recent than the app version), the most suitable fallback will be used (no-op or exception).

Versioning

When the SDKRuntime Client library loads a bundled SDK it performs a handshake with the SDKRuntime Provider library inside the SDK. During the handshake, libraries exchange their versions and adjust behavior to substitute unavailable APIs with the most suitable fallback (no-op or exception).

Using the most recent version of the library is highly recommended for both app and SDK Developers, otherwise functionality that requires support in both parts may not be available.

Any version of the SDKRuntime Client library can load an SDK with any version of SDKRuntime Provider library and the other way around.

In the future that will be changed to the minimal client library version required to load the SDK with a particular version of the provider library.

This will minimize fragmentation and help ensure that most APIs will be supported if the bundled SDK successfully loaded.