SDK ランタイムの下位互換性

このドキュメントでは、デベロッパーが SDK ランタイムに移行する場合に役立つ新しい Jetpack ライブラリをご紹介します。 以前のバージョンの 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 は、アプリが依存するすべての SDK を含むアプリ APK セットのバリアントをビルドします。各 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 を追加し、SdkSandboxManager ではなく SdkSandboxManagerCompat を使用します。

アプリが SDK を読み込もうとすると、ライブラリはまず、ビルド中に該当 SDK がアプリにバンドルされたかどうかを確認します。バンドルされている場合、ライブラリは SDK 分割から該当 SDK を抽出してアプリプロセスに読み込みます。バンドルされていない場合、ライブラリは該当 SDK の読み込みをプラットフォームの API にデリゲートします。

アセットから SDK を抽出する

アプリがバンドルの SDK を読み込もうとすると、SDK ランタイム クライアント ライブラリは、該当 SDK の DEX ファイルがすでにデバイス ストレージ(code_cache)に抽出されているかどうかを確認し、抽出されていない場合はアセットから抽出します。

通常、ライブラリがファイルを抽出するのは、アプリのインストールまたは更新後の 1 回のみです。

ストレージの空き容量が許容しきい値(現在は 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 環境

SDK ランタイム プロバイダ ライブラリから SDK デベロッパーに API が提供されます。これらの API はプラットフォームの API に似ていますが、SDK ランタイム環境と SDK ランタイム クライアント ライブラリの両方で SDK を読み込むことができます。

ライブラリの SDK を使用するには、androidx.privacysandbox.sdkruntime:sdkruntime-provider 依存関係を追加し、SandboxedSdkProvider ではなく SandboxedSdkProviderCompat を拡張する必要があります。

また、SandboxedSdkProviderAdapter を SDK プロバイダとして使用することも必要です。これで、対応するプロバイダを SDK ランタイム環境に読み込むことができます。

SdkSandboxControllerCompat は、SDK が SDK ランタイムに読み込まれた場合はプラットフォームの API にデリゲートし、SDK がバンドルの SDK として読み込まれた場合は SDK ランタイム クライアント ライブラリにデリゲートします。

バンドルの SDK の場合、ライブラリは SDK ランタイム環境と同様の動作をエミュレートする方法で SDK 環境を変更します。

以下のセクションでは、SDK が SDK ランタイム クライアント ライブラリで読み込まれた場合の想定される動作について説明します。

SDK リソース

SDK がアプリプロセスで読み込まれた場合は、SDK リソース(res/)がサポートされます。 Bundletool によって、すべての SDK のリソースがアプリリソースと統合されます。

競合を回避するため、すべてのリソース ID の packageId 接頭辞を変更することで SDK リソースを再マッピングします。

SDK が SDK ランタイム クライアント ライブラリで読み込まれた場合は、ランタイムで packageId が更新され、再マッピングされたリソースを R クラスを使用してアドレス指定できるようになります。

Java リソース

SDK がアプリプロセスで読み込まれた場合は、Java リソースがサポートされます。Bundletool によって、すべての SDK の Java リソースがアプリアセット内の特別なディレクトリにコピーされます。SDK ランタイム クライアント ライブラリは、中間クラスローダーを使用して、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

SDK ランタイム クライアント ライブラリは、アプリプロセスで読み込まれたバンドルの SDK に SdkSandboxControllerCompat 実装を提供します。

API がクライアント ライブラリでサポートされていない場合は(アプリ バージョンよりも新しいバージョンのライブラリでビルドされた SDK など)、最適なフォールバックが使用されます(NoOps または例外)。

バージョニング

SDK ランタイム クライアント ライブラリは、バンドルの SDK を読み込むと、SDK の内部で SDK ランタイム プロバイダ ライブラリとの handshake を実行します。handshake の際、これらのライブラリはバージョンを交換して動作を調整し、利用できない API を最適なフォールバックに置き換えます(NoOps または例外)。

アプリ デベロッパーと SDK デベロッパーのどちらも、最新バージョンのライブラリを使用することが強く推奨されます。最新バージョンを使用しないと、どちらの側のサポートも必要とする機能を利用できなくなる可能性があります。

すべてのバージョンの SDK ランタイム クライアント ライブラリは、すべてのバージョンの SDK ランタイム プロバイダ ライブラリを使用して SDK を読み込むことができます。その逆も同様です。

今後はこれを変更し、特定のバージョンのプロバイダ ライブラリを使用して SDK を読み込むためのクライアント ライブラリの最小バージョン要件を設定する予定です。

これにより、断片化が最小限に抑えられ、バンドルの SDK が正常に読み込まれた場合はほとんどの API がサポートされるようになります。