دليل مطوّري البرامج حول وقت تشغيل حزمة تطوير البرامج (SDK)

أثناء الاطّلاع على مستندات "مبادرة حماية الخصوصية" على Android، استخدِم الزر معاينة المطوّر أو الزر إصدار تجريبي لاختيار إصدار البرنامج الذي تعمل معه، لأنّ التعليمات قد تختلف.


تقديم ملاحظات

يسمح "وقت تشغيل SDK" بتشغيل حِزم SDK في وضع حماية مخصَّص ومنفصل عن تطبيق الاتصال. ويوفّر "وقت تشغيل SDK" إجراءات حماية وضمانات محسّنة بشأن جمع بيانات المستخدمين. ويتم ذلك من خلال بيئة تنفيذ معدَّلة تحدّ من حقوق الوصول إلى البيانات ومجموعة الأذونات المسموح بها. اطّلِع على مزيد من المعلومات عن "وقت تشغيل SDK" في اقتراح التصميم.

ترشدك الخطوات الواردة في هذه الصفحة خلال عملية إنشاء حزمة تطوير برامج (SDK) يتم تفعيلها في وقت التشغيل وتحدّد عرضًا مستنِدًا إلى الويب يمكن عرضه عن بُعد في تطبيق اتصال.

القيود المعروفة

للحصول على قائمة بالإمكانات قيد التقدم في "وقت تشغيل SDK"، يمكنك الاطّلاع على ملاحظات الإصدار.

من المتوقع إصلاح القيود التالية في الإصدار الرئيسي التالي لنظام Android الأساسي.

  • عرض الإعلانات ضمن شاشة عرض قابلة للتمرير على سبيل المثال، لا يعمل RecyclerView بشكل صحيح.
    • قد يحدث التشويش عند تغيير الحجم.
    • لا يتم تمرير أحداث التمرير باللمس للمستخدم إلى وقت التشغيل بشكل صحيح.
  • واجهة برمجة التطبيقات Storage

سيتم حلّ المشكلة التالية في عام 2023:

  • لا تعمل واجهتا برمجة التطبيقات getAdId وgetAppSetId بشكل صحيح لأنّه لم يتم بعد تفعيل إمكانية استخدامهما.

قبل البدء

قبل البدء، أكمل الخطوات التالية:

  1. إعداد بيئة تطوير "مبادرة حماية الخصوصية" على Android لا تزال الأدوات اللازمة لدعم "وقت تشغيل SDK" قيد التطوير النشط، لذا سيُطلب منك في هذا الدليل استخدام أحدث إصدار Canary من Android استوديو. يمكنك تشغيل هذا الإصدار من "استوديو Android" بالتوازي مع الإصدارات الأخرى التي تستخدمها، لذا يُرجى إعلامنا إذا لم يكن هذا الشرط مناسبًا لك.

  2. يمكنك إما تثبيت صورة نظام على جهاز متوافق أو إعداد محاكي يتيح استخدام "مبادرة حماية الخصوصية" على Android.

إعداد مشروعك في "استوديو Android"

لتجربة "وقت تشغيل SDK"، يمكنك استخدام نموذج مشابه لنموذج خادم العميل. يتمثل الاختلاف الرئيسي في أن التطبيقات (العميل) وحزم SDK ("الخادم") تعمل على نفس الجهاز.

  1. أضِف وحدة تطبيق إلى مشروعك. هذه الوحدة هي البرنامج الذي يشغِّل حزمة تطوير البرامج (SDK).
  2. في وحدة تطبيقك، فعِّل "وقت تشغيل SDK" وحدِّد الأذونات اللازمة واضبط الخدمات الإعلانية الخاصة بواجهة برمجة التطبيقات.
  3. أضف وحدة مكتبة واحدة إلى مشروعك. تحتوي هذه الوحدة على رمز حزمة تطوير البرامج (SDK).
  4. في وحدة حزمة تطوير البرامج (SDK)، حدِّد الأذونات اللازمة. ولن تحتاج إلى ضبط الخدمات الإعلانية الخاصة بواجهة برمجة التطبيقات في هذه الوحدة.
  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. نزِّل صورة المحاكي TiramisuPrivacySandbox وأنشِئ محاكيًا بهذه الصورة التي تتضمّن "متجر Play".

قد يكون لديك إعداد نهائي مختلف عن الإعداد الموضّح في الفقرة السابقة، وذلك بناءً على ما إذا كنت مطوِّر حزمة SDK أو مطوِّر تطبيقات.

ثبِّت حزمة SDK على جهاز اختباري بالطريقة نفسها التي يتم بها تثبيت التطبيق، وذلك باستخدام "استوديو Android" أو Android Debug Bridge (ADB). لمساعدتك في البدء، أنشأنا نماذج من التطبيقات بلغتَي البرمجة Kotlin وJava، ويمكن العثور عليها في مستودع GitHub هذا. تحتوي ملفات README وملفات البيان على تعليقات تصف ما يجب تغييره لتشغيل العيّنة في الإصدارات الثابتة من "استوديو Android".

إعداد حزمة تطوير البرامج (SDK)

  1. إنشاء دليل على مستوى الوحدة يدويًا. يعمل هذا كبرنامج تضمين حول رمز التنفيذ لإنشاء حزمة SDK لحزمة 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>')
    }
    
  2. أنشئ فئة في مكتبة التنفيذ لتكون بمثابة نقطة دخول لحزمة تطوير البرامج (SDK). ويجب أن يرتبط اسم الفئة بقيمة sdkProviderClassName وأن يوسّع نطاق SandboxedSdkProvider.

تم توسيع نقطة دخول حزمة تطوير البرامج (SDK) في SandboxedSdkProvider. يحتوي SandboxedSdkProvider على عنصر Context لحزمة تطوير البرامج (SDK) الخاصة بك، والذي يمكنك الوصول إليه عن طريق طلب getContext(). يجب الوصول إلى هذا السياق مرة واحدة فقط عند استدعاء onLoadSdk().

لتجميع تطبيق SDK، يجب إلغاء الطرق للتعامل مع دورة حياة 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 هو برنامج تضمين خدمة نظام مستند إلى السياق متوفر لحِزم SDK. ويمكن جلب هذه الحزمة بواسطة حِزم SDK باستخدام السياق المُستلَم من SandboxedSdkProvider#getContext() واستدعاء context.getSystemService(SdkSandboxController.class) في هذا السياق. وتتضمّن وحدة التحكّم واجهات برمجة تطبيقات تساعد حِزم SDK على التفاعل مع "مبادرة حماية الخصوصية" والحصول على المعلومات منها.

تعرض معظم واجهات برمجة التطبيقات في وحدة التحكّم استثناءات إذا لم يكن SandboxedSdkContext هو السياق المستخدَم للوصول إلى البيانات. نحن نخطط لجعل برنامج تضمين الخدمة غير متاح للسياقات الأخرى. تم تصميم SdkSandboxController ليستخدمه موفّرو حِزم SDK، ولا يُنصح باستخدامه من قِبل مطوّري التطبيقات.

الاتصال من حزمة SDK إلى حزمة تطوير البرامج (SDK)

يجب أن تتمكن حِزم SDK في وقت التشغيل من التواصل بعضها مع بعض لدعم التوسّط وحالات الاستخدام ذات الصلة. توفر مجموعة الأدوات الموضحة هنا واجهة للعمل مع حزم SDK المتوفرة لحزم SDK الأخرى داخل وضع الحماية. يُعتبر تنفيذ "وقت تشغيل SDK" خطوة أولى نحو تفعيل التواصل باستخدام حزمة SDK، وقد لا يشمل حتى الآن جميع حالات الاستخدام للتوسّط في "مبادرة حماية الخصوصية".

توفّر واجهة برمجة التطبيقات getSandboxedSdks() في SdkSandboxController فئة SandboxedSdk لكل حِزم SDK التي يتم تحميلها في "مبادرة حماية الخصوصية". يتضمّن الكائن SandboxedSdk تفاصيل حول حزمة تطوير البرامج (SDK) وsdkInterface يمكن للعملاء التواصل معها.

في حِزم تطوير البرامج (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 طريقة لتسجيل واجهاتها على النظام الأساسي إذا لم يتم تفعيل ميزة وقت التشغيل في وقت التشغيل. توفّر واجهة برمجة التطبيقات 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 وبدء نشاط وضع الحماية:

1- تسجيل SdkSandboxActivityHandler

تسجيل مثيل لـ SdkSandboxActivityHandler باستخدام SdkSandboxController#registerSdkSandboxActivityHandler(SandboxedActivityHandler)

تُسجِّل واجهة برمجة التطبيقات ذلك الكائن وتعرض عنصر IBinder الذي يحدّد SdkSandboxActivityHandler الذي تم تمريره.

public interface SdkSandboxActivityHandler {
    void onActivityCreated(Activity activity);
}

تُسجِّل واجهة برمجة التطبيقات ذلك الكائن وتعرض عنصر IBinder الذي يحدّد SdkSandboxActivityHandler الذي تم تمريره.

2- بدء نشاط "وضع الحماية"

تمرِّر حزمة تطوير البرامج (SDK) الرمز المميّز الذي تم عرضه لتحديد رمز SdkSandboxActivityHandler المسجَّل إلى تطبيق العميل. بعد ذلك، يتصل تطبيق العميل بـ SdkSandboxManager#startSdkSandboxActivity(Activity, Binder)، ويمرِّر نشاطًا لبدء وضع الحماية منه، ويحدِّد رمزًا مميزًا يحدِّد رمز 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;
        }
    }

استخدام واجهات برمجة التطبيقات للتخزين في حزمة تطوير البرامج (SDK)

لم يعُد بإمكان حِزم SDK في "وقت تشغيل SDK" الوصول إلى وحدة التخزين الداخلية للتطبيق أو قراءتها أو كتابتها، والعكس صحيح. سيتم تخصيص مساحة التخزين الداخلية الخاصة بها لوقت تشغيل SDK، والتي يمكن ضمان أن تكون منفصلة عن التطبيق.

ستتمكن حِزم تطوير البرامج (SDK) من الوصول إلى وحدة التخزين الداخلية المنفصلة هذه باستخدام واجهات برمجة التطبيقات لتخزين الملفات في العنصر Context الذي يعرضه SandboxedSdkProvider#getContext(). يمكن لحِزم SDK استخدام مساحة تخزين داخلية فقط، لذلك لن تعمل سوى واجهات برمجة تطبيقات مساحة التخزين الداخلية، مثل Context.getFilesDir() أو Context.getCacheDir(). يمكنك الاطّلاع على المزيد من الأمثلة في قسم الوصول من وحدة التخزين الداخلية.

لا يمكن الوصول إلى وحدة التخزين الخارجية من "وقت تشغيل SDK". سيؤدي استدعاء واجهات برمجة التطبيقات للوصول إلى وحدة التخزين الخارجية إما إلى إنشاء استثناء أو عرض null. عدة أمثلة:

في نظام التشغيل Android 13، ستشارك كل حِزم SDK في "وقت تشغيل SDK" وحدة التخزين الداخلية المخصّصة لوقت تشغيل SDK. وسيستمر التخزين إلى أن يتم إلغاء تثبيت تطبيق العميل أو عند تنظيف بيانات تطبيق العميل.

عليك استخدام Context التي تم إرجاعها من خلال SandboxedSdkProvider.getContext() لتوفير مساحة التخزين. لا نضمن لك عمل واجهة برمجة التطبيقات الخاصة بمساحة تخزين الملفات مع أي مثيل آخر من عناصر 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، تعرض واجهة برمجة تطبيقات واحدة فقط مسارًا إلى مساحة التخزين لكل حزمة SDK: Context#getDataDir().

في نظام التشغيل Android 14، تعرض جميع واجهات برمجة التطبيقات لمساحة التخزين الداخلية في العنصر Context مسار تخزين لكل حزمة SDK. قد تحتاج إلى تفعيل هذه الميزة عن طريق تشغيل أمر adb التالي:

adb shell device_config put adservices sdksandbox_customized_sdk_context_enabled true

قراءة SharedPreferences للعميل

يمكن لتطبيقات العملاء اختيار مشاركة مجموعة من المفاتيح من خلال SharedPreferences مع SdkSandbox. يمكن لحِزم SDK قراءة البيانات التي تمت مزامنتها من تطبيق العميل باستخدام SdkSanboxController#getClientSharedPreferences() API. واجهة برمجة التطبيقات SharedPreferences التي تعرضها واجهة برمجة التطبيقات هذه مخصّصة للقراءة فقط. يجب عدم الكتابة إليه.

الوصول إلى المعرِّف الإعلاني المقدَّم من "خدمات Google Play"

إذا كانت حزمة تطوير البرامج (SDK) بحاجة إلى الوصول إلى المعرِّف الإعلاني الذي تقدِّمه "خدمات Google Play":

  • يُرجى تقديم بيان عن إذن "android.permission.ACCESS_ADSERVICES_AD_ID" في ملف بيان حزمة تطوير البرامج (SDK).
  • استخدِم AdIdManager#getAdId() لاسترداد القيمة بشكل غير متزامن.

الوصول إلى معرّف مجموعة التطبيقات المقدَّم من "خدمات Google Play"

إذا كانت حزمة تطوير البرامج (SDK) بحاجة إلى الوصول إلى معرّف مجموعة التطبيقات الذي توفّره "خدمات Google Play":

  • استخدِم 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. حدِّد فئة معاودة الاتصال من خلال تنفيذ OutcomeReceiver للتفاعل مع حزمة تطوير البرامج (SDK) في وقت التشغيل بعد تحميلها. في المثال التالي، يستخدم العميل معاودة الاتصال للانتظار حتى يتم تحميل SDK بنجاح، ثم يحاول عرض عرض ويب من حزمة SDK. يتم تحديد عمليات معاودة الاستدعاء لاحقًا في هذه الخطوة.

    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. يمكن للتطبيق اختيار مشاركة بعض مفاتيح SharedPreferences التلقائية مع وضع الحماية. ويمكنهم إجراء ذلك من خلال استدعاء طريقة SdkSandboxManager#addSyncedSharedPreferencesKeys(Set<String>keys) على أي مثيل لمدير SdkSandbox. بعد أن يرسِل التطبيق إشعارًا إلى SdkSandboxManager بشأن المفاتيح التي يجب مزامنتها، سيزامن SdkSandboxManager قيم هذه المفاتيح في وضع الحماية وأجهزة SDK، ثم يمكنه قراءتها باستخدام SdkSandboxController#getClientSharedPreferences. راجع Reading Client's SharedPreferences للحصول على مزيد من المعلومات.

    لا تستمر مجموعة المفاتيح التي تتم مزامنتها خلال إعادة تشغيل التطبيق ويتم محو البيانات التي تمت مزامنتها مع وضع الحماية عند إعادة تشغيل وضع الحماية. وبالتالي، من الضروري أن يبدأ التطبيق عملية المزامنة من خلال استدعاء addSyncedSharedPreferencesKeys في كل مرة يتم فيها بدء تشغيل التطبيق.

    يمكنك تعديل مجموعة المفاتيح التي تتم مزامنتها عن طريق طلب الرمز SdkSandboxManager#removeSyncedSharedPreferencesKeys(Set<String>keys) لإزالة المفاتيح. لعرض المجموعة الحالية من المفاتيح التي تتم مزامنتها، استخدِم SdkSandboxManager#getSyncedSharedPreferencesKeys().

    نقترح الاحتفاظ بمجموعة المفاتيح صغيرة قدر الإمكان واستخدامها فقط عند الضرورة. إذا كنت تريد تمرير المعلومات إلى حِزم تطوير البرامج (SDK) لأغراض عامة، يُرجى التواصل مع SDK مباشرةً باستخدام واجهة SandboxedSdk. أحد السيناريوهات المحتملة لاستخدام واجهات برمجة التطبيقات هذه هو إذا كان التطبيق يستخدم حزمة تطوير البرامج (SDK) لمنصّة إدارة الموافقة (CMP)، ستهتم حِزم تطوير البرامج (SDK) داخل وضع الحماية بقراءة مخازن حزمة تطوير البرامج CPM 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"));
    }
    
    
  7. لمعالجة حالة انتهاء عملية وضع الحماية لحزمة 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);
              }
          }
    

    لتسجيل معاودة الاتصال هذه لتلقي معلومات حول وقت إنهاء وضع الحماية لحزمة SDK، أضف السطر التالي في أي وقت:

    Kotlin

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

    Java

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

    ونظرًا لفقدان حالة وضع الحماية عند إنهاء العملية، قد لا تعمل طرق العرض التي تم عرضها عن بُعد بواسطة حزمة SDK بشكل صحيح. لمواصلة التفاعل مع حِزم SDK، يجب تحميل طرق العرض هذه مرة أخرى لبدء عملية وضع حماية جديدة.

  8. أضِف عنصر الاعتماد إلى وحدة حزمة تطوير البرامج (SDK) في build.gradle في تطبيق العميل:

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

اختبار تطبيقاتك

لتشغيل تطبيق العميل، يجب تثبيت تطبيق SDK وتطبيق العميل على جهاز الاختبار باستخدام Android Studio أو سطر الأوامر.

النشر من خلال "استوديو Android"

عند النشر من خلال "استوديو Android"، أكمل الخطوات التالية:

  1. افتح مشروع "استوديو Android" لتطبيق عميلك.
  2. انتقِل إلى تشغيل > تعديل عمليات الضبط. تظهر نافذة إعداد التشغيل/تصحيح الأخطاء.
  3. ضمن خيارات الإطلاق، اضبط إطلاق على نشاط محدّد.
  4. انقر على قائمة الخيارات الإضافية بجانب "النشاط" واختَر النشاط الرئيسي لعميلك.
  5. انقر على تطبيق ثم حسنًا.
  6. انقر على تشغيل لتثبيت تطبيق العميل وحزمة تطوير البرامج (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"، انقر على تشغيل > تعديل عمليات الضبط. ستظهر نافذة التشغيل/تصحيح الأخطاء.

  4. ضمن خيارات التثبيت، اضبط نشر على حزمة APK التلقائية.

  5. انقر على تطبيق ثم حسنًا.

  6. انقر على Run (تشغيل) لتثبيت حزمة APK على جهاز الاختبار.

تصحيح أخطاء تطبيقاتك

لتصحيح أخطاء تطبيق العميل، انقر على الزر تصحيح الأخطاء في "استوديو Android".

لتصحيح أخطاء تطبيق SDK، انتقل إلى Run > Attach to Process (تشغيل > إرفاق بالعملية)، حيث تظهر لك شاشة منبثقة (كما هو موضح أدناه). ضَع علامة في المربّع عرض جميع العمليات. في القائمة التي تظهر، ابحث عن عملية تسمى CLIENT_APP_PROCESS_sdk_sandbox. حدد هذا الخيار وأضف نقاط التوقف في التعليمات البرمجية لتطبيق SDK لبدء تصحيح أخطاء SDK.

تظهر عملية تطبيق حزمة تطوير البرامج (SDK) في عرض على شكل قائمة بالقرب من أسفل مربّع الحوار.
شاشة اختيار العملية، التي يمكنك من خلالها اختيار تطبيق حزمة تطوير البرامج (SDK) لتصحيح الأخطاء فيه.

بدء وقت تشغيل حزمة SDK وإيقافه من سطر الأوامر

لبدء عملية تشغيل حزمة تطوير البرامج (SDK) في تطبيقك، استخدِم أمر PowerShell التالي:

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"، يمكنك الاطّلاع على ملاحظات الإصدار.

عيّنات تعليمات برمجية

يحتوي مستودع واجهات برمجة التطبيقات (API) الخاصة بـ "وقت تشغيل SDK" و"الحفاظ على الخصوصية" على GitHub على مجموعة من المشاريع الفردية الخاصة بـ "استوديو Android" لمساعدتك في البدء، بما في ذلك نماذج توضّح كيفية إعداد "وقت تشغيل SDK" واستدعاءه.

الإبلاغ عن الأخطاء والمشاكل

ملاحظاتك هي جزء مهم من "مبادرة حماية الخصوصية" على Android. يُرجى إعلامنا بأي مشاكل تصادفها أو أفكارًا لتحسين "مبادرة حماية الخصوصية" على Android.