Tworzenie i wykorzystywanie pakietu SDK z włączonym środowiskiem wykonawczym

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

Tworzenie pakietu SDK wywoływanego w czasie działania aplikacji

Aby utworzyć pakiet SDK z włączonym środowiskiem wykonawczym, musisz wykonać te czynności:

  1. Konfigurowanie struktury projektu
  2. Przygotuj projekt i zależności modułów
  3. Dodawanie logiki biznesowej pakietu SDK
  4. Określ interfejsy API pakietu SDK
  5. Określanie punktu wejścia pakietu SDK

Konfigurowanie struktury projektu

Zalecamy podzielenie projektu na te moduły:

  1. Moduł aplikacji – aplikacja testowa, której używasz do testowania i rozwijania pakietu SDK. Odzwierciedla ona to, co mają Twoi klienci korzystający z prawdziwej aplikacji. Aplikacja powinna być zależna od istniejącego modułu biblioteki reklam (pakiet SDK z włączonym środowiskiem wykonawczym).
  2. Istniejący moduł biblioteki reklam (pakiet SDK uwzględniający środowisko wykonawcze) – moduł biblioteki Androida zawierający istniejącą logikę pakietu SDK „nieobsługującego środowiska wykonawczego”, czyli pakiet SDK połączony statycznie.
    • Na początek można podzielić możliwości. Na przykład część kodu może być obsługiwana przez dotychczasowy pakiet SDK, a część może być kierowana do pakietu SDK z włączonym środowiskiem wykonawczym.
  3. Moduł biblioteki reklamowej używanej w czasie działania aplikacji – zawiera logikę biznesową pakietu SDK używanego w czasie działania aplikacji. Można go utworzyć w Android Studio jako moduł biblioteki Androida.
  4. Moduł ASB używany w czasie działania aplikacji – określa dane pakietu, które mają być dołączone do kodu pakietu SDK używanego w czasie działania aplikacji w formacie ASB.
    • Musisz utworzyć go ręcznie, używając typu com.android.privacy-sandbox-sdk. Możesz to zrobić, tworząc nowy katalog.
    • Ten moduł nie powinien zawierać żadnego kodu, tylko pusty plik build.gradle z zależnościami od modułu biblioteki reklamowej z włączonym środowiskiem wykonawczym. Zawartość tego pliku jest określona w sekcji Przygotowywanie pakietu SDK.
    • Pamiętaj, aby uwzględnić ten moduł w pliku settings.gradle i w istniejącym module biblioteki reklam.

Struktura projektu w tym przewodniku jest tylko sugestią. Możesz wybrać inną strukturę pakietu SDK i zastosować te same zasady techniczne. Zawsze możesz utworzyć dodatkowe moduły, aby podzielić kod w aplikacji i modułach biblioteki.

Przygotowywanie pakietu SDK

Aby przygotować projekt do tworzenia pakietów SDK z włączonym środowiskiem wykonawczym, musisz najpierw zdefiniować niektóre narzędzia i zależności bibliotek:

  • Biblioteki zgodności wstecznej środowiska wykonawczego SDK, które zapewniają obsługę urządzeń bez Piaskownicy prywatności (Android 13 i starsze wersje)(androidx.privacysandbox.sdkruntime:)
  • Biblioteki interfejsu do obsługi wyświetlania reklam (androidx.privacysandbox.ui:)
  • Narzędzia dla deweloperów pakietu SDK do obsługi deklaracji interfejsu API pakietu SDK i generowania elementów shim (androidx.privacysandbox.tools:)
  1. Aby włączyć możliwość tworzenia pakietów SDK z włączonym środowiskiem wykonawczym, dodaj ten flagę do pliku gradle.properties projektu.

    # This enables the Privacy Sandbox for your project on Android Studio.
    android.experimental.privacysandboxsdk.enable=true
    android.experimental.privacysandboxsdk.requireServices=false
    
  2. Zmodyfikuj plik build.gradle projektu, aby uwzględnić biblioteki pomocnicze Jetpack i inne zależności:

    // 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. Zaktualizuj plik build.gradle w module biblioteki reklam wywoływanych w czasie działania aplikacji (RE SDK), aby uwzględnić te zależności.

    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. Zastąp plik build.gradle w module ASB z włączonym środowiskiem wykonawczym tym kodem:

    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. Zaktualizuj plik build.gradle w istniejącym module biblioteki reklam (pakiet SDK RA), aby uwzględnić te zależności:

    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"
    }
    

Dodawanie logiki biznesowej pakietu SDK

Zaimplementuj logikę biznesową pakietu SDK w module biblioteki reklamowej z włączonym środowiskiem wykonawczym w taki sam sposób, jak zwykle.

Jeśli masz już pakiet SDK, który chcesz przenieść, możesz na tym etapie przenieść dowolną część logiki biznesowej, interfejsu i funkcji systemowych, ale pamiętaj, że w przyszłości musisz przeprowadzić pełną migrację.

Jeśli potrzebujesz dostępu do pamięci, identyfikatora reklamowego Google Play lub identyfikatora zestawu aplikacji, zapoznaj się z tymi sekcjami:

Korzystanie z interfejsów API pamięci w pakiecie SDK

Pakiety SDK w środowisku wykonawczym SDK nie mogą już uzyskiwać dostępu do pamięci wewnętrznej aplikacji, odczytywać ani zapisywać w niej danych i na odwrót.

Środowisko wykonawcze SDK ma własny obszar pamięci wewnętrznej, oddzielony od aplikacji.

Pakiety SDK mają dostęp do tego oddzielnego wewnętrznego miejsca na dane za pomocą interfejsów API do przechowywania plików w obiekcie Context zwracanym przez SandboxedSdkProvider#getContext().

Pakiety SDK mogą korzystać tylko z pamięci wewnętrznej, więc działają tylko interfejsy API pamięci wewnętrznej, np. Context.getFilesDir() lub Context.getCacheDir(). Więcej przykładów znajdziesz w sekcji Dostęp z pamięci wewnętrznej.

Dostęp do pamięci zewnętrznej z poziomu środowiska wykonawczego pakietu SDK nie jest obsługiwany. Wywoływanie interfejsów API w celu uzyskania dostępu do pamięci zewnętrznej spowoduje zgłoszenie wyjątku lub zwrócenie wartości null. Oto kilka przykładów:

Musisz użyć wartości Context zwróconej przez SandboxedSdkProvider.getContext() na potrzeby przechowywania. Używanie interfejsu API pamięci plików w przypadku innych instancji obiektów Context, np. kontekstu aplikacji, nie gwarantuje prawidłowego działania we wszystkich sytuacjach.

Poniższy fragment kodu pokazuje, jak używać pamięci w środowisku wykonawczym 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"
    }
}

W ramach oddzielnego pamięci wewnętrznej każdego środowiska wykonawczego SDK każdy pakiet SDK ma własny katalog pamięci. Miejsce na dane poszczególnych pakietów SDK to logiczne oddzielenie wewnętrznego miejsca na dane środowiska wykonawczego SDK, które pomaga określić, ile miejsca na dane wykorzystuje każdy pakiet SDK.

Wszystkie interfejsy API pamięci wewnętrznej w obiekcie Context zwracają ścieżkę do pamięci dla każdego pakietu SDK.

Dostęp do identyfikatora wyświetlania reklam udostępnianego przez Usługi Google Play

Jeśli Twój pakiet SDK potrzebuje dostępu do identyfikatora wyświetlania reklam udostępnianego przez Usługi Google Play, użyj metody AdIdManager#getAdId(), aby pobrać wartość asynchronicznie.

Dostęp do identyfikatora zestawu aplikacji udostępnianego przez Usługi Google Play

Jeśli Twój pakiet SDK potrzebuje dostępu do identyfikatora zestawu aplikacji udostępnianego przez Usługi Google Play, użyj AppSetIdManager#getAppSetId(), aby pobrać wartość asynchronicznie.

Deklarowanie interfejsów API pakietu SDK

Aby pakiet SDK z włączonym środowiskiem wykonawczym był dostępny poza tym środowiskiem, musisz zdefiniować interfejsy API, z których mogą korzystać klienci (pakiet SDK RA lub aplikacja klienta).

Do deklarowania tych interfejsów używaj adnotacji.

Adnotacje

Interfejsy API pakietu SDK muszą być zadeklarowane w języku Kotlin jako interfejsy i klasy danych z użyciem tych adnotacji:

Adnotacje
@PrivacySandboxService
  • Określa punkt wejścia do pakietu SDK RE.
  • Musi być niepowtarzalny
@PrivacySandboxInterface
  • Umożliwia dalszą modularizację i udostępnianie interfejsów.
  • Może mieć wiele instancji
@PrivacySandboxValue
  • Umożliwia wysyłanie danych między procesami.
  • Podobnie jak struktury niezmienne, które mogą zwracać wiele wartości różnych typów.
@PrivacySandboxCallback
  • Deklaruje interfejsy API z wywołaniem zwrotnym.
  • Udostępnia kanał zwrotny do wywoływania kodu klienta.

Te interfejsy i klasy musisz zdefiniować w dowolnym miejscu w module biblioteki reklamowej z włączonym środowiskiem wykonawczym.

Sposób użycia tych adnotacji znajdziesz w sekcjach poniżej.

@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()
}

Typy obsługiwane

Interfejsy API pakietu SDK używanego w czasie działania aplikacji obsługują te typy:

  • Wszystkie typy proste w języku programowania Java (takie jak int, long, char, boolean itp.)
  • Ciąg znaków
  • Interfejsy Kotlin z adnotacjami @PrivacySandboxInterface lub @PrivacySandboxCallback
  • Klasy danych Kotlin z adnotacją @PrivacySandboxValue
  • java.lang.List – wszystkie elementy na liście muszą być jednego z obsługiwanych typów danych.

Istnieją dodatkowe zastrzeżenia:

  • Klasy danych oznaczone symbolem @PrivacySandboxValue nie mogą zawierać pól typu @PrivacySandboxCallback
  • Typy zwracane nie mogą zawierać typów oznaczonych adnotacją @PrivacySandboxCallback.
  • Lista nie może zawierać elementów typów oznaczonych adnotacją @PrivacySandboxInterface lub @PrivacySandboxCallback

Asynchroniczne interfejsy API

Interfejsy API pakietu SDK zawsze wywołują oddzielny proces, dlatego musimy zadbać o to, aby te wywołania nie blokowały wątku wywołującego klienta.

Aby to osiągnąć, wszystkie metody w interfejsach oznaczonych adnotacjami @PrivacySandboxService, @PrivacySandboxInterface@PrivacySandboxCallback muszą być wyraźnie zadeklarowane jako asynchroniczne interfejsy API.

Asynchroniczne interfejsy API można zaimplementować w Kotlinie na 2 sposoby:

  1. Użyj funkcji zawieszania.
  2. Akceptuj wywołania zwrotne, które są powiadamiane o zakończeniu operacji lub o innych zdarzeniach podczas jej wykonywania. Typ zwracany funkcji musi być jednostką.

Wyjątki

Interfejsy API pakietu SDK nie obsługują żadnych wyjątków sprawdzanych.

Wygenerowany kod pośredniczący przechwytuje wszelkie wyjątki środowiska wykonawczego zgłaszane przez pakiet SDK i przekazuje je jako PrivacySandboxException do klienta wraz z informacjami o przyczynie.

Biblioteka interfejsu

Jeśli masz interfejsy reprezentujące reklamy, np. baner, musisz też wdrożyć interfejs SandboxedUiAdapter, aby umożliwić otwieranie sesji w przypadku załadowanej reklamy.

Te sesje tworzą kanał boczny między klientem a pakietem SDK i służą do 2 głównych celów:

  • Otrzymuj powiadomienia o każdej zmianie interfejsu.
  • powiadamiać klienta o wszelkich zmianach w sposobie prezentacji interfejsu;

Klient może używać interfejsu oznaczonego adnotacją @PrivacySandboxService do komunikacji z pakietem SDK, więc do tego interfejsu można dodawać dowolne interfejsy API do wczytywania reklam.

Gdy klient poprosi o wczytanie reklamy, wczytaj ją i zwróć instancję interfejsu implementującego SandboxedUiAdapter. Dzięki temu klient może poprosić o otwarcie sesji dla tej reklamy.

Gdy klient poprosi o otwarcie sesji, pakiet SDK z włączonym środowiskiem wykonawczym może utworzyć widok reklamy na podstawie odpowiedzi na żądanie reklamy i dostarczonego kontekstu.

Aby to zrobić, utwórz klasę, która implementuje interfejs SandboxedUiAdapter.Session, i gdy wywoływana jest funkcja SandboxedUiAdapter.openSession(), wywołaj funkcję client.onSessionOpened(), przekazując jako parametr instancję klasy 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)
       }
   }
}

Ta klasa otrzymuje też powiadomienia o każdej zmianie interfejsu. Możesz użyć tej klasy, aby zmienić rozmiar reklamy lub dowiedzieć się, kiedy zmieniła się konfiguracja.

Więcej informacji o interfejsach API prezentacji interfejsu w środowisku wykonawczym

Pomoc dotycząca aktywności

Aby uruchamiać działania należące do pakietu SDK z Piaskownicy prywatności, musisz zmodyfikować interfejs API pakietu SDK, aby odbierał obiekt SdkActivityLauncher, który jest też udostępniany przez bibliotekę interfejsu.

Na przykład ten interfejs API pakietu SDK powinien uruchamiać działania, więc oczekuje parametru SdkActivityLauncher:

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

Punkt wejścia pakietu SDK

Klasa abstrakcyjna SandboxedSdkProvider zawiera interfejs API, którego środowisko wykonawcze pakietu SDK używa do interakcji z załadowanymi do niego pakietami SDK.

Pakiet SDK używany w czasie działania aplikacji musi implementować tę klasę abstrakcyjną, aby generować punkt wejścia dla środowiska wykonawczego pakietu SDK, które będzie mogło się z nim komunikować.

Aby zapewnić zgodność wsteczną, wprowadziliśmy te klasy:

Więcej informacji o kompatybilności wstecznej środowiska wykonawczego SDK

Narzędzia do generowania warstwy pośredniej dodają kolejną warstwę abstrakcji: generują klasę abstrakcyjną o nazwie AbstractSandboxedSdkProvider na podstawie interfejsu, który został oznaczony adnotacją @PrivacySandboxService.

Ta klasa rozszerza klasę SandboxedSdkProviderCompat i znajduje się w tym samym pakiecie co interfejs z adnotacjami.

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

Wygenerowana klasa udostępnia jedną abstrakcyjną metodę fabryczną, która przyjmuje Context i oczekuje zwrócenia interfejsu z adnotacją punktu wejścia.

Ta metoda jest nazwana na podstawie interfejsu @PrivacySandboxService, a jej nazwa zaczyna się od create. Jeśli na przykład interfejs ma nazwę MySdk, narzędzia wygenerują createMySdk.

Aby w pełni połączyć punkt wejścia, musisz w pakiecie SDK z włączonym środowiskiem wykonawczym podać implementację @PrivacySandboxService interfejsu z adnotacjami do wygenerowanego AbstractSandboxedSdkProvider.

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

Zmiany w module ASB

W polu compatSdkProviderClassName pliku build.gradle modułu ASB musisz zadeklarować pełną i jednoznaczną nazwę klasy implementacji interfejsu SandboxedSdkProviderCompat.

Jest to klasa zaimplementowana w poprzednim kroku. Plik build.gradle w module ASB należy zmodyfikować w ten sposób:

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"
}

Krok 2. Skonfiguruj środowisko programistyczne Krok 4. Użyj pakietu SDK z włączonym środowiskiem wykonawczym