כשתקראו את מסמכי התיעוד של ארגז החול לפרטיות ב-Android, השתמשו בלחצן תצוגה מקדימה למפתחים או בטא כדי לבחור את גרסת התוכנית שאיתה אתם עובדים, מכיוון שההוראות עשויות להשתנות.
זמן הריצה של ה-SDK מאפשר לערכות SDK לפעול בארגז חול ייעודי שנפרד מהאפליקציה לשיחות. זמן הריצה של ה-SDK מספק אמצעי הגנה ואחריות משופרת לאיסוף נתוני משתמשים. הדבר מתבצע באמצעות סביבת ביצוע מותאמת שמגבילה את זכויות הגישה לנתונים ואת קבוצת ההרשאות המותרות. מידע נוסף על זמן הריצה של ה-SDK זמין בהצעת העיצוב.
ההוראות בדף הזה ינחו אותך בתהליך היצירה של SDK עם זמן ריצה, שמגדיר תצוגה מבוססת-אינטרנט שאפשר לעבד מרחוק לאפליקציה לשיחות.
מגבלות ידועות
נתוני הגרסה כוללים את רשימת היכולות שמתבצעות בזמן הריצה של ה-SDK.
המגבלות הבאות צפויות להיות מתוקנות בגרסה הגדולה הבאה של הפלטפורמה של Android.
- מודעות שמוצגות בתצוגה שניתנת לגלילה. לדוגמה,
RecyclerView
לא פועל כראוי.- ייתכן שתיתקלו ב-jank בעת שינוי הגודל.
- אירועי הגלילה באמצעות מגע של המשתמש לא מועברים לזמן הריצה בצורה תקינה.
- Storage API
- אחסון לכל SDK לא זמין ב-Android 13.
הבעיה הבאה תיפתר בשנת 2023:
- ממשקי ה-API של
getAdId
ו-getAppSetId
עדיין לא פועלים בצורה תקינה כי התמיכה בהם עדיין לא הופעלה.
לפני שמתחילים
לפני שמתחילים, חשוב לבצע את השלבים הבאים:
מגדירים את סביבת הפיתוח של ארגז החול לפרטיות ב-Android. השימוש בכלי לתמיכה בזמן הריצה ב-SDK נמצא בפיתוח פעיל, ולכן תצטרכו להשתמש בגרסה Canary של Android Studio העדכנית. תוכלו להריץ את הגרסה הזו של Android Studio במקביל לגרסאות אחרות שבהן אתם משתמשים, אז נשמח לדעת אם הדרישה הזו לא עובדת.
מתקינים תמונת מערכת במכשיר נתמך או מגדירים אמולטור שכולל תמיכה בארגז החול לפרטיות ב-Android.
הגדרת הפרויקט ב-Android Studio
כדי לנסות את זמן הריצה של ה-SDK, עליכם להשתמש במודל שדומה למודל שרת הלקוח. ההבדל העיקרי הוא שאפליקציות (הלקוח) וערכות SDK (ה "שרת") פועלות באותו מכשיר.
- מוסיפים מודול אפליקציה לפרויקט. המודול הזה משמש כלקוח שמניע את ה-SDK.
- במודול האפליקציה, מפעילים את זמן הריצה ל-SDK, מצהירים על ההרשאות הנדרשות ומגדירים שירותי פרסום שספציפיים ל-API.
- הוספת מודול ספרייה אחד לפרויקט. המודול הזה מכיל את קוד ה-SDK שלך.
- מצהירים במודול ה-SDK על ההרשאות הנדרשות. במודול הזה לא צריך להגדיר שירותי מודעות ספציפיים ל-API.
- מסירים את
dependencies
בקובץbuild.gradle
של המודול של הספרייה, שה-SDK לא משתמש בו. ברוב המקרים אפשר להסיר את כל יחסי התלות. כדי לעשות זאת, יוצרים ספרייה חדשה ששמה תואם ל-SDK שלכם. יוצרים באופן ידני מודול חדש עם הסוג
com.android.privacy-sandbox-sdk
. החבילה הזו כלולה בחבילה עם קוד ה-SDK כדי ליצור חבילת APK שאפשר לפרוס במכשיר שלכם. כדי לעשות זאת, אפשר ליצור ספרייה חדשה ששמה תואם ל-SDK שלכם. יש להוסיף קובץbuild.gradle
ריק. התוכן של הקובץ הזה יאוכלס בהמשך המדריך.מוסיפים את קטע הקוד הבא לקובץ
gradle.properties
:android.experimental.privacysandboxsdk.enable=true
מורידים את תמונת האמולטור TiramisuPrivacySandbox ויוצרים אמולטור עם התמונה הזו שכוללת את חנות Play.
ההגדרה הסופית עשויה להיות שונה מזו שמתוארת בפסקה הקודמת, בהתאם למפתחים של SDK או למפתחי אפליקציות.
מתקינים את ה-SDK במכשיר בדיקה, בדומה לאופן שבו מתקינים אפליקציה, באמצעות Android Studio או גשר ניפוי הבאגים של Android (ADB). כדי לעזור לכם להתחיל, יצרנו אפליקציות לדוגמה בשפות התכנות Kotlin ו-Java, שזמינות במאגר הזה של GitHub. בקובצי README ובקובצי המניפסט יש הערות שמתארות מה צריך לשנות כדי להריץ את הדוגמה בגרסאות יציבות של Android Studio.
הכנת ה-SDK
יצירת ספרייה ברמת המודול באופן ידני. היא משמשת כ-wrapper סביב קוד ההטמעה לצורך בניית ה-APK של ה-SDK. בספרייה החדשה, צריך להוסיף קובץ
build.gradle
ולאכלס אותו בקטע הקוד הבא. עליכם להשתמש בשם ייחודי ל-SDK עם תמיכה בזמן ריצה (RE-SDK) ולספק גרסה. כוללים את מודול הספרייה בקטעdependencies
.plugins { id 'com.android.privacy-sandbox-sdk' } android { compileSdkPreview 'TiramisuPrivacySandbox' minSdkPreview 'TiramisuPrivacySandbox' 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>') }
יוצרים מחלקה בספריית ההטמעה, שתשמש כנקודת כניסה ל-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. } } }
SdkSandboxController
SdkSandboxController
הוא wrapper של שירות מערכת מבוסס הקשר שזמין לערכות SDK. אפשר לאחזר אותו באמצעות ערכות SDK באמצעות ההקשר שהתקבל מ-SandboxedSdkProvider#getContext()
והפעלה של context.getSystemService(SdkSandboxController.class)
בהקשר הזה. לנאמני המידע יש ממשקי API שעוזרים לערכות ה-SDK לקיים אינטראקציה עם ארגז החול לפרטיות ולקבל מידע ממנו.
רוב ממשקי ה-API בבקר מקפיצים חריגים אם SandboxedSdkContext
הוא לא ההקשר שמשמש לגישה. אנחנו מתכננים לא לאפשר את ה-wrapper של השירות בהקשרים אחרים. הקובץ SdkSandboxController
מיועד לשימוש על ידי ספקי SDK,
ומפתחי אפליקציות לא ממליצים להשתמש בו.
תקשורת בין SDK ל-SDK
ערכות SDK בזמן הריצה צריכות להיות מסוגלות לתקשר זו עם זו, כדי לתמוך בתהליך בחירת הרשת ובתרחישים לדוגמה הקשורים. ערכת הכלים שמתוארת כאן מספקת ממשק לעבודה עם ערכות SDK שזמינות לערכות SDK אחרות בתוך ארגז החול. ההטמעה של זמן הריצה ל-SDK היא שלב ראשון לקראת הפעלת SDK לתקשורת SDK, ויכול להיות שהיא עדיין לא כוללת את כל התרחישים לדוגמה לתהליך בחירת הרשת (Mediation) בארגז החול לפרטיות.
getSandboxedSdks()
API ב-SdkSandboxController
מספק מחלקה של
SandboxedSdk
לכל ערכות ה-SDK שנטענו בארגז החול לפרטיות. האובייקט SandboxedSdk
כולל פרטים על ה-SDK ועל sdkInterface
, כדי שהלקוחות יוכלו לתקשר איתו.
ה-SDK בארגז החול לפרטיות אמור להשתמש בקטעי קוד שדומים לערכות ה-SDK הבאות, כדי לתקשר עם ערכות SDK אחרות. נניח שהקוד נכתב כדי לאפשר ל-SDK1 לתקשר עם SDK2, ושניהם נטענים על ידי האפליקציה בארגז החול לפרטיות.
SdkSandboxController controller = mSdkContext
.getSystemService(SdkSandboxController.class);
List<SandboxedSdk> sandboxedSdks = controller.getSandboxedSdks();
SandboxedSdk sdk2 = sandboxedSdks.stream().filter( // The SDK it wants to
// connect to, based on SDK name or SharedLibraryInfo.
try {
IBinder binder = sdk2.getInterface();
ISdkApi sdkApi = ISdkApi.Stub.asInterface(binder);
// Call API on SDK2
message = sdkApi.getMessage();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
בדוגמה שלמעלה, ערכת SDK1 מוסיפה את ספריית ה-AIDL של ה-SDK2 כתלות. ערכת ה-SDK של הלקוח הזו מכילה קוד Binder שנוצר על ידי ה-AIDL. שתי ערכות ה-SDK צריכות לייצא את ספריית ה-AIDL הזו. הפעולות האלה זהות למה שהאפליקציות מבצעות כשהן מתקשרות עם ערכות SDK בארגז החול לפרטיות.
תמיכה באופן שנוצר באופן אוטומטי לשיתוף ממשקים בין ערכות SDK תתווסף בעדכון עתידי.
יכול להיות שערכות SDK בזמן הריצה צריכות לתקשר עם יחסי תלות של אפליקציות וערכות SDK של מודעות שעדיין לא הופעלו בהן זמן ריצה.
registerAppOwnedSdkSandboxInterface()
API ב-SdkSandboxManager
מאפשר לערכות SDK שלא תואמות זמן ריצה לרשום את הממשקים בפלטפורמה. ה-API של getAppOwnedSdkSandboxInterfaces()
ב-SdkSandboxController
מספק את AppOwnedSdkSandboxInterface
לכל ערכות ה-SDK הרשומות שמקושרות באופן סטטי.
הדוגמה הבאה ממחישה איך לרשום ממשקים כדי שיהיו זמינים לתקשורת ערכות SDK עם תמיכה בזמן ריצה:
// Register AppOwnedSdkSandboxInterface
mSdkSandboxManager.registerAppOwnedSdkSandboxInterface(
new AppOwnedSdkSandboxInterface(
APP_OWNED_SDK_NAME, (long) APP_OWNED_SDK_VERSION, new AppOwnedSdkApi())
);
הדוגמה הזו ממחישה איך למדוד את התקשורת עם ערכות SDK שלא תואמות זמן ריצה:
// Get AppOwnedSdkSandboxInterface
List<AppOwnedSdkSandboxInterface> appOwnedSdks = mSdkContext
.getSystemService(SdkSandboxController.class)
.getAppOwnedSdkSandboxInterfaces();
AppOwnedSdkSandboxInterface appOwnedSdk = appOwnedSdks.stream()
.filter(s -> s.getName().contains(APP_OWNED_SDK_NAME))
.findAny()
.get();
IAppOwnedSdkApi appOwnedSdkApi =
IAppOwnedSdkApi.Stub.asInterface(appOwnedSdk.getInterface());
message = appOwnedSdkApi.getMessage();
מאחר שייתכן שלא ניתן לרשום את ערכות ה-SDK של מודעות בלי להפעיל את זמן הריצה, אנחנו מציעים ליצור SDK מגשר שמטפל ברישום וכולל ערכות SDK של שותפים או אפליקציות כיחסי תלות ישירים. ה-SDK של המגשר יוצר תקשורת בין ערכות SDK ויחסי תלות שלא תומכים בזמן ריצה לבין המגשר עם תמיכה בסביבת זמן הריצה שפועל כמתאם.
תמיכה בפעילות
ערכות SDK תואמות זמן ריצה לא יכולות להוסיף תג פעילות לקובץ המניפסט, והן לא יכולות להתחיל את הפעילויות שלהן ישירות. הגישה לאובייקט Activity
ניתנת על ידי רישום SdkSandboxActivityHandler
והתחלת הפעילות ב-Sandbox:
1. רישום SdkSandboxActivityHandler
רישום מכונה של SdkSandboxActivityHandler
באמצעות SdkSandboxController#registerSdkSandboxActivityHandler(SandboxedActivityHandler)
ה-API רושם את האובייקט ומחזיר אובייקט IBinder
שמזהה את ה-SdkSandboxActivityHandler
שהועבר.
public interface SdkSandboxActivityHandler {
void onActivityCreated(Activity activity);
}
ה-API רושם את האובייקט ומחזיר אובייקט IBinder
שמזהה את ה-SdkSandboxActivityHandler
שהועבר.
2. התחלת הפעילות ב-Sandbox
ה-SDK מעביר לאפליקציית הלקוח את האסימון שמוחזר כדי לזהות את SdkSandboxActivityHandler
הרשום. לאחר מכן, אפליקציית הלקוח קוראת ל-SdkSandboxManager#startSdkSandboxActivity(Activity, Binder)
, מעבירה פעילות שממנה מתחיל את Sandbox ואסימון שמזהה את SdkSandboxActivityHandler
הרשום.
בשלב הזה מתחילה פעילות פלטפורמה חדשה שפועלת באותו זמן ריצה של ה-SDK כמו ה-SDK שמבקש.
כשהפעילות מתחילה, ה-SDK מקבל הודעה באמצעות קריאה ל-SdkSandboxActivityHandler#onActivityCreated(Activity)
כחלק מההפעלה של Activity#OnCreate(Bundle)
.
לדוגמה, עם גישה לאובייקט Activity
, והמתקשר יכול להגדיר תצוגה (contentView
) על ידי קריאה ל-Activity#setContentView(View)
.
כדי לרשום קריאות חוזרות במחזור החיים, משתמשים ב-Activity#registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks)
.
כדי לרשום את OnBackInvokedCallback
לפעילות שהועברה, צריך להשתמש ב-Activity#getOnBackInvokedDispatcher().registerOnBackInvokedCallback(Int,
OnBackInvokedCallback)
.
בדיקת נגני וידאו בזמן הריצה ל-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
. דוגמאות:
- גישה לקבצים באמצעות Storage Access Framework תוביל ל-
SecurityException
. - הערך
getExternalFilsDir()
תמיד יחזיר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
קריאת הנתונים של SharedPreferences
של הלקוח
אפליקציות לקוח יכולות לשתף קבוצת מפתחות מה-SharedPreferences
שלה עם SdkSandbox
. ערכות SDK יכולות לקרוא את הנתונים שסונכרנו מאפליקציית הלקוח באמצעות SdkSanboxController#getClientSharedPreferences()
API. ה-SharedPreferences
שמוחזר מה-API הזה מיועדים לקריאה בלבד. אין לכתוב אליה.
גישה למזהה הפרסום שסופק על ידי 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, צריך לבצע את השינויים הבאים באפליקציית הלקוח לשיחות:
מוסיפים את ההרשאות
INTERNET
ו-ACCESS_NETWORK_STATE
למניפסט של האפליקציה:<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
בפעילות באפליקציה שכוללת מודעה, צריך להצהיר על הפניה ל-
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;
צריך לבדוק אם תהליך זמן הריצה של ה-SDK זמין במכשיר.
צריך לבדוק את הערך הקבוע
SdkSandboxState
(getSdkSandboxState()
). המשמעות שלSDK_SANDBOX_STATE_ENABLED_PROCESS_ISOLATION
היא שזמן הריצה של ה-SDK זמין.יש לוודא שהשיחה אל
loadSdk()
בוצעה בהצלחה. ההצלחה אם לא הופעלו חריגות, והנמען הוא המופע שלSandboxedSdk
.יש להפעיל את
loadSdk()
מהחזית. אם קוראים לזה מהרקע, יושלךSecurityException
.בודקים ב-
OutcomeReceiver
מופע שלSandboxedSdk
כדי לוודא ש-LoadSdkException
נזרק. חריגה מציינת שזמן הריצה של ה-SDK לא זמין.
אם הקריאה
SdkSandboxState
או הקריאהloadSdk
נכשלת, זמן הריצה של ה-SDK לא זמין, והקריאה צריכה לחזור ל-SDK הקיים.מגדירים מחלקה של קריאה חוזרת (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()
ב-
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); }
האפליקציה יכולה לשתף עם Sandbox מפתחות מסוימים של
SharedPreferences
שהוגדרה כברירת מחדל. הם יכולים לעשות זאת על ידי קריאה ל-methodSdkSandboxManager#addSyncedSharedPreferencesKeys(Set<String>keys)
בכל מכונה של ניהולSdkSandbox
. לאחר שהאפליקציה תודיעSdkSandboxManager
על אילו מקשים לסנכרן,SdkSandboxManager
תסנכרן את הערכים של המפתחות האלה ב-Sandbox ובערכות SDK כדי לקרוא אותם באמצעותSdkSandboxController#getClientSharedPreferences
. למידע נוסף, ראו קריאת SharedPreferences של הלקוח.קבוצת המפתחות שמסונכרנת לא נשמרת בהפעלה מחדש של האפליקציה, והנתונים שמסונכרנים ל-Sandbox נמחקים במהלך ההפעלה מחדש של ה-Sandbox. לכן חשוב שהאפליקציה תפעיל את הסנכרון על ידי קריאה ל-
addSyncedSharedPreferencesKeys
בכל פעם שהיא מופעלת.כדי לשנות את קבוצת המפתחות שמסונכרנים, מתקשרים אל
SdkSandboxManager#removeSyncedSharedPreferencesKeys(Set<String>keys)
כדי להסיר את המפתחות. כדי להציג את הקבוצה הנוכחית של המפתחות שמסונכרנים צריך להשתמש ב-SdkSandboxManager#getSyncedSharedPreferencesKeys()
.אנחנו ממליצים לשמור על קבוצת המפתחות קטנה ככל האפשר ולהשתמש בה רק אם יש צורך. אם ברצונך להעביר מידע לערכות SDK למטרה כללית, עליך ליצור קשר עם ה-SDK ישירות באמצעות ממשק
SandboxedSdk
. תרחיש אפשרי לשימוש בממשקי ה-API האלה: אם האפליקציה משתמשת ב-SDK של פלטפורמה לניהול הסכמה (CMP), וערכות SDK בתוך ה-Sandbox יביעו עניין בקריאת הנתונים ששמורים ב-CMP SDK כברירת המחדלSharedPreferences
.Kotlin
override fun onCreate(savedInstanceState: Bundle?) { … // At some point, initiate the set of keys for synchronization with sandbox mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of("foo", "bar")); }
Java
@Override protected void onCreate(Bundle savedInstanceState) { … // At some point, initiate the set of keys for synchronization with sandbox mSdkSandboxManager.addSyncedSharedPreferencesKeys(Set.of("foo", "bar")); }
כדי לטפל בפנייה שבה תהליך ה-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.
מוסיפים תלות במודול ה-SDK ל-
build.gradle
של אפליקציית הלקוח:dependencies { ... implementation project(':<your-sdk-module>') ... }
בדיקת האפליקציות
כדי להריץ את אפליקציית הלקוח, מתקינים את אפליקציית ה-SDK ואת אפליקציית הלקוח במכשיר הבדיקה באמצעות Android Studio או באמצעות שורת הפקודה.
פריסה באמצעות Android Studio
במהלך הפריסה באמצעות Android Studio, מבצעים את השלבים הבאים:
- פותחים את פרויקט Android Studio של אפליקציית הלקוח.
- עוברים אל הפעלה > עריכת הגדרות. יופיע החלון Run/Debug Configuration.
- בקטע אפשרויות הפעלה, מגדירים את האפשרות הפעלה לאפשרות פעילות שצוינה.
- לחצו על תפריט שלוש הנקודות לצד 'פעילות' ובחרו באפשרות הפעילות הראשית של הלקוח.
- לוחצים על Apply (אישור) ואז על OK (אישור).
- לוחצים על Run כדי להתקין את אפליקציית הלקוח ואת ה-SDK במכשיר הבדיקה.
פריסה בשורת הפקודה
במהלך הפריסה באמצעות שורת הפקודה, מבצעים את השלבים שמפורטים ברשימה הבאה.
בקטע הזה ההנחה היא שהשם של מודול האפליקציה של ה-SDK הוא sdk-app
והשם של מודול אפליקציית הלקוח הוא client-app
.
ממסוף של שורת הפקודה, יוצרים את חבילות ה-APK של ה-SDK של ארגז החול לפרטיות:
./gradlew :client-app:buildPrivacySandboxSdkApksForDebug
פלט המיקום של חבילות ה-APK שנוצרו. חבילות ה-APK האלה חתומות באמצעות מפתח ניפוי הבאגים המקומי. הנתיב הזה נדרש בפקודה הבאה.
מתקינים את ה-APK במכשיר:
adb install -t /path/to/your/standalone.apk
ב-Android Studio, לוחצים על הפעלה > עריכת הגדרות. מופיע החלון Run/Debug Configuration.
בקטע אפשרויות התקנה, מגדירים את האפשרות פריסה ל-APK המוגדר כברירת מחדל.
לוחצים על Apply (אישור) ואז על OK (אישור).
לוחצים על הפעלה כדי להתקין את חבילת ה-APK במכשיר הבדיקה.
ניפוי באגים באפליקציות
על מנת לנפות באגים באפליקציית הלקוח, לוחצים על הלחצן Debug ב-Android Studio.
כדי לנפות באגים באפליקציית ה-SDK, נכנסים לקטע Run > Attach to Process, שמופיע מסך קופץ (מוצג למטה). מסמנים את התיבה הצגת כל התהליכים. ברשימה שמופיעה, חפשו תהליך בשם CLIENT_APP_PROCESS_sdk_sandbox
. כדי להתחיל לנפות באגים ב-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 נטענות כרגע, אפשר להשתמש בפונקציונליות getSandboxedSdks
בתוך SdkSandboxManager
.
מגבלות
בנתוני הגרסה תוכלו למצוא את רשימת היכולות שמתבצעות בזמן הריצה של ה-SDK.
דוגמאות קוד
מאגר זמן הריצה של ה-SDK וממשק ה-API לשמירה על הפרטיות ב-GitHub מכיל קבוצה של פרויקטים נפרדים ב-Android Studio שיעזרו לכם להתחיל, כולל דוגמאות שמדגימות איך להפעיל את ה-SDK ול לקרוא לו זמן ריצה.דיווח על באגים ובעיות
המשוב שלך הוא חלק חיוני מארגז החול לפרטיות ב-Android. דווחו לנו על בעיות שמצאתם או על רעיונות לשיפור ארגז החול לפרטיות ב-Android.
מומלץ עבורכם
- הערה: טקסט הקישור מוצג כאשר JavaScript מושבת
- זמן ריצה ל-SDK
- נתוני גרסה
- Protected Audience API במדריך למפתחים ל-Android