המדריך למפתחים של זמן ריצה ל-SDK

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


שליחת משוב

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

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

מגבלות ידועות

נתוני הגרסה כוללים את רשימת היכולות שמתבצעות בזמן הריצה של ה-SDK.

המגבלות הבאות צפויות להיות מתוקנות בגרסה הגדולה הבאה של הפלטפורמה של Android.

  • מודעות שמוצגות בתצוגה שניתנת לגלילה. לדוגמה, RecyclerView לא פועל כראוי.
    • ייתכן שתיתקלו ב-jank בעת שינוי הגודל.
    • אירועי הגלילה באמצעות מגע של המשתמש לא מועברים לזמן הריצה בצורה תקינה.
  • Storage API

הבעיה הבאה תיפתר בשנת 2023:

  • ממשקי ה-API של getAdId ו-getAppSetId עדיין לא פועלים בצורה תקינה כי התמיכה בהם עדיין לא הופעלה.

לפני שמתחילים

לפני שמתחילים, חשוב לבצע את השלבים הבאים:

  1. איך מגדירים את סביבת הפיתוח לארגז החול לפרטיות ב-Android. הכלים לתמיכה בזמן הריצה של ה-SDK נמצאים בפיתוח פעיל, לכן המדריך הזה ידרוש מכם להשתמש בגרסה האחרונה של Canary של Android Studio. תוכלו להריץ את הגרסה הזו של Android Studio במקביל לגרסאות אחרות שבהן אתם משתמשים, אז נשמח לדעת אם הדרישה הזו לא עובדת.

  2. מתקינים תמונת מערכת במכשיר נתמך או מגדירים אמולטור שכולל תמיכה בארגז החול לפרטיות ב-Android.

הגדרת הפרויקט ב-Android Studio

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

  1. מוסיפים מודול אפליקציה לפרויקט. המודול הזה משמש כלקוח שמניע את ה-SDK.
  2. במודול האפליקציה, מפעילים את זמן הריצה ל-SDK, מצהירים על ההרשאות הנדרשות ומגדירים שירותי פרסום שספציפיים ל-API.
  3. הוספת מודול ספרייה אחד לפרויקט. המודול הזה מכיל את קוד ה-SDK שלך.
  4. מצהירים במודול ה-SDK על ההרשאות הנדרשות. במודול הזה לא צריך להגדיר שירותי מודעות ספציפיים ל-API.
  5. מסירים את dependencies בקובץ build.gradle של המודול של הספרייה, שה-SDK לא משתמש בו. ברוב המקרים אפשר להסיר את כל יחסי התלות. כדי לעשות זאת, יוצרים ספרייה חדשה ששמה תואם ל-SDK שלכם.
  6. יוצרים באופן ידני מודול חדש עם הסוג com.android.privacy-sandbox-sdk. החבילה הזו כלולה בחבילה עם קוד ה-SDK כדי ליצור חבילת APK שאפשר לפרוס במכשיר שלכם. כדי לעשות זאת, אפשר ליצור ספרייה חדשה ששמה תואם ל-SDK שלכם. יש להוסיף קובץ build.gradle ריק. התוכן של הקובץ הזה יאוכלס בהמשך המדריך.

  7. מוסיפים את קטע הקוד הבא לקובץ gradle.properties:

    android.experimental.privacysandboxsdk.enable=true
    

  8. מורידים את תמונת האמולטור Tiramisu (רמת תוסף 4) ויוצרים אמולטור עם התמונה הזו שכוללת את חנות Play.

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

מתקינים את ה-SDK במכשיר בדיקה, בדומה לאופן שבו מתקינים אפליקציה, באמצעות Android Studio או גשר ניפוי הבאגים של Android (ADB). כדי לעזור לכם להתחיל, יצרנו אפליקציות לדוגמה בשפות התכנות Kotlin ו-Java, שזמינות במאגר הזה של GitHub. בקובצי README ובקובצי המניפסט יש הערות שמתארות מה צריך לשנות כדי להריץ את הדוגמה בגרסאות יציבות של Android Studio.

הכנת ה-SDK

  1. יצירת ספרייה ברמת המודול באופן ידני. היא משמשת כ-wrapper סביב קוד ההטמעה לצורך בניית ה-APK של ה-SDK. בספרייה החדשה, צריך להוסיף קובץ build.gradle ולאכלס אותו בקטע הקוד הבא. עליכם להשתמש בשם ייחודי ל-SDK עם תמיכה בזמן ריצה (RE-SDK) ולספק גרסה. כוללים את מודול הספרייה בקטע dependencies.

    plugins {
        id 'com.android.privacy-sandbox-sdk'
    }
    
    android {
        compileSdk 33
        compileSdkExtension 4
        minSdk 33
        targetSdk 33
        namespace = "com.example.example-sdk"
    
        bundle {
            packageName = "com.example.privacysandbox.provider"
            sdkProviderClassName = "com.example.sdk_implementation.SdkProviderImpl"
            setVersion(1, 0, 0)
        }
    }
    
    dependencies {
        include project(':<your-library-here>')
    }
    
  2. יוצרים מחלקה בספריית ההטמעה, שתשמש כנקודת כניסה ל-SDK. שם המחלקה צריך להיות ממופה לערך של sdkProviderClassName ולהרחיב את SandboxedSdkProvider.

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

כדי לגרום לאפליקציית ה-SDK לעבור הידור (compile), צריך לשנות את השיטות לטיפול במחזור החיים של ה-SDK:

onLoadSdk()

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

בעזרת AIDL כדוגמה, יש להגדיר קובץ AIDL כך שיציג את IBinder שהאפליקציה תשתף:

// ISdkInterface.aidl
interface ISdkInterface {
    // the public functions to share with the App.
    int doSomething();
}
getView()

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

קטע הקוד הבא מדגים איך לבטל את השיטות האלה:

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    override fun onLoadSdk(params: Bundle?): SandboxedSdk {
        // Returns a SandboxedSdk, passed back to the client. The IBinder used
        // to create the SandboxedSdk object is used by the app to call into the
        // SDK.
        return SandboxedSdk(SdkInterfaceProxy())
    }

    override fun getView(windowContext: Context, bundle: Bundle, width: Int,
            height: Int): View {
        val webView = WebView(windowContext)
        val layoutParams = LinearLayout.LayoutParams(width, height)
        webView.setLayoutParams(layoutParams)
        webView.loadUrl("https://developer.android.com/privacy-sandbox")
        return webView
    }

    private class SdkInterfaceProxy : ISdkInterface.Stub() {
        fun doSomething() {
            // Implementation of the API.
        }
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    @Override
    public SandboxedSdk onLoadSdk(Bundle params) {
        // Returns a SandboxedSdk, passed back to the client. The IBinder used
        // to create the SandboxedSdk object is used by the app to call into the
        // SDK.
        return new SandboxedSdk(new SdkInterfaceProxy());
    }

    @Override
    public View getView(Context windowContext, Bundle bundle, int width,
            int height) {
        WebView webView = new WebView(windowContext);
        LinearLayout.LayoutParams layoutParams =
                new LinearLayout.LayoutParams(width, height);
        webView.setLayoutParams(layoutParams);
        webView.loadUrl("https://developer.android.com/privacy-sandbox");
        return webView;
    }

    private static class SdkInterfaceProxy extends ISdkInterface.Stub {
        @Override
        public void doSomething() {
            // Implementation of the API.
        }
    }
}

בדיקת נגני וידאו בזמן הריצה ל-SDK

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

התהליך לבדיקת נגני וידאו דומה לתהליך הבדיקה של מודעות באנר. משנים את השיטה getView() בנקודת הכניסה של ה-SDK כך שתכלול נגן וידאו באובייקט View שהוחזר. בודקים את כל התהליכים של נגן הווידאו שאתם מצפים שנתמכים על ידי ארגז החול לפרטיות. שימו לב: אין תקשורת בין ה-SDK לבין אפליקציית הלקוח לגבי מחזור החיים של הסרטון, ולכן עדיין לא נדרש משוב כדי להשתמש בפונקציונליות הזו.

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

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

Kotlin

    class SdkProviderImpl : SandboxedSdkProvider() {

        override fun getView(windowContext: Context, bundle: Bundle, width: Int,
                height: Int): View {
            val videoView = VideoView(windowContext)
            val layoutParams = LinearLayout.LayoutParams(width, height)
            videoView.setLayoutParams(layoutParams)
            videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"))
            videoView.setOnPreparedListener { mp -> mp.start() }
            return videoView
        }
    }

Java

    public class SdkProviderImpl extends SandboxedSdkProvider {

        @Override
        public View getView(Context windowContext, Bundle bundle, int width,
                int height) {
            VideoView videoView = new VideoView(windowContext);
            LinearLayout.LayoutParams layoutParams =
                    new LinearLayout.LayoutParams(width, height);
            videoView.setLayoutParams(layoutParams);
            videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"));
            videoView.setOnPreparedListener(mp -> {
                mp.start();
            });
            return videoView;
        }
    }

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

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

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

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

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

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

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

Kotlin

    private static class SdkInterfaceStorage extends ISdkInterface.Stub {
    override fun doSomething() {
        val filename = "myfile"
        val fileContents = "content"
        try {
            getContext().openFileOutput(filename, Context.MODE_PRIVATE).use {
                it.write(fileContents.toByteArray())
            } catch (e: Exception) {
                throw RuntimeException(e)
            }
        }
    }
}

    

Java

    private static class SdkInterfaceStorage extends ISdkInterface.Stub {
    @Override
    public void doSomething() {
        final filename = "myFile";
        final String fileContents = "content";
        try (FileOutputStream fos = getContext().openFileOutput(filename, Context.MODE_PRIVATE)) {
            fos.write(fileContents.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

    

אחסון לכל SDK

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

ב-Android 13, רק API אחד מחזיר נתיב לאחסון לכל ערכת SDK: Context#getDataDir().

ב-Android 14, כל ממשקי ה-API של האחסון הפנימי באובייקט Context מחזירים נתיב אחסון לכל SDK. יכול להיות שתצטרכו להפעיל את התכונה הזו על ידי הרצת פקודת ה-adb הבאה:

adb shell device_config put adservices sdksandbox_customized_sdk_context_enabled true

גישה למזהה הפרסום שסופק על ידי Google Play Services

אם ל-SDK נדרשת גישה למזהה הפרסום שסופק על ידי Google Play Services:

  • יש להצהיר על ההרשאה android.permission.ACCESS_ADSERVICES_AD_ID במניפסט של ה-SDK.
  • משתמשים בפונקציה AdIdManager#getAdId() כדי לאחזר את הערך באופן אסינכרוני.

גישה למזהה קבוצת האפליקציות שסופק על ידי Google Play Services

אם ל-SDK נדרשת גישה למזהה קבוצת האפליקציות שסופק על ידי Google Play Services:

  • משתמשים בפונקציה AppSetIdManager#getAppSetId() כדי לאחזר את הערך באופן אסינכרוני.

עדכון אפליקציות לקוח

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

  1. מוסיפים את ההרשאות INTERNET ו-ACCESS_NETWORK_STATE למניפסט של האפליקציה:

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
  2. בפעילות באפליקציה שכוללת מודעה, צריך להצהיר על הפניה ל-SdkSandboxManager, לערך בוליאני שמציין אם ה-SDK נטען ואובייקט SurfaceView לעיבוד מרחוק:

    Kotlin

        private lateinit var mSdkSandboxManager: SdkSandboxManager
        private lateinit var mClientView: SurfaceView
        private var mSdkLoaded = false
    
        companion object {
            private const val SDK_NAME = "com.example.privacysandbox.provider"
        }
    

    Java

        private static final String SDK_NAME = "com.example.privacysandbox.provider";
    
        private SdkSandboxManager mSdkSandboxManager;
        private SurfaceView mClientView;
        private boolean mSdkLoaded = false;
    
  3. צריך לבדוק אם תהליך זמן הריצה של ה-SDK זמין במכשיר.

    1. צריך לבדוק את הערך הקבוע SdkSandboxState (getSdkSandboxState()). המשמעות של SDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION היא שזמן הריצה של ה-SDK זמין.

    2. יש לוודא שהשיחה אל loadSdk() בוצעה בהצלחה. ההצלחה אם לא הופעלו חריגות, והנמען הוא המופע של SandboxedSdk.

      • יש להפעיל את loadSdk() מהחזית. אם קוראים לזה מהרקע, יושלך SecurityException.

      • בודקים ב-OutcomeReceiver מופע של SandboxedSdk כדי לוודא ש-LoadSdkException נזרק. חריגה מציינת שזמן הריצה של ה-SDK לא זמין.

    אם הקריאה SdkSandboxState או הקריאה loadSdk נכשלת, זמן הריצה של ה-SDK לא זמין, והקריאה צריכה לחזור ל-SDK הקיים.

  4. מגדירים מחלקה של קריאה חוזרת (callback) על ידי הטמעת OutcomeReceiver לצורך אינטראקציה עם ה-SDK בזמן הריצה, אחרי שהוא נטען. בדוגמה הבאה, הלקוח משתמש בקריאה חוזרת כדי להמתין עד שה-SDK ייטען בהצלחה, ואז מנסה לעבד תצוגה מפורטת של אתר מה-SDK. הקריאות החוזרות (callback) מוגדרות בשלב מאוחר יותר בשלב הזה.

    Kotlin

        private inner class LoadSdkOutcomeReceiverImpl private constructor() :
                OutcomeReceiver {
    
          override fun onResult(sandboxedSdk: SandboxedSdk) {
              mSdkLoaded = true
    
              val binder: IBinder = sandboxedSdk.getInterface()
              if (!binderInterface.isPresent()) {
                  // SDK is not loaded anymore.
                  return
              }
              val sdkInterface: ISdkInterface = ISdkInterface.Stub.asInterface(binder)
              sdkInterface.doSomething()
    
              Handler(Looper.getMainLooper()).post {
                  val bundle = Bundle()
                  bundle.putInt(SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS, mClientView.getWidth())
                  bundle.putInt(SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS, mClientView.getHeight())
                  bundle.putInt(SdkSandboxManager.EXTRA_DISPLAY_ID, display!!.displayId)
                  bundle.putInt(SdkSandboxManager.EXTRA_HOST_TOKEN, mClientView.getHostToken())
                  mSdkSandboxManager!!.requestSurfacePackage(
                          SDK_NAME, bundle, { obj: Runnable -> obj.run() },
                          RequestSurfacePackageOutcomeReceiverImpl())
              }
          }
    
          override fun onError(error: LoadSdkException) {
                  // Log or show error.
          }
        }
    

    Java

        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_DISPLAY_ID;
        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HEIGHT_IN_PIXELS;
        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_HOST_TOKEN;
        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_WIDTH_IN_PIXELS;
    
        private class LoadSdkOutcomeReceiverImpl
                implements OutcomeReceiver {
            private LoadSdkOutcomeReceiverImpl() {}
    
            @Override
            public void onResult(@NonNull SandboxedSdk sandboxedSdk) {
                mSdkLoaded = true;
    
                IBinder binder = sandboxedSdk.getInterface();
                if (!binderInterface.isPresent()) {
                    // SDK is not loaded anymore.
                    return;
                }
                ISdkInterface sdkInterface = ISdkInterface.Stub.asInterface(binder);
                sdkInterface.doSomething();
    
                new Handler(Looper.getMainLooper()).post(() -> {
                    Bundle bundle = new Bundle();
                    bundle.putInt(EXTRA_WIDTH_IN_PIXELS, mClientView.getWidth());
                    bundle.putInt(EXTRA_HEIGHT_IN_PIXELS, mClientView.getHeight());
                    bundle.putInt(EXTRA_DISPLAY_ID, getDisplay().getDisplayId());
                    bundle.putInt(EXTRA_HOST_TOKEN, mClientView.getHostToken());
    
                    mSdkSandboxManager.requestSurfacePackage(
                            SDK_NAME, bundle, Runnable::run,
                            new RequestSurfacePackageOutcomeReceiverImpl());
                });
            }
    
            @Override
            public void onError(@NonNull LoadSdkException error) {
                // Log or show error.
            }
        }
    

    כדי לחזור לתצוגה מרחוק מה-SDK בזמן הריצה בזמן הריצה ל-requestSurfacePackage(), מטמיעים את הממשק OutcomeReceiver<Bundle, RequestSurfacePackageException>:

    Kotlin

        private inner class RequestSurfacePackageOutcomeReceiverImpl :
                OutcomeReceiver {
            fun onResult(@NonNull result: Bundle) {
                Handler(Looper.getMainLooper())
                        .post {
                            val surfacePackage: SurfacePackage = result.getParcelable(
                                    EXTRA_SURFACE_PACKAGE,
                                    SurfacePackage::class.java)
                            mRenderedView.setChildSurfacePackage(surfacePackage)
                            mRenderedView.setVisibility(View.VISIBLE)
                        }
            }
    
            fun onError(@NonNull error: RequestSurfacePackageException?) {
                // Error handling
            }
        }
    

    Java

        import static android.app.sdksandbox.SdkSandboxManager.EXTRA_SURFACE_PACKAGE;
    
        private class RequestSurfacePackageOutcomeReceiverImpl
                implements OutcomeReceiver {
            @Override
            public void onResult(@NonNull Bundle result) {
                new Handler(Looper.getMainLooper())
                        .post(
                                () -> {
                                    SurfacePackage surfacePackage =
                                            result.getParcelable(
                                                    EXTRA_SURFACE_PACKAGE,
                                                    SurfacePackage.class);
                                    mRenderedView.setChildSurfacePackage(surfacePackage);
                                    mRenderedView.setVisibility(View.VISIBLE);
                                });
            }
            @Override
            public void onError(@NonNull RequestSurfacePackageException error) {
                // Error handling
            }
        }
    

    כשהתצוגה תסתיים, חשוב לזכור לשחרר SurfacePackage באמצעות:

    surfacePackage.notifyDetachedFromWindow()
    
  5. ב-onCreate(), מפעילים את SdkSandboxManager, הקריאות החוזרות הנחוצות, ולאחר מכן שולחים בקשה לטעינת ה-SDK:

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mSdkSandboxManager = applicationContext.getSystemService(
                SdkSandboxManager::class.java
        )
    
        mClientView = findViewById(R.id.rendered_view)
        mClientView.setZOrderOnTop(true)
    
        val loadSdkCallback = LoadSdkCallbackImpl()
        mSdkSandboxManager.loadSdk(
                SDK_NAME, Bundle(), { obj: Runnable -> obj.run() }, loadSdkCallback
        )
    }
    

    Java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        mSdkSandboxManager = getApplicationContext().getSystemService(
                SdkSandboxManager.class);
    
        mClientView = findViewById(R.id.rendered_view);
        mClientView.setZOrderOnTop(true);
    
        LoadSdkCallbackImpl loadSdkCallback = new LoadSdkCallbackImpl();
        mSdkSandboxManager.loadSdk(
                SDK_NAME, new Bundle(), Runnable::run, loadSdkCallback);
    }
    
  6. כדי לטפל בפנייה שבה תהליך ה-Sandbox של ה-SDK מסתיים באופן בלתי צפוי, צריך להגדיר הטמעה לממשק SdkSandboxProcessDeathCallback:

    Kotlin

        private inner class SdkSandboxLifecycleCallbackImpl() : SdkSandboxProcessDeathCallback {
            override fun onSdkSandboxDied() {
                // The SDK runtime process has terminated. To bring back up the
                // sandbox and continue using SDKs, load the SDKs again.
                val loadSdkCallback = LoadSdkOutcomeReceiverImpl()
                mSdkSandboxManager.loadSdk(
                          SDK_NAME, Bundle(), { obj: Runnable -> obj.run() },
                          loadSdkCallback)
            }
        }
    

    Java

          private class SdkSandboxLifecycleCallbackImpl
                  implements SdkSandboxProcessDeathCallback {
              @Override
              public void onSdkSandboxDied() {
                  // The SDK runtime process has terminated. To bring back up
                  // the sandbox and continue using SDKs, load the SDKs again.
                  LoadSdkOutcomeReceiverImpl loadSdkCallback =
                          new LoadSdkOutcomeReceiverImpl();
                  mSdkSandboxManager.loadSdk(
                              SDK_NAME, new Bundle(), Runnable::run, loadSdkCallback);
              }
          }
    

    כדי לרשום את הקריאה החוזרת (callback) כך שתקבלו מידע על סיום ה-Sandbox של ה-SDK, תוכלו להוסיף את השורה הבאה בכל שלב:

    Kotlin

        mSdkSandboxManager.addSdkSandboxProcessDeathCallback({ obj: Runnable -> obj.run() },
                SdkSandboxLifecycleCallbackImpl())
    

    Java

        mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run,
                new SdkSandboxLifecycleCallbackImpl());
    

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

  7. מוסיפים תלות במודול ה-SDK ל-build.gradle של אפליקציית הלקוח:

    dependencies {
        ...
        implementation project(':<your-sdk-module>')
        ...
    }

בדיקת האפליקציות

כדי להריץ את אפליקציית הלקוח, מתקינים את אפליקציית ה-SDK ואת אפליקציית הלקוח במכשיר הבדיקה באמצעות Android Studio או באמצעות שורת הפקודה.

פריסה באמצעות Android Studio

במהלך הפריסה באמצעות Android Studio, מבצעים את השלבים הבאים:

  1. פותחים את פרויקט Android Studio של אפליקציית הלקוח.
  2. עוברים אל הפעלה > עריכת הגדרות. יופיע החלון Run/Debug Configuration.
  3. בקטע אפשרויות הפעלה, מגדירים את האפשרות הפעלה לאפשרות פעילות שצוינה.
  4. לחצו על תפריט שלוש הנקודות לצד 'פעילות' ובחרו באפשרות הפעילות הראשית של הלקוח.
  5. לוחצים על Apply (אישור) ואז על OK (אישור).
  6. לוחצים על Run כדי להתקין את אפליקציית הלקוח ואת ה-SDK במכשיר הבדיקה.

פריסה בשורת הפקודה

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

  1. ממסוף של שורת הפקודה, יוצרים את חבילות ה-APK של ה-SDK של ארגז החול לפרטיות:

    ./gradlew :client-app:buildPrivacySandboxSdkApksForDebug
    

    פלט המיקום של חבילות ה-APK שנוצרו. חבילות ה-APK האלה חתומות באמצעות מפתח ניפוי הבאגים המקומי. הנתיב הזה נדרש בפקודה הבאה.

  2. מתקינים את ה-APK במכשיר:

    adb install -t /path/to/your/standalone.apk
    
  3. ב-Android Studio, לוחצים על הפעלה > עריכת הגדרות. מופיע החלון Run/Debug Configuration.

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

  5. לוחצים על Apply (אישור) ואז על OK (אישור).

  6. לוחצים על הפעלה כדי להתקין את חבילת ה-APK במכשיר הבדיקה.

ניפוי באגים באפליקציות

על מנת לנפות באגים באפליקציית הלקוח, לוחצים על הלחצן Debug ב-Android Studio.

כדי לנפות באגים באפליקציית ה-SDK, נכנסים לקטע Run > Attach to Process, שמופיע מסך קופץ (מוצג למטה). מסמנים את התיבה הצגת כל התהליכים. ברשימה שמופיעה, חפשו תהליך בשם CLIENT_APP_PROCESS_sdk_sandbox. כדי להתחיל לנפות באגים ב-SDK, יש לבחור באפשרות הזו ולהוסיף נקודות עצירה לקוד של אפליקציית ה-SDK.

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

הפעלה ועצירה של זמן הריצה של ה-SDK משורת הפקודה

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

adb shell cmd sdk_sandbox start [--user <USER_ID> | current] <CLIENT_APP_PACKAGE>

באופן דומה, כדי לעצור את תהליך זמן הריצה של ה-SDK, מריצים את הפקודה הבאה:

adb shell cmd sdk_sandbox stop [--user <USER_ID> | current] <CLIENT_APP_PACKAGE>

מגבלות

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

דוגמאות קוד

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

דיווח על באגים ובעיות

המשוב שלך הוא חלק חיוני מארגז החול לפרטיות ב-Android. דווחו לנו על בעיות שמצאתם או על רעיונות לשיפור ארגז החול לפרטיות ב-Android.