ランタイム対応 SDK をビルドして使用する

1
Key concepts
2
Set up your development environment
3
Build an RE SDK
4
Consume the RE SDK
5
Testing, and building for distribution

ランタイム対応 SDK をビルドする

ランタイム対応 SDK をビルドするには、次の手順を完了する必要があります。

  1. プロジェクト構造を設定する
  2. プロジェクトとモジュールの依存関係を準備する
  3. SDK のビジネス ロジックを追加する
  4. SDK API を定義する
  5. SDK のエントリ ポイントを指定する

プロジェクト構造を設定する

プロジェクトは次のモジュールに整理することをおすすめします。

  1. アプリ モジュール - SDK のテストと開発に使用するテストアプリ。実際のアプリ クライアントが持つものを表します。アプリは、既存の広告ライブラリ モジュール(ランタイム対応 SDK)に依存している必要があります。
  2. 既存の広告ライブラリ モジュール(ランタイム対応 SDK) - 既存の「ランタイム非対応」SDK ロジックと静的にリンクされた SDK を含む Android ライブラリ モジュール。
    • まず、機能を分割できます。たとえば、一部のコードは既存の SDK で処理し、一部のコードはランタイム対応 SDK にルーティングできます。
  3. ランタイム対応広告ライブラリ モジュール - ランタイム対応 SDK のビジネス ロジックが含まれています。これは、Android Studio で Android ライブラリ モジュールとして作成できます。
  4. ランタイム対応 ASB モジュール - ランタイム対応 SDK コードを ASB にバンドルするパッケージ データを定義します。
    • com.android.privacy-sandbox-sdk タイプを使用して手動で作成する必要があります。これを行うには、新しいディレクトリを作成します。
    • このモジュールにはコードを含めず、ランタイム対応の広告ライブラリ モジュールへの依存関係を含む空の build.gradle ファイルのみを含めます。このファイルの内容は、SDK を準備するで定義されています。
    • このモジュールを settings.gradle ファイルと既存の広告ライブラリ モジュールに含めることを忘れないでください。

このガイドのプロジェクト構造は提案です。SDK に別の構造を選択し、同じ技術原則を適用することもできます。アプリ モジュールとライブラリ モジュールのコードをモジュール化するために、他の追加モジュールをいつでも作成できます。

SDK を準備する

ランタイム対応 SDK 開発用にプロジェクトを準備するには、まずツールとライブラリの依存関係を定義する必要があります。

  • プライバシー サンドボックスを搭載していないデバイス(Android 13 以下)をサポートする SDK ランタイムの下位互換性ライブラリandroidx.privacysandbox.sdkruntime:
  • 広告の表示をサポートする UI ライブラリ(androidx.privacysandbox.ui:
  • SDK API 宣言と shim 生成をサポートする SDK デベロッパー ツール androidx.privacysandbox.tools:
  1. このフラグをプロジェクトの gradle.properties ファイルに追加して、ランタイム対応 SDK を作成する機能を有効にします。

    # This enables the Privacy Sandbox for your project on Android Studio.
    android.experimental.privacysandboxsdk.enable=true
    android.experimental.privacysandboxsdk.requireServices=false
    
  2. ヘルパー Jetpack ライブラリやその他の依存関係を含めるように、プロジェクトの build.gradle を変更します。

    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    buildscript {
        ext.kotlin_version = '1.9.10'
        ext.ksp_version = "$kotlin_version-1.0.13"
        ext.privacy_sandbox_activity_version = "1.0.0-alpha01"
        ext.privacy_sandbox_sdk_runtime_version = "1.0.0-alpha13"
        ext.privacy_sandbox_tools_version = "1.0.0-alpha09"
        ext.privacy_sandbox_ui_version = "1.0.0-alpha09"
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        }
    }
    
    plugins {
        id 'com.android.application' version '8.4.0-alpha13' apply false
        id 'com.android.library' version '8.4.0-alpha13' apply false
    
        // These two plugins do annotation processing and code generation for the sdk-implementation.
        id 'androidx.privacysandbox.library' version '1.0.0-alpha02' apply false
        id 'com.google.devtools.ksp' version "$ksp_version" apply false
    
        id 'org.jetbrains.kotlin.jvm' version '1.9.10' apply false
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    
  3. これらの依存関係を含めるように、ランタイム対応広告ライブラリ(RE SDK)モジュールの build.gradle ファイルを更新します。

    dependencies {
        // This allows Android Studio to parse and validate your SDK APIs.
        ksp "androidx.privacysandbox.tools:tools-apicompiler:$privacy_sandbox_tools_version"
    
        // This contains the annotation classes to decorate your SDK APIs.
        implementation "androidx.privacysandbox.tools:tools:$privacy_sandbox_tools_version"
    
        // This is runtime dependency required by the generated server shim code for
        // backward compatibility.
        implementation "androidx.privacysandbox.sdkruntime:sdkruntime-provider:$privacy_sandbox_sdk_runtime_version"
    
        // These are runtime dependencies required by the generated server shim code as
        // they use Kotlin.
        implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
        implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
    
        // This is the core part of the UI library to help with UI notifications.
        implementation "androidx.privacysandbox.ui:ui-core:$privacy_sandbox_ui_version"
    
        // This helps the SDK open sessions for the ad.
        implementation "androidx.privacysandbox.ui:ui-provider:$privacy_sandbox_ui_version"
    
        // This is needed if your SDK implements mediation use cases
        implementation "androidx.privacysandbox.ui:ui-client:$privacy_sandbox_ui_version"
    }
    
  4. ランタイム対応 ASB モジュールの build.gradle ファイルを次のように置き換えます。

    plugins {
        id 'com.android.privacy-sandbox-sdk'
    }
    
    android {
        compileSdk 34
        minSdk 21
    
        bundle {
            // This is the package name of the SDK that you want to publish.
            // This is used as the public identifier of your SDK.
            // You use this later on to load the runtime-enabled SDK
            packageName = '<package name of your runtime-enabled SDK>'
    
            // This is the version of the SDK that you want to publish.
            // This is used as the public identifier of your SDK version.
            setVersion(1, 0, 0)
    
            // SDK provider defined in the SDK Runtime library.
            // This is an important part of the future backwards compatibility
            // support, most SDKs won't need to change it.
            sdkProviderClassName = "androidx.privacysandbox.sdkruntime.provider.SandboxedSdkProviderAdapter"
    
            // This is the class path of your implementation of the SandboxedSdkProviderCompat class.
            // It's the implementation of your runtime-enabled SDK's entry-point.
            // If you miss this step, your runtime-enabled SDK will fail to load at runtime:
            compatSdkProviderClassName = "<your-sandboxed-sdk-provider-compat-fully-qualified-class-name>"
        }
    }
    
    dependencies {
        // This declares the dependency on your runtime-enabled ad library module.
        include project(':<your-runtime-enabled-ad-library-here>')
    }
    
  5. 既存の広告ライブラリ(RA SDK)モジュールの build.gradle ファイルを更新して、次の依存関係を含めます。

    dependencies {
        // This declares the client's dependency on the runtime-enabled ASB module.
        //  ⚠️ Important: We depend on the ASB module, not the runtime-enabled module.
        implementation project(':<your-runtime-enabled-asb-module-here>')
    
        // Required for backwards compatibility on devices where SDK Runtime is unavailable.
        implementation "androidx.privacysandbox.sdkruntime:sdkruntime-client:$privacy_sandbox_sdk_runtime_version"
    
        // This is required to display banner ads using the SandboxedUiAdapter interface.
        implementation "androidx.privacysandbox.ui:ui-core:$privacy_sandbox_ui_version"
        implementation "androidx.privacysandbox.ui:ui-client:$privacy_sandbox_ui_version"
    
        // This is required to use SDK ActivityLaunchers.
        implementation "androidx.privacysandbox.activity:activity-core:$privacy_sandbox_activity_version"
        implementation "androidx.privacysandbox.activity:activity-client:$privacy_sandbox_activity_version"
    }
    

SDK のビジネス ロジックを追加する

ランタイム対応の広告ライブラリ モジュール内で、通常どおりに SDK のビジネス ロジックを実装します。

移行する既存の SDK がある場合は、この段階でビジネス ロジック、インターフェース、システム対応関数を必要なだけ移動しますが、将来の完全な移行を考慮してください。

ストレージ、Google Play 広告 ID、アプリセット ID にアクセスする必要がある場合は、以下のセクションをご覧ください。

SDK でストレージ API を使用する

SDK ランタイムの SDK は、アプリの内部ストレージに対するアクセス、読み取り、書き込みができなくなります。その逆も同様です。

SDK ランタイムには、アプリとは別の独自の内部ストレージ領域が割り当てられます。

SDK は、SandboxedSdkProvider#getContext() によって返される Context オブジェクトのファイル ストレージ API を使用して、この個別の内部ストレージにアクセスできます。

SDK は内部ストレージのみを使用できるため、Context.getFilesDir()Context.getCacheDir() などの内部ストレージ API のみが機能します。その他の例については、内部ストレージからアクセスするをご覧ください。

SDK ランタイムから外部ストレージへのアクセスはサポートされていません。API を呼び出して外部ストレージにアクセスすると、例外がスローされるか、null が返されます。以下に例をいくつか示します。

ストレージには、SandboxedSdkProvider.getContext() から返される Context を使用する必要があります。アプリ コンテキストなど、他の Context オブジェクト インスタンスでファイル ストレージ API を使用すると、あらゆる状況において想定どおりに動作するとは限りません。

次のコード スニペットは、SDK ランタイムでストレージを使用する方法を示しています。

class SdkServiceImpl(private val context: Context) : SdkService {
    override suspend fun getMessage(): String = "Hello from Privacy Sandbox!"

    override suspend fun createFile(sizeInMb: Int): String {
        val path = Paths.get(
            context.dataDir.path, "file.txt"
        )

        withContext(Dispatchers.IO) {
            Files.deleteIfExists(path)
            Files.createFile(path)
            val buffer = ByteArray(sizeInMb * 1024 * 1024)
            Files.write(path, buffer)
        }

        val file = File(path.toString())
        val actualFileSize: Long = file.length() / (1024 * 1024)
        return "Created $actualFileSize MB file successfully"
    }
}

各 SDK ランタイムの個別の内部ストレージ内で、各 SDK には独自のストレージ ディレクトリがあります。SDK ごとのストレージは、SDK ランタイムの内部ストレージを論理的に分離したものです。これにより、各 SDK が使用するストレージの量を確保できます。

Context オブジェクトのすべての内部ストレージ API は、各 SDK のストレージ パスを返します。

Google Play 開発者サービスから提供された広告 ID にアクセスする

Google Play 開発者サービスから提供された広告 ID に SDK がアクセスする必要がある場合は、AdIdManager#getAdId() を使用して値を非同期で取得します。

Google Play 開発者サービスが提供するアプリセット ID にアクセスする

SDK が Google Play 開発者サービスから提供されたアプリセット ID にアクセスする必要がある場合は、AppSetIdManager#getAppSetId() を使用して値を非同期で取得します。

SDK API を宣言する

ランタイム対応 SDK をランタイム外からアクセスできるようにするには、クライアント(RA SDK またはクライアント アプリ)が使用できる API を定義する必要があります。

アノテーションを使用してこれらのインターフェースを宣言します。

アノテーション

SDK API は、次のアノテーションを使用して、Kotlin でインターフェースとデータクラスとして宣言する必要があります。

アノテーション
@PrivacySandboxService
  • RE SDK のエントリ ポイントを定義する
  • 一意である必要があります
@PrivacySandboxInterface
  • さらなるモジュール化とインターフェースの公開を可能にする
  • 複数のインスタンスを持つことができる
@PrivacySandboxValue
  • プロセス間のデータ送信を可能にする
  • 異なる型の複数の値を返すことができる不変構造体と同様
@PrivacySandboxCallback
  • コールバックで API を宣言する
  • クライアント コードを呼び出すバックチャネルを提供します

これらのインターフェースとクラスは、ランタイム対応の広告ライブラリ モジュールの内部の任意の場所で定義する必要があります。

これらのアノテーションの使用方法については、次のセクションをご覧ください。

@PrivacySandboxService

@PrivacySandboxService
interface SdkService {
    suspend fun getMessage(): String

    suspend fun createFile(sizeInMb: Int): String

    suspend fun getBanner(request: SdkBannerRequest, requestMediatedAd: Boolean): SdkSandboxedUiAdapter?

    suspend fun getFullscreenAd(): FullscreenAd
}

@PrivacySandboxInterface

@PrivacySandboxInterface
interface SdkSandboxedUiAdapter : SandboxedUiAdapter

@PrivacySandboxValue

@PrivacySandboxValue
data class SdkBannerRequest(
    /** The package name of the app. */
    val appPackageName: String,
    /**
     *  An [SdkActivityLauncher] used to launch an activity when the banner is clicked.
     */
    val activityLauncher: SdkActivityLauncher,
    /**
     * Denotes if a WebView banner ad needs to be loaded.
     */
    val isWebViewBannerAd: Boolean
)

@PrivacySandboxCallback

@PrivacySandboxCallback
interface InAppMediateeSdkInterface {
    suspend fun show()
}

サポートされているタイプ

ランタイム対応 SDK API は次の型をサポートしています。

  • Java プログラミング言語のすべてのプリミティブ型(int、long、char、boolean など)
  • 文字列
  • @PrivacySandboxInterface または @PrivacySandboxCallback でアノテーションが付けられた Kotlin インターフェース
  • @PrivacySandboxValue でアノテーションが付けられた Kotlin データクラス
  • java.lang.List - List 内のすべての要素は、サポートされているデータ型のいずれかである必要があります。

その他の注意点:

  • @PrivacySandboxValue でアノテーションされたデータクラスに @PrivacySandboxCallback 型のフィールドを含めることはできません
  • 戻り値の型に @PrivacySandboxCallback でアノテーションが付けられた型を含めることはできません
  • リストに @PrivacySandboxInterface または @PrivacySandboxCallback でアノテーションされた型の要素を含めることはできません

非同期 API

SDK API は常に別のプロセスを呼び出すため、これらの呼び出しがクライアントの呼び出しスレッドをブロックしないようにする必要があります。

これを実現するには、@PrivacySandboxService@PrivacySandboxInterface@PrivacySandboxCallback でアノテーションが付けられたインターフェース内のすべてのメソッドを非同期 API として明示的に宣言する必要があります。

非同期 API は、Kotlin で次の 2 つの方法で実装できます。

  1. 一時停止関数を使用します。
  2. オペレーションの完了時や、オペレーションの進行中の他のイベントの通知を受け取るコールバックを受け入れます。関数の戻り値の型は Unit でなければなりません。

例外

SDK API は、チェック済みの例外をサポートしていません。

生成されたシムコードは、SDK によってスローされたランタイム例外をキャッチし、原因に関する情報がラップされた PrivacySandboxException としてクライアントにスローします。

UI ライブラリ

バナーなどの広告を表すインターフェースがある場合は、読み込まれた広告のセッションを開くために SandboxedUiAdapter インターフェースも実装する必要があります。

これらのセッションは、クライアントと SDK の間のサイドチャネルを形成し、次の 2 つの主な目的を果たします。

  • UI の変更が発生するたびに通知を受け取ります。
  • UI の表示に変更があった場合は、クライアントに通知します。

クライアントは @PrivacySandboxService でアノテーションされたインターフェースを使用して SDK と通信できるため、広告を読み込むための API をこのインターフェースに追加できます。

クライアントが広告の読み込みをリクエストしたら、広告を読み込み、SandboxedUiAdapter を実装するインターフェースのインスタンスを返します。これにより、クライアントはその広告のセッションを開くリクエストを送信できます。

クライアントがセッションの開始をリクエストすると、ランタイム対応の SDK は、提供された広告レスポンスとコンテキストを使用して広告ビューを作成できます。

これを実現するには、SandboxedUiAdapter.Session インターフェースを実装するクラスを作成し、SandboxedUiAdapter.openSession() が呼び出されたときに、Session クラスのインスタンスをパラメータとして渡して client.onSessionOpened() を呼び出すようにします。

class SdkSandboxedUiAdapterImpl(
   private val sdkContext: Context,
   private val request: SdkBannerRequest,
) : SdkSandboxedUiAdapter {
   override fun openSession(
       context: Context,
       windowInputToken: IBinder,
       initialWidth: Int,
       initialHeight: Int,
       isZOrderOnTop: Boolean,
       clientExecutor: Executor,
       client: SandboxedUiAdapter.SessionClient
   ) {
       val session = SdkUiSession(clientExecutor, sdkContext, request)
       clientExecutor.execute {
           client.onSessionOpened(session)
       }
   }
}

このクラスは、UI の変更が発生するたびに通知も受け取ります。このクラスを使用すると、広告のサイズを変更したり、構成が変更されたタイミングを把握したりできます。

ランタイムの UI 表示 API について詳しくは、こちらをご覧ください。

アクティビティのサポート

プライバシー サンドボックスから SDK 所有のアクティビティを開始するには、UI ライブラリによって提供される SdkActivityLauncher オブジェクトを受け取るように SDK API を変更する必要があります。

たとえば、次の SDK API はアクティビティを起動する必要があるため、SdkActivityLauncher パラメータが必要です。

@PrivacySandboxInterface
interface FullscreenAd {
    suspend fun show(activityLauncher: SdkActivityLauncher)
}

SDK エントリ ポイント

抽象クラス SandboxedSdkProvider は、SDK ランタイムが読み込まれた SDK とのやり取りに使用する API をカプセル化します。

ランタイム対応 SDK は、SDK ランタイムが通信できるようにエントリ ポイントを生成するために、この抽象クラスを実装する必要があります

下位互換性をサポートするため、次のクラスが導入されました。

SDK ランタイムの下位互換性について詳しくは、こちらをご覧ください。

シム生成ツールは、抽象化の別のレイヤを追加します。つまり、@PrivacySandboxService でアノテーションを付けたインターフェースを使用して、AbstractSandboxedSdkProvider という抽象クラスを生成します。

このクラスは SandboxedSdkProviderCompat を拡張し、アノテーション付きインターフェースと同じパッケージにあります。

// Auto-generated code.
abstract class AbstractSandboxedSdkProvider : SandboxedSdkProviderCompat {
    abstract fun createMySdk(context: Context): MySdk
}

この生成されたクラスは、Context を受け取り、エントリ ポイント アノテーション付きのインターフェースが返されることを想定する単一の抽象ファクトリー メソッドを公開します。

このメソッドは、@PrivacySandboxService インターフェースにちなんで名付けられ、名前の先頭に create が付加されます。たとえば、インターフェースの名前が MySdk の場合、ツールは createMySdk を生成します。

エントリ ポイントを完全に接続するには、ランタイム対応 SDK で @PrivacySandboxService アノテーション付きインターフェースの実装を生成された AbstractSandboxedSdkProvider に提供する必要があります。

class MySdkSandboxedSdkProvider : AbstractSandboxedSdkProvider() {
    override fun createMySdk(context: Context): MySdk = MySdkImpl(context)
}

ASB モジュールの変更

ASB モジュールの build.gradle の compatSdkProviderClassName フィールドで、SandboxedSdkProviderCompat の実装の完全修飾クラス名を宣言する必要があります。

これは前の手順で実装したクラスです。ASB モジュールの build.gradle を次のように変更します。

bundle {
    packageName = '<package name of your runtime-enabled SDK>'
    setVersion(1, 0, 0)

    // SDK provider defined in the SDK Runtime library.
    sdkProviderClassName = "androidx.privacysandbox.sdkruntime.provider.SandboxedSdkProviderAdapter"
    // This is the class that extends AbstractSandboxedSdkProvider,
    // MySdkSandboxProvider as per the example provided.
    compatSdkProviderClassName = "com.example.mysdk.MySdkSandboxProvider"
}

ステップ 2: 開発環境をセットアップする ステップ 4: ランタイム対応 SDK を使用する