建構和使用支援執行階段的 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 邏輯的 Android 程式庫模組,也就是靜態連結的 SDK。
    • 首先,您可以將功能拆開。舉例來說,部分程式碼可由現有 SDK 處理,部分則可轉送至支援執行階段的 SDK。
  3. 支援執行階段的廣告程式庫模組 - 包含支援執行階段的 SDK 業務邏輯。您可以在 Android Studio 中建立 Android 程式庫模組。
  4. 支援執行階段的 ASB 模組 - 定義要將支援執行階段的 SDK 程式碼打包成 ASB 的套件資料。
    • 您必須使用 com.android.privacy-sandbox-sdk 類型手動建立。只要建立新目錄,即可達成此目的。
    • 這個模組不應包含任何程式碼,只要有空白的 build.gradle 檔案,以及啟用執行階段的廣告程式庫模組的依附元件即可。這個檔案的內容在「準備 SDK」中定義。
    • 請記得在 settings.gradle 檔案中加入這個模組,並在現有的廣告程式庫模組中加入。

本指南中的專案結構僅供參考,您可以為 SDK 選擇不同的結構,並套用相同的技術原則。您隨時可以建立其他額外模組,將應用程式和程式庫模組中的程式碼模組化。

準備 SDK

如要準備專案以進行啟用執行階段的 SDK 開發作業,請先定義一些工具和程式庫依附元件:

  • SDK 執行階段回溯相容性程式庫,可支援不具備 Privacy Sandbox 的裝置 (Android 13 以下版本) (androidx.privacysandbox.sdkruntime:)
  • 支援廣告呈現的 UI 程式庫 (androidx.privacysandbox.ui:)
  • SDK 開發人員工具,支援 SDK API 宣告和墊片產生 (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. 修改專案的 build.gradle,加入輔助 Jetpack 程式庫和其他依附元件:

    // 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 只能使用內部儲存空間,因此只有內部儲存空間 API (例如 Context.getFilesDir()Context.getCacheDir()) 才能運作。如需更多範例,請參閱「從內部儲存空間存取」一文。

無法透過 SDK 執行階段存取外部儲存空間。呼叫 API 以存取外部儲存空間時,系統會擲回例外狀況或傳回空值。以下列舉部分示例:

您必須使用 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

如果 SDK 需要存取 Google Play 服務提供的廣告 ID,請使用 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。

您可以使用下列兩種方式,在 Kotlin 中實作非同步 API:

  1. 使用暫停函式
  2. 接受回呼,在作業完成時收到通知,或在作業進行期間收到其他事件的通知。函式的傳回型別必須為 Unit。

例外狀況

SDK API 不支援任何形式的檢查例外狀況。

產生的墊片程式碼會擷取 SDK 擲回的任何執行階段例外狀況,並以 PrivacySandboxException 形式擲回給用戶端,其中包含原因的相關資訊。

UI 程式庫

如果您有代表廣告的介面 (例如橫幅),也需要實作 SandboxedUiAdapter 介面,才能為已載入的廣告開啟工作階段。

這些工作階段會在用戶端和 SDK 之間形成側邊管道,主要有兩個用途:

  • 每當發生 UI 變更時,就會收到通知。
  • 如有任何 UI 呈現方式變更,請通知用戶端。

由於用戶端可以使用以 @PrivacySandboxService 註解的介面與 SDK 通訊,因此任何用於載入廣告的 API 都可以新增至這個介面。

當用戶端要求載入廣告時,請載入廣告並傳回實作 SandboxedUiAdapter 的介面例項。這樣用戶端就能要求開啟該廣告的階段作業。

當用戶端要求開啟工作階段時,啟用執行階段的 SDK 可以使用廣告回應和提供的內容建立廣告檢視畫面。

如要達成這個目標,請建立實作 SandboxedUiAdapter.Session 介面的類別,並在呼叫 SandboxedUiAdapter.openSession() 時,確保呼叫 client.onSessionOpened(),並將 Session 類別的例項做為參數傳遞。

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。

活動支援

如要從 Privacy Sandbox 啟動 SDK 擁有的活動,您需要修改 SDK API 來接收 SdkActivityLauncher 物件,這也是 UI 程式庫提供的物件。

舉例來說,下列 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 中,為產生的 AbstractSandboxedSdkProvider 提供 @PrivacySandboxService 註解介面的實作項目。

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