פיתוח וצריכה של ערכת 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. הגדרת ממשקי ה-API של ה-SDK
  5. ציון נקודת כניסה ל-SDK

הגדרת מבנה הפרויקט

מומלץ לארגן את הפרויקט במודולים הבאים:

  1. מודול אפליקציה – אפליקציית הבדיקה שבה אתם משתמשים כדי לבדוק ולפתח את SDK, שמייצג את מה שיהיו לקוחות האפליקציה האמיתיים שלכם. האפליקציה שלך צריכה תלויות במודול הקיים של ספריית המודעות (SDK מבוסס-זמן ריצה).
  2. מודול קיים של ספריית מודעות (SDK מבוסס-זמן ריצה) – מודול של ספרייה ב-Android שמכיל את הערך הקיים 'שאינו מופעל זמן ריצה' לוגיקת SDK, SDK מקושר.
    • בתור התחלה, אפשר לפצל את היכולות. לדוגמה, חלק מהקודים יכולים להיות, מטופל ב-SDK הקיים, וחלקם יכולים להיות מנותבים להרצת זמן הריצה SDK.
  3. מודול של ספריית מודעות שפועל בסביבת זמן ריצה – מכיל את הלוגיקה העסקית של ערכת ה-SDK שפועלת בסביבת זמן ריצה. אפשר ליצור את היצירה הזו ב-Android Studio כספריית Android של מודל טרנספורמר.
  4. מודול ASB שמופעל עם זמן ריצה – מגדיר את נתוני החבילה כדי לקבץ את נתוני החבילה. ל-ASB, בקוד SDK שמותאם לזמן ריצה.
    • צריך ליצור אותו באופן ידני com.android.privacy-sandbox-sdk. כדי לעשות זאת, צריך ליצור ספרייה חדשה.
    • המודול הזה לא יכול להכיל קוד אלא רק build.gradle ריק תלויים במודול של ספריית המודעות שהוגדר בזמן ריצה. התוכן של הקובץ הזה מוגדר כאן מכינים את ה-SDK.
    • חשוב לכלול את המודול הזה בקובץ settings.gradle ובמודול הקיים של ספריית המודעות.

מבנה הפרויקט במדריך הזה הוא הצעה, אפשר לבחור של ה-SDK וליישם את אותם עקרונות טכניים. תמיד אפשר ליצור מודולים נוספים כדי לשנות את הקוד באפליקציה ובמודולים של הספרייה.

הכנת ה-SDK

כדי להכין את הפרויקט לפיתוח SDK שתואם לזמן ריצה, צריך: מגדירים תחילה כמה יחסי תלות של כלים וספריות:

  • ספריות תאימות לאחור של זמן ריצה ל-SDK, שמספקות תמיכה מכשירים שאין בהם ארגז חול לפרטיות (Android 13 ומטה) (androidx.privacysandbox.sdkruntime:)
  • ספריות ממשק משתמש לתמיכה בהצגת מודעות (androidx.privacysandbox.ui:)
  • כלים למפתחים של ערכות SDK לתמיכה בהצהרה על SDK API וביצירת ספריית shim (androidx.privacysandbox.tools:)
  1. כדי לאפשר את היכולת ליצור ערכות SDK תואמות זמן ריצה, מוסיפים את הדגל הזה לקובץ gradle.properties של הפרויקט.

    # 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. מעדכנים את הקובץ build.gradle במודול של ספריית המודעות שתואמת לזמן ריצה (RE SDK) כך שיכלול את יחסי התלות האלה.

    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. מחליפים את הקובץ build.gradle במודול ASB שתומך בזמן ריצה בערך הבא:

    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. מעדכנים את הקובץ build.gradle במודול של ספריית המודעות הקיימת (RA SDK) כך שיכלול את יחסי התלות הבאים:

    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 או למזהה של קבוצת האפליקציות, כדאי לקרוא את הקטעים הבאים:

שימוש בממשקי API לאחסון ב-SDK

ערכות SDK בזמן הריצה של ה-SDK כבר לא יכולות לגשת, לקרוא או לכתוב באחסון הפנימי של האפליקציה ולהפך.

זמן הריצה של ה-SDK מוקצה לאזור אחסון פנימי משלו, בנפרד מהאפליקציה.

ערכות SDK יכולות לגשת לאחסון הפנימי הנפרד הזה באמצעות ממשקי ה-API לאחסון קבצים באובייקט Context שמוחזר על ידי SandboxedSdkProvider#getContext().

ערכות SDK יכולות להשתמש רק באחסון פנימי, לכן רק ממשקי API של אחסון פנימי כמו Context.getFilesDir() או Context.getCacheDir() פועלות. הצגת דוגמאות נוספות ב גישה מאחסון פנימי.

אין תמיכה בגישה לאחסון חיצוני מזמן הריצה של ה-SDK. קריאה לממשקי API כדי לגשת לאחסון חיצוני תגרום להשלכת חריגה או להחזרת ערך null. הרשימה הבאה כוללת כמה דוגמאות:

עליך להשתמש בContext שהוחזר על ידי SandboxedSdkProvider.getContext() לאחסון. לא בטוח שהשימוש ב-File Storage API בכל מופע אחר של אובייקט Context, כמו בהקשר של האפליקציה, יפעל כצפוי בכל מצב.

קטע הקוד הבא מדגים איך להשתמש באחסון בזמן הריצה של ה-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 משתמשת.

כל ממשקי ה-API של האחסון הפנימי באובייקט Context מחזירים נתיב אחסון לכל ערכת SDK.

אם ל-SDK שלכם דרושה גישה למזהה הפרסום שסופק על ידי Google Play Services, צריך להשתמש ב-AdIdManager#getAdId() כדי לאחזר את הערך באופן אסינכררוני.

אם ל-SDK נדרשת גישה למזהה קבוצת האפליקציות שסופק על ידי Google Play Services, צריך להשתמש ב- AppSetIdManager#getAppSetId() כדי לאחזר את הערך באופן אסינכרוני.

הצהרה על ממשקי API של SDK

כדי שאפשר יהיה לגשת ל-SDK שתומך בסביבת זמן ריצה מחוץ לסביבת זמן הריצה, צריך להגדיר ממשקי API שלקוחות (RA SDK או אפליקציית הלקוח) יכולים להשתמש בהם.

משתמשים בהערות כדי להצהיר על הממשקים האלה.

הערות

צריך להצהיר על ממשקי SDK ב-Kotlin כממשקים וסיווגי נתונים באמצעות ההערות הבאות:

הערות
@PrivacySandboxService
  • הגדרת נקודת הכניסה ל-RE SDK
  • חייב להיות ייחודי
@PrivacySandboxInterface
  • הפעלת מודולריזציה נוספת וחשיפת ממשקים
  • יכולים להיות מספר מופעים
@PrivacySandboxValue
  • מאפשר שליחת נתונים בין תהליכים
  • בדומה למבנים שלא ניתנים לשינוי, שיכולים להחזיר ערכים מרובים מסוגים שונים.
@PrivacySandboxCallback
  • הצהרת ממשקי API עם פונקציית קריאה חוזרת
  • ערוץ עורפי להפעלת קוד לקוח

צריך להגדיר את הממשקים והמחלקות האלה בכל מקום של ספריית מודעות שהופעלה בזמן הריצה.

ניתן לראות את השימוש בהערות אלה בקטעים הבאים.

@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
interface SdkSandboxedUiAdapter : SandboxedUiAdapter
@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
interface InAppMediateeSdkInterface {
    suspend fun show()
}

סוגים נתמכים

ממשקי API ל-SDK המותאמים לזמן ריצה תומכים בסוגים הבאים:

  • כל הסוגים הראשוניים בשפת Java (כגון int, ארוך, char, boolean וכו')
  • מחרוזת
  • ממשקי Kotlin עם הערות @PrivacySandboxInterface או @PrivacySandboxCallback
  • מחלקות נתונים ב-Kotlin עם הערות @PrivacySandboxValue
  • java.lang.List – כל הפריטים ברשימה חייבים להיות מאחד מסוגי הנתונים הנתמכים

יש כמה אזהרות נוספות:

  • כיתות נתונים עם הערה @PrivacySandboxValue לא יכולות להכיל שדות מסוג @PrivacySandboxCallback
  • סוגי החזרה לא יכולים להכיל סוגים עם הערות עם @PrivacySandboxCallback
  • הרשימה לא יכולה להכיל אלמנטים מסוגים עם הערות @PrivacySandboxInterface או @PrivacySandboxCallback

ממשקי API אסינכררוניים

מכיוון שממשקי ה-API של ה-SDK תמיד מפעילים קריאה לתהליך נפרד, אנחנו צריכים לוודא שהשיחות האלה לא חוסמות את שרשור השיחות של הלקוח.

כדי להשיג זאת, כל השיטות בממשקים שנוספו עם הערות @PrivacySandboxService, @PrivacySandboxInterface וגם @PrivacySandboxCallback צריך להצהיר במפורש כממשקי API אסינכרוניים.

אפשר להטמיע ממשקי API אסינכרוניים ב-Kotlin בשתי דרכים:

  1. להשתמש בפונקציות השעיה.
  2. לקבל שיחות חוזרות (callback) שמקבלות הודעה כשהפעולה תסתיים, או אירועים אחרים במהלך התקדמות הפעולה. סוג ההחזרה של הפונקציה חייבת להיות יחידה.

חריגים

ממשקי ה-API של ה-SDK לא תומכים באף סוג של חריגות מאומתות.

קוד ה-shim שנוצר מתעד חריגים בסביבת זמן הריצה של ה-SDK, זורקת אותן בתור PrivacySandboxException ללקוח עם מידע על הסיבה שעטופה.

ספרייה בממשק המשתמש

אם יש לכם ממשקים שמייצגים מודעות, כמו באנר, עליכם גם להטמיע את הממשק SandboxedUiAdapter כדי לאפשר פתיחת סשנים של המודעה שנטענה.

הסשנים האלה יוצרים ערוץ צדדי בין הלקוח ל-SDK, למלא שתי מטרות עיקריות:

  • קבלת התראות בכל פעם שמתרחש שינוי בממשק המשתמש.
  • חשוב להודיע ללקוח על שינויים במצגת בממשק המשתמש.

מכיוון שהלקוח יכול להשתמש בממשק עם הערות @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)
       }
   }
}

הכיתה הזו מקבלת גם התראות בכל פעם שמתבצע שינוי בממשק המשתמש. אפשר אפשר להשתמש בסיווג הזה כדי לשנות את גודל המודעה, או לדעת מתי ההגדרות האישיות השתנו, למשל.

מידע נוסף על ממשקי API להצגת ממשק משתמש בסביבת זמן הריצה

תמיכה בפעילות

כדי להתחיל פעילויות בבעלות SDK מארגז החול לפרטיות, צריך לשנות את ה-SDK API כדי לקבל אובייקט SdkActivityLauncher, שגם הוא מסופק על ידי ספריית ממשק המשתמש.

לדוגמה, ממשק ה-API הבא של SDK אמור להפעיל פעילויות, ולכן הוא מצפה לפרמטר SdkActivityLauncher:

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

נקודת הכניסה ל-SDK

הכיתה התיאורטית SandboxedSdkProvider מכילה את ה-API שבו זמן הריצה ל-SDK משתמש כדי ליצור אינטראקציה עם ערכות ה-SDK שנטענו בו.

צריך להטמיע את הכיתה הזוחית ב-SDK שתומך בסביבת זמן ריצה כדי ליצור נקודת כניסה שסביבת זמן הריצה של ה-SDK תוכל לתקשר איתה.

לצורך תמיכה בתאימות לאחור, השקנו את הסוגים הבאים:

מידע נוסף על תאימות לאחור של זמן הריצה של ה-SDK

הכלים ליצירת ספריית shim מוסיפים שכבה נוספת של הפשטה: הם יוצרים מחלקה מופשטת בשם AbstractSandboxedSdkProvider באמצעות הממשק שהוספתם לו הערות ב-@PrivacySandboxService.

הכיתה הזו מתרחבת ל-SandboxedSdkProviderCompat ונמצאת באותה חבילה כמו הממשק עם ההערות.

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

הכיתה שנוצרה חושפת שיטה אבסטרקטית אחת של מפעל, שמקבלת Context ומצפה להחזרת הממשק המתויג של נקודת הכניסה.

השם של השיטה הזו נגזר משם ממשק @PrivacySandboxService, עם התחילית create. לדוגמה, אם השם של הממשק הוא MySdk, הכלים יוצרים את createMySdk.

כדי לחבר באופן מלא את נקודת הכניסה, עליך לספק הטמעה של @PrivacySandboxServiceהממשק עם הערות ב-SDK שתואם לזמן ריצה ל-AbstractSandboxedSdkProvider שנוצר.

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

שינויים במודול ASB

צריך להצהיר על שם המחלקה המלא של ההטמעה של SandboxedSdkProviderCompat בשדה compatSdkProviderClassName בקובץ build.gradle של מודול ה-ASB.

זו המחלקה שהטמעתם בשלב הקודם, וצריך לשנות את build.gradle במודול ASB באופן הבא:

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 שתואם לזמן הריצה