مواضيع متقدمة

هذه الأقسام مخصّصة للرجوع إليها وليس مطلوبًا قراءتها منверхувниз.

استخدام واجهات برمجة تطبيقات الإطار:

سيتم تضمين واجهات برمجة التطبيقات هذه في حزمة SDK لتوفير واجهة برمجة تطبيقات أكثر اتساقًا (مثل تجنُّب عناصر UserHandle)، ولكن يمكنك في الوقت الحالي استدعاء هذه الواجهات مباشرةً.

إنّ عملية التنفيذ بسيطة: إذا كان بإمكانك التفاعل، يمكنك المتابعة. إذا لم يكن الأمر كذلك، يمكنك طلب ذلك، ثم عرض طلب/إعلان بانر/ملاحظة توضيحية/غير ذلك للمستخدم. إذا وافق العميل على الانتقال إلى "الإعدادات"، أنشئ طلب الإجراء واستخدِم Context#startActivity لإرسال المستخدم إلى هناك. يمكنك استخدام البث لرصد حالات تغيُّر هذه الإمكانية، أو التحقّق مرة أخرى عندما يعود المستخدم.

لاختبار ذلك، عليك فتح TestDPC في ملف العمل، والانتقال إلى أسفل الشاشة واختيار إضافة اسم الحزمة إلى القائمة المسموح بها للتطبيقات المتصلة. ويؤدي ذلك إلى محاكاة عملية "إضافة التطبيق إلى القائمة المسموح بها" التي يجريها المشرف.

مسرد المصطلحات

يحدّد هذا القسم المصطلحات الرئيسية المتعلّقة بتطوير المحتوى على جميع الملفات التجارية.

ضبط الإعدادات على مستوى الملفات الشخصية

تجمع إعدادات العرض في الملف الشخصي للعمل معًا فئات مقدّمي خدمات العرض في الملف الشخصي للعمل ذات الصلة، وتقدّم إعدادات عامة للميزات التي تتوفّر في الملف الشخصي للعمل. عادةً ما يكون هناك تعليق توضيحي واحد @CrossProfileConfiguration لكل قاعدة بيانات، ولكن قد يكون هناك عدة تعليقات توضيحية في بعض التطبيقات المعقدة.

أداة ربط الملفات الشخصية

يدير "الموصّل" عمليات الربط بين الملفات الشخصية. عادةً ما يشير كل نوع من أنواع الملفات الشخصية المتداخلة إلى "موصّل" معيّن. يجب أن يستخدم كل نوع من أنواع الملفات الشخصية المتداخلة في ملف واحد الإعدادات الموصّل نفسه.

فئة مقدّم خدمة العرض في الملف الشخصي

تجمع فئة مقدّمي خدمات "العرض في الملف الشخصي للعمل" أنواع "العرض في الملف الشخصي للعمل" ذات الصلة معًا.

Mediator

يُعدّ الوسيط وسيطًا بين الرموز البرمجية العالية المستوى والمنخفضة المستوى، حيث يوزّع المكالمات على الملفات الشخصية الصحيحة ويدمج النتائج. هذا هو الرمز البرمجي الوحيد الذي يجب أن يكون مدركًا للملف الشخصي. هذا مفهوم معماري وليس شيئًا مضمّنًا في IDE.

نوع الملف الشخصي للعمل

نوع الملف الشخصي المتعدّد هو فئة أو واجهة تحتوي على طرق مُشارَك فيها تعليقات توضيحية @CrossProfile. لا يجب أن يكون الرمز البرمجي في هذا النوع مدركًا للملف الشخصي، ويجب أن يستند في العادة إلى بياناته المحلية فقط.

أنواع الملفات الشخصية

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

معرّف الملف التجاري

فئة تمثّل نوع الملف الشخصي (شخصي أو عمل) سيتم عرض هذه العناصر من خلال طُرق يتم تنفيذها على ملفات تعريف متعددة ويمكن استخدامها لتشغيل المزيد من الرموز البرمجية على هذه الملفات. ويمكن تسلسلها إلى int لسهولة التخزين.

يوضّح هذا الدليل البنى المقترَحة لإنشاء وظائف فعّالة و قابلة للصيانة على جميع الملفات الشخصية في تطبيق Android.

تحويل CrossProfileConnector إلى عنصر فريد

يجب استخدام نسخة افتراضية واحدة فقط طوال دورة حياة تطبيقك، وإلا ستُنشئ اتصالات متوازية. ويمكن إجراء ذلك إما باستخدام إطار عمل حقن التبعية مثل Dagger، أو باستخدام نمط Singleton الكلاسيكي، إما في فئة جديدة أو في فئة حالية.

يمكنك إدخال مثيل Profile الذي تم إنشاؤه أو تمريره إلى صفك عند إجراء المكالمة، بدلاً من إنشائه في الطريقة.

يتيح لك ذلك تمرير مثيل FakeProfile الذي تم إنشاؤه تلقائيًا في اختبارات الوحدات لاحقًا.

راجِع نمط التوسّط.

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

بهذه الطريقة، لن تضطر إلى إجبار كل متصل على معرفة كيفية إجراء مكالمة على مستوى جميع الملفات التجارية، وسيصبح ذلك مجرد جزء من واجهة برمجة التطبيقات.

ننصحك بالتفكير في إضافة تعليق توضيحي لطريقة واجهة على أنّها @CrossProfile بدلاً من ذلك لتجنُّب الحاجة إلى عرض فئات التنفيذ في موفِّر.

يعمل هذا الأسلوب بشكل جيد مع إطارات عمل حقن التبعية.

إذا كنت تتلقّى أي بيانات من مكالمة بين الملفات الشخصية، ننصحك بالتفكير في إضافة حقل يشير إلى الملف الشخصي الذي نشأت منه هذه البيانات.

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

العرض في الملف الشخصي للعمل

يوضّح هذا القسم كيفية إنشاء تفاعلات بين الملفات التجارية.

الملفات الشخصية الأساسية

تحتوي معظم الطلبات الواردة في الأمثلة الواردة في هذا المستند على تعليمات واضحة بشأن الملفات الشخصية التي سيتم تنفيذها عليها، بما في ذلك الملف الشخصي للعمل والملف الشخصي والملف الشخصي لكلاهما.

من الناحية العملية، بالنسبة إلى التطبيقات التي توفّر تجربة مدمجة في ملف شخصي واحد فقط، من المحتمل أن تفضّل أن يعتمد هذا القرار على الملف الشخصي الذي تستخدمه، لذلك هناك methods مماثلة وسهلة تأخذ ذلك في الاعتبار أيضًا، لتجنّب تملّؤه بشروط الملف الشخصي التي تتضمّن "إذا كان" أو "غير ذلك".

عند إنشاء مثيل الموصّل، يمكنك تحديد نوع الملف الشخصي الذي هو "الرئيسي" (مثل "العمل"). يتيح ذلك خيارات إضافية، مثل ما يلي:

profileCalendarDatabase.primary().getEvents();

profileCalendarDatabase.secondary().getEvents();

// Runs on all profiles if running on the primary, or just
// on the current profile if running on the secondary.
profileCalendarDatabase.suppliers().getEvents();

أنواع الملفات الشخصية

تُعرف الفئات الواجهات التي تحتوي على طريقة تمّت إضافة تعليق توضيحي إليها @CrossProfile باسم أنواع الملفات التجارية المتعددة.

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

مثال على نوع العرض في الملف الشخصي للعمل:

public class Calculator {
  @CrossProfile
  public int add(int a, int b) {
    return a + b;
  }
}

التعليق التوضيحي للصف

لتقديم أفضل واجهة برمجة تطبيقات، عليك تحديد الموصّل لكل نوع من أنواع الملفات الشخصية المتداخلة، على النحو التالي:

@CrossProfile(connector=MyProfileConnector.class)
public class Calculator {
  @CrossProfile
  public int add(int a, int b) {
    return a + b;
  }
}

هذا الإجراء اختياري، ولكنه يعني أنّ واجهة برمجة التطبيقات التي تم إنشاؤها ستكون أكثر تحديدًا بشأن الأنواع وأكثر صرامة في التحقّق من وقت الترجمة.

واجهات

من خلال وضع تعليق توضيحي على الطرق في واجهة على أنّها @CrossProfile، أنت تشير إلى أنّه يمكن أن يكون هناك بعض عمليات تنفيذ لهذه الطريقة التي يجب أن يكون بالإمكان الوصول إليها في جميع الملفات الشخصية.

يمكنك عرض أيّ عملية تنفيذ لواجهة "ملف شخصي متعدد" في موفّر ملف شخصي متعدد، وهذا يعني أنّه يجب أن يكون بالإمكان الوصول إلى عملية التنفيذ هذه في جميع الملفات الشخصية. لست بحاجة إلى إضافة تعليقات توضيحية لفئات التنفيذ.

موفّرو "العرض في الملف الشخصي للعمل"

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

الشركة المصنِّعة

يجب أن يتضمّن مقدّم الخدمة عنصرًا منشئًا متاحًا للجميع لا يقبل أي وسيطات أو وسيطة واحدةContext.

طرق موفّري المحتوى

يجب ألا تأخذ طُرق موفّر البيانات أي وسيطات أو وسيطة Context واحدة.

حقن التبعية

إذا كنت تستخدم إطار عمل لتوفير التبعية مثل Dagger لإدارة الملحقات، ننصحك بطلب من هذا الإطار إنشاء أنواع الملفات الشخصية كما تفعل عادةً، ثم إدخال هذه الأنواع في فئة مقدّم الخدمة. ويمكن بعد ذلك لطرق @CrossProfileProvider عرض تلك المثيلات المُدرَجة.

أداة ربط الملفات الشخصية

يجب أن تحتوي كلّ عملية إعداد للملفّ الشخصي على أداة ربط ملفات شخصية واحدة، وهي مسؤولة عن إدارة عملية الربط بالملفّ الشخصي الآخر.

أداة ربط الملف الشخصي التلقائي

إذا كان هناك إعداد واحد فقط لملف شخصي في قاعدة بيانات، يمكنك تجنُّب إنشاء "موصِّل الملف الشخصي" الخاص بك واستخدام com.google.android.enterprise.connectedapps.CrossProfileConnector. هذا هو القيمة التلقائية المستخدَمة إذا لم يتم تحديد أي قيمة.

عند إنشاء "موصّل الملفات التجارية على مستوى عدّة نطاقات"، يمكنك تحديد بعض الخيارات في أداة الإنشاء:

  • Scheduled Executor Service

    إذا كنت تريد التحكّم في سلاسل المحادثات التي أنشأتها حزمة SDK، استخدِم #setScheduledExecutorService().

  • مجلد

    إذا كانت لديك احتياجات محدّدة بشأن ربط الملف الشخصي، استخدِم #setBinder. من المحتمل أن يتم استخدام هذه القيمة فقط من قِبل أدوات التحكّم في سياسة الجهاز.

موصّل الملف الشخصي المخصّص

ستحتاج إلى موصِّل ملف شخصي مخصّص لتتمكّن من ضبط بعض الإعدادات (باستخدام CustomProfileConnector)، وستحتاج إلى موصِّل واحد إذا كنت بحاجة إلى عدة موصِّلات في قاعدة بيانات واحدة (على سبيل المثال، إذا كانت لديك عمليات متعددة، ننصح باستخدام موصِّل واحد لكل عملية).

عند إنشاء ProfileConnector، من المفترض أن يظهر على النحو التالي:

@GeneratedProfileConnector
public interface MyProfileConnector extends ProfileConnector {
  public static MyProfileConnector create(Context context) {
    // Configuration can be specified on the builder
    return GeneratedMyProfileConnector.builder(context).build();
  }
}
  • serviceClassName

    لتغيير اسم الخدمة التي تم إنشاؤها (الذي يجب الإشارة إليه في AndroidManifest.xml)، استخدِم serviceClassName=.

  • primaryProfile

    لتحديد الملف الشخصي الأساسي، استخدِم primaryProfile.

  • availabilityRestrictions

    لتغيير القيود التي تفرضها حزمة تطوير البرامج (SDK) على عمليات الربط ومدى توافره، استخدِم availabilityRestrictions.

وحدات التحكّم بسياسات الأجهزة

إذا كان تطبيقك وحدة تحكّم في سياسة الجهاز، يجب تحديد مثيل لمحاولة DpcProfileBinder تشير إلى DeviceAdminReceiver.

في حال كنت بصدد تنفيذ أداة ربط الملف التجاري الخاصة بك:

@GeneratedProfileConnector
public interface DpcProfileConnector extends ProfileConnector {
  public static DpcProfileConnector get(Context context) {
    return GeneratedDpcProfileConnector.builder(context).setBinder(new
DpcProfileBinder(new ComponentName("com.google.testdpc",
"AdminReceiver"))).build();
  }
}

أو باستخدام الرمز التلقائي CrossProfileConnector:

CrossProfileConnector connector =
CrossProfileConnector.builder(context).setBinder(new DpcProfileBinder(new
ComponentName("com.google.testdpc", "AdminReceiver"))).build();

ضبط الإعدادات على مستوى الملفات الشخصية

يُستخدَم التعليق التوضيحي @CrossProfileConfiguration لربط جميع أنواع الملفات الشخصية معًا باستخدام موصّل من أجل إرسال طلبات استدعاء الطريقة بشكل صحيح. للقيام بذلك، تتم إضافة تعليق توضيحي إلى فئة باستخدام الرمز @CrossProfileConfiguration الذي يشير إلى كل مقدّم خدمة، على النحو التالي:

@CrossProfileConfiguration(providers = {TestProvider.class})
public abstract class TestApplication {
}

سيؤدي ذلك إلى التحقّق من أنّه لكل أنواع الملفات الشخصية المختلفة، لديهم إما موصِّل الملف الشخصي نفسه أو لم يتم تحديد أي موصِّل.

  • serviceSuperclass

    ستستخدم الخدمة التي تم إنشاؤها تلقائيًا android.app.Service كأحد الفئات العليا. إذا كنت بحاجة إلى فئة مختلفة (يجب أن تكون فئة فرعية من android.app.Service) لتكون الفئة الرئيسية، حدِّد serviceSuperclass=.

  • serviceClass

    في حال تحديده، لن يتم إنشاء أي خدمة. يجب أن يتطابق مع serviceClassName في أداة ربط الملف الشخصي التي تستخدمها. يجب أن تُرسِل خدمتك المخصّصة المكالمات باستخدام فئة _Dispatcher التي تم إنشاؤها على النحو التالي:

public final class TestProfileConnector_Service extends Service {
  private Stub binder = new Stub() {
    private final TestProfileConnector_Service_Dispatcher dispatcher = new
TestProfileConnector_Service_Dispatcher();

    @Override
    public void prepareCall(long callId, int blockId, int numBytes, byte[] params)
{
      dispatcher.prepareCall(callId, blockId, numBytes, params);
    }

    @Override
    public byte[] call(long callId, int blockId, long crossProfileTypeIdentifier,
int methodIdentifier, byte[] params,
    ICrossProfileCallback callback) {
      return dispatcher.call(callId, blockId, crossProfileTypeIdentifier,
methodIdentifier, params, callback);
    }

    @Override
    public byte[] fetchResponse(long callId, int blockId) {
      return dispatcher.fetchResponse(callId, blockId);
  };

  @Override
  public Binder onBind(Intent intent) {
    return binder;
  }
}

يمكن استخدام هذا الإجراء إذا كنت بحاجة إلى تنفيذ إجراءات إضافية قبل أو بعد إجراء مكالمة بين حسابات متعددة.

  • الموصِّل

    إذا كنت تستخدم موصّلًا غير الموصّل التلقائي CrossProfileConnector، يجب تحديده باستخدام connector=.

مستوى الرؤية

يجب أن يكون بإمكان كل جزء من تطبيقك الذي يتفاعل مع الملفات التجارية الأخرى الاطّلاع على موصِّل الملفات التجارية.

يجب أن يتمكّن صف @CrossProfileConfiguration المُشارَك فيه بشكل توضيحي من الاطّلاع على كل مقدّم خدمة مستخدَم في تطبيقك.

طلبات البيانات المتزامنة

تتوافق حزمة تطوير البرامج (SDK) لتطبيقات Connected Apps مع طلبات البيانات المتزامنة (المحظورة) في الحالات التي لا يمكن تجنُّبها. ومع ذلك، هناك عدد من العيوب لاستخدام هذه المكالمات (مثل احتمال حظر المكالمات لفترة طويلة)، لذا ننصح بتجنُّب المكالمات المتزامنة كلما أمكن ذلك. لاستخدام المكالمات غير المتزامنة، اطّلِع على المكالمات غير المتزامنة .

حاملو حسابات الربط

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

لإضافة حامل اتصال، يمكنك استدعاء ProfileConnector#addConnectionHolder(Object) مع أي عنصر (قد يكون مثيل العنصر الذي يجري مكالمة بين الملفات التجارية). سيؤدي ذلك إلى تسجيل أنّ هذا العنصر يستخدم الاتصال وسيحاول إجراء عملية ربط. يجب استدعاء هذه الوظيفة قبل إجراء أيّ مكالمات متزامنة. هذه مكالمة غير حظر، لذا من الممكن أنّ الاتصال لن يكون جاهزًا (أو قد لا يكون ممكنًا) بحلول وقت إجرائه، وفي هذه الحالة ينطبق السلوك المعتاد لمعالجة الأخطاء.

إذا لم تكن لديك الأذونات المناسبة على جميع الملفات الشخصية عند الاتصال ProfileConnector#addConnectionHolder(Object) أو لم يكن متاحًا أي ملف شخصي للربط، لن يتم عرض أي خطأ، ولكن لن يتم أبدًا استدعاء دالة الاستدعاء المرتبطة. إذا تم منح الإذن لاحقًا أو أصبح الملف الشخصي الآخر متاحًا، سيتم إجراء الاتصال في ذلك الحين وسيتم استدعاء طلب إعادة الاتصال.

بدلاً من ذلك، ProfileConnector#connect(Object) هي طريقة حظر ستضيف العنصر كحامل اتصال، وسيتم إنشاء اتصال أو سيتم طرح UnavailableProfileException. لا يمكن استدعاء هذه الطريقة من سلسلة مهام واجهة المستخدم.

تؤدي المكالمات إلى ProfileConnector#connect(Object) والعنصر المشابه ProfileConnector#connect إلى عرض عناصر ذات إغلاق تلقائي مما يؤدي إلى إزالة حامل الاتصال تلقائيًا بعد الإغلاق. يتيح ذلك الاستخدامات التالية:

try (ProfileConnectionHolder p = connector.connect()) {
  // Use the connection
}

بعد الانتهاء من إجراء المكالمات المتزامنة، عليك الاتصال بالرقم ProfileConnector#removeConnectionHolder(Object). بعد إزالة كل مالكي عملية الربط ، سيتم إغلاق عملية الربط.

إمكانية الاتصال

يمكن استخدام مستمع الاتصال للحصول على إشعار عند تغيُّر حالة الاتصال، ويمكن استخدام connector.utils().isConnected لتحديد ما إذا كان هناك اتصال. على سبيل المثال:

// Only use this if using synchronous calls instead of Futures.
crossProfileConnector.connect(this);
crossProfileConnector.registerConnectionListener(() -> {
  if (crossProfileConnector.utils().isConnected()) {
    // Make cross-profile calls.
  }
});

المكالمات غير المتزامنة

يجب تصنيف كل طريقة معروضة على مستوى ملف التعريف على أنّها حظر (متزامنة) أو غير حظر (غير متزامنة). يتم وضع علامة على أي طريقة تُرجع نوع بيانات غير متزامن (مثل ListenableFuture) أو تقبل مَعلمة callback على أنّها غير محظورة. ويتم وضع علامة "حظر" على جميع الطرق الأخرى.

يُنصح باستخدام طلبات غير متزامنة. إذا كان عليك استخدام المكالمات المتزامنة، اطّلِع على المكالمات المتزامنة.

عمليات معاودة الاتصال

إنّ النوع الأكثر أساسية من الاستدعاءات غير المحظورة هو طريقة فارغة تقبل واجهة واحدة من مَعلماتها تحتوي على طريقة يتم استدعاؤها مع النتيجة. لكي تعمل هذه الواجهات مع حزمة SDK، يجب أن تكون الواجهة مُشارَكًا فيها تعليقات توضيحية @CrossProfileCallback. على سبيل المثال:

@CrossProfileCallback
public interface InstallationCompleteListener {
  void installationComplete(int state);
}

ويمكن بعد ذلك استخدام هذه الواجهة كمَعلمة في @CrossProfileطريقة annotated وإجراؤها كالمعتاد. على سبيل المثال:

@CrossProfile
public void install(String filename, InstallationCompleteListener callback) {
  // Do something on a separate thread and then:
  callback.installationComplete(1);
}

// In the mediator
profileInstaller.work().install(filename, (status) -> {
  // Deal with callback
}, (exception) -> {
  // Deal with possibility of profile unavailability
});

إذا كانت هذه الواجهة تحتوي على طريقة واحدة تأخذ مَعلمة واحدة أو لا تأخذ أي مَعلمة، يمكن استخدامها أيضًا في طلبات البيانات إلى ملفات تعريف متعددة في آنٍ واحد.

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

الطرق المتزامنة مع وظائف ردّ الاتصال

من الميزات غير المعتادة لاستخدام وظائف الاستدعاء مع حزمة SDK هي أنّه يمكنك كتابة طريقة متزامنة تستخدم وظيفة استدعاء من الناحية الفنية:

public void install(InstallationCompleteListener callback) {
  callback.installationComplete(1);
}

في هذه الحالة، تكون الطريقة متزامنة في الواقع، على الرغم من طلب الاستدعاء. سيتم تنفيذ هذا الرمز البرمجي بشكل صحيح:

System.out.println("This prints first");
installer.install(() -> {
        System.out.println("This prints second");
});
System.out.println("This prints third");

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

طلبات معاودة الاتصال البسيطة

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

يمكنك فرض بقاء واجهة ردّ الاتصال من خلال تحديد simple=true في التعليق التوضيحي @CrossProfileCallback.

يمكن استخدام طلبات إعادة الاتصال البسيطة مع طرق مختلفة مثل .both() و.suppliers() وغيرها.

حاملو حسابات الربط

عند إجراء مكالمة غير متزامنة (باستخدام وظائف الاستدعاء أو المستقبلات)، تتم إضافة ملف تعريف اتصال عند إجراء المكالمة وإزالته عند تمرير استثناء أو قيمة.

إذا كنت تتوقّع تمرير أكثر من نتيجة واحدة باستخدام طلب معاودة الاتصال، عليك إضافة طلب معاودة الاتصال يدويًا كحامل اتصال:

MyCallback b = //...
connector.addConnectionHolder(b);

  profileMyClass.other().registerListener(b);

  // Now the connection will be held open indefinitely, once finished:
  connector.removeConnectionHolder(b);

يمكن أيضًا استخدام هذا مع كتلة try-with-resources:

MyCallback b = //...
try (ProfileConnectionHolder p = connector.addConnectionHolder(b)) {
  profileMyClass.other().registerListener(b);

  // Other things running while we expect results
}

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

connector.removeConnectionHolder(myCallback);
connector.removeConnectionHolder(future);

لمزيد من المعلومات، يُرجى الاطّلاع على "أصحاب عمليات الربط".

العقد الآجل

تتوفّر أيضًا العقود المستقبلية بشكلٍ أصلي في حزمة تطوير البرامج (SDK). النوع الوحيد المتوافق تلقائيًا للمستقبل هو ListenableFuture، ولكن يمكن استخدام أنواع المستقبل المخصّصة. لاستخدام العقود المستقبلية، ما عليك سوى تحديد نوع Future متوافق كنوع الإرجاع لطريقة عبر الملف الشخصي، ثم استخدامه كالمعتاد.

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

سلاسل المحادثات

لا تحظر نتيجة طلب مستقبلي أو طلب إعادة اتصال على مستوى جميع الملفات الشخصية في المحادثة الرئيسية. في بعض الحالات، سيؤدي ذلك إلى حظر الرمز المبرمَج إلى أجل غير مسمى. ويعود السبب في ذلك إلى أنّه يتم أيضًا إنشاء اتصال بالملف الشخصي الآخر في سلسلة المحادثات الرئيسية، ولن يحدث ذلك أبدًا إذا تم حظره في انتظار نتيجة على مستوى الملفات الشخصية.

مدى التوفّر

يمكن استخدام مستمع مدى التوفّر للاطّلاع على آخر المعلومات عند تغيُّر حالة مدى التوفّر، ويمكن استخدام connector.utils().isAvailable لتحديد ما إذا كان هناك ملف شخصي آخر متاح للاستخدام. على سبيل المثال:

crossProfileConnector.registerAvailabilityListener(() -> {
  if (crossProfileConnector.utils().isAvailable()) {
    // Show cross-profile content
  } else {
    // Hide cross-profile content
  }
});

حاملو حسابات الربط

مالكي الاتصالات هم عناصر عشوائية يتم تسجيلها على أنّها مهتمة بإنشاء اتصال بين الملفات الشخصية والحفاظ عليه.

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

يمكن أيضًا إضافة مالكي عمليات الربط وإزالتهم يدويًا للتحكّم بشكلٍ أكبر في عملية الربط. يمكن إضافة مالكي حسابات الربط باستخدام connector.addConnectionHolder وإزالتهم باستخدام connector.removeConnectionHolder.

عند إضافة حامل اتصال واحد على الأقل، ستحاول حزمة SDK الحفاظ على الاتصال. عندما لا يكون هناك أيّ جهات اتصال تمت إضافتها، يمكن إغلاق عملية الاتصال.

يجب الاحتفاظ بمرجع لأي حامل اتصال تضيفه، وإزالته عندما لا يعود ذا صلة.

المكالمات المتزامنة

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

الطلبات غير المتزامنة

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

خطأ أثناء المعالجة

بشكلٍ تلقائي، ستؤدي أيّ مكالمات يتم إجراؤها إلى الملف الشخصي الآخر عندما لا يكون متاحًا إلى طرح UnavailableProfileException (أو إرساله إلى Future أو استدعاء خطأ لمكالمة غير متزامنة).

لتجنُّب ذلك، يمكن للمطوّرين استخدام #both() أو #suppliers() وكتابة الرمز الخاص بهم للتعامل مع أي عدد من الإدخالات في القائمة الناتجة (سيكون هذا الرقم 1 إذا كان الملف الشخصي الآخر غير متاح، أو 2 إذا كان متاحًا).

الاستثناءات

سيتم توسيع نطاق أي استثناءات غير محدَّدة تحدث بعد طلب إلى الملف الشخصي الحالي كالمعتاد. وينطبق ذلك بغض النظر عن الطريقة المستخدَمة لإجراء المكالمة (#current() أو #personal أو #both أو غير ذلك).

إنّ الاستثناءات غير المحدَّدة التي تحدث بعد طلب إلى الملف الشخصي الآخر ستؤدي إلى طرح ProfileRuntimeException مع الاستثناء الأصلي كسبب. وينطبق ذلك بغض النظر عن الطريقة المستخدَمة لإجراء المكالمة (#other() #personal و#both وما إلى ذلك).

ifAvailable

كبديل لرصد حالات UnavailableProfileException والتعامل معها، يمكنك استخدام طريقة .ifAvailable() لتوفير قيمة تلقائية ستتم إعادتها بدلاً من طرح UnavailableProfileException.

على سبيل المثال:

profileNotesDatabase.other().ifAvailable().getNumberOfNotes(/* defaultValue= */ 0);

الاختبار

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

نوفّر بيانات وهمية للموصّل والأنواع التي يمكن استخدامها في الاختبارات.

أولاً، أضِف التبعيات المتعلقة بالاختبار:

  testAnnotationProcessor
'com.google.android.enterprise.connectedapps:connectedapps-processor:1.1.2'
  testCompileOnly
'com.google.android.enterprise.connectedapps:connectedapps-testing-annotations:1.1.2'
  testImplementation
'com.google.android.enterprise.connectedapps:connectedapps-testing:1.1.2'

بعد ذلك، أضِف تعليقًا توضيحيًا إلى فئة الاختبار باستخدام @CrossProfileTest، مع تحديد @CrossProfileConfiguration الفئة التي تمّت إضافة تعليق توضيحي إليها والتي سيتم اختبارها:

@CrossProfileTest(configuration = MyApplication.class)
@RunWith(RobolectricTestRunner.class)
public class NotesMediatorTest {

}

سيؤدي ذلك إلى إنشاء بيانات زائفة لجميع الأنواع والموصّلات المستخدَمة في الإعداد.

أنشئ نُسخًا من هذه العناصر المزيّفة في اختبارك:

private final FakeCrossProfileConnector connector = new
FakeCrossProfileConnector();
private final NotesManager personalNotesManager = new NotesManager(); //
real/mock/fake
private final NotesManager workNotesManager = new NotesManager(); // real/mock/fake

private final FakeProfileNotesManager profileNotesManager =
  FakeProfileNotesManager.builder()
    .personal(personalNotesManager)
    .work(workNotesManager)
    .connector(connector)
    .build();

اضبط حالة الملف الشخصي:

connector.setRunningOnProfile(PERSONAL);
connector.createWorkProfile();
connector.turnOffWorkProfile();

نقْل الموصِّل المزيّف وفئة الملف الشخصي المتعدّد إلى الرمز البرمجي الذي يتم اختباره، ثم بدِّل المكالمات.

سيتم توجيه المكالمات إلى الهدف الصحيح، وسيتم طرح استثناءات عند إجراء مكالمات إلى الملفات التجارية غير المتصلة أو غير المتوفّرة.

الأنواع المشمولة

تتوفّر الأنواع التالية بدون أي جهد إضافي من جانبك. ويمكن استخدامها كوسيطات أو أنواع عرض لجميع طلبات البيانات على مستوى جميع الملفات التجارية.

  • الأشكال الأساسية (byte وshort وint وlong وfloat وdouble وchar boolean)
  • الأشكال الأساسية المربّعة (java.lang.Byte وjava.lang.Short وjava.lang.Integer java.lang.Long وjava.lang.Float وjava.lang.Double java.lang.Character وjava.lang.Boolean وjava.lang.Void)
  • java.lang.String،
  • أيّ شيء ينفذ android.os.Parcelable
  • أيّ شيء ينفذ java.io.Serializable
  • الصفائف غير الأساسية أحادية البُعد
  • java.util.Optional،
  • java.util.Collection،
  • java.util.List،
  • java.util.Map،
  • java.util.Set،
  • android.util.Pair،
  • com.google.common.collect.ImmutableMap.

يمكن أن تحتوي أي أنواع عامة متوافقة (مثل java.util.Collection) على أي نوع متوافق كمَعلمة النوع. على سبيل المثال:

java.util.Collection<java.util.Map<java.lang.String,MySerializableType[]>> هو نوع صالح.

العقد الآجل

لا يمكن استخدام الأنواع التالية إلا كأنواع عرض:

  • com.google.common.util.concurrent.ListenableFuture

لفائف Parcelable المخصّصة

إذا لم يكن نوع التطبيق مدرَجًا في القائمة السابقة، ننصحك أولاً بالتفكير في إمكانية تنفيذ android.os.Parcelable أو java.io.Serializable بشكلٍ صحيح. إذا تعذّر عليه بعد ذلك العثور على عناصر التفاف قابلة للتقسيم لإضافة إمكانية استخدام نوعك.

برامج تضمين المستقبل المخصّصة

إذا كنت تريد استخدام نوع مستقبلي غير مُدرَج في القائمة السابقة، اطّلِع على future wrappers لإضافة دعم.

الأغلفة القابلة للاستخدام كطرد

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

حزمة Parcelable Wrapper هي فئة مصمّمة لتغليف فئة أخرى وجعلها قابلة للتعبئة. ويتّبع هذا النوع عقدًا ثابتًا محدّدًا ويتم تسجيله في حزمة تطوير البرامج (SDK)، لذا يمكن استخدامه لتحويل نوع معيّن إلى نوع قابل للتقسيم، وأيضاً استخراج هذا النوع من النوع القابل للتقسيم.

تعليق توضيحي

يجب وضع تعليق توضيحي @CustomParcelableWrapper على فئة الحزمة التي تُغلِّف البيانات، مع تحديد الفئة المُغلَّفة على أنّها originalType. على سبيل المثال:

@CustomParcelableWrapper(originalType=ImmutableList.class)

التنسيق

يجب أن تُنفِّذ لفائف Parcelable واجهة برمجة التطبيقات Parcelable بشكل صحيح، ويجب أن تتضمّن W of(Bundler, BundlerType, T) static method التي تلف النوع المُلفَّف وT get() method غير ثابتة تُعيد النوع المُلفَّف.

ستستخدم حزمة SDK هذه الطرق لتوفير دعم سلس للنوع.

أداة تجميع التطبيقات

للسماح بتغليف الأنواع العامة (مثل القوائم والخرائط)، يتم إرسال Bundler إلى طريقة of يمكنه قراءة (باستخدام #readFromParcel) وكتابة (باستخدام #writeToParcel) جميع الأنواع المتوافقة في Parcel، وBundlerType الذي يمثّل النوع المُعلَن المطلوب كتابته.

إنّ مثيلَي Bundler وBundlerType قابلان للتقسيم، ويجب كتابتهما كجزء من تقسيم الغلاف القابل للتقسيم، حتى يمكن استخدامهما عند إعادة إنشاء الغلاف القابل للتقسيم.

إذا كان BundlerType يمثّل نوعًا عامًا، يمكن العثور على متغيّرات النوع من خلال استدعاء .typeArguments(). كل وسيطة نوع هي نفسها BundlerType.

على سبيل المثال، يمكنك الاطّلاع على ParcelableCustomWrapper:

public class CustomWrapper<F> {
  private final F value;

  public CustomWrapper(F value) {
    this.value = value;
  }
  public F value() {
    return value;
  }
}

@CustomParcelableWrapper(originalType = CustomWrapper.class)
public class ParcelableCustomWrapper<E> implements Parcelable {

  private static final int NULL = -1;
  private static final int NOT_NULL = 1;

  private final Bundler bundler;
  private final BundlerType type;
  private final CustomWrapper<E> customWrapper;

  /**
  *   Create a wrapper for a given {@link CustomWrapper}.
  *
  *   <p>The passed in {@link Bundler} must be capable of bundling {@code F}.
  */
  public static <F> ParcelableCustomWrapper<F> of(
      Bundler bundler, BundlerType type, CustomWrapper<F> customWrapper) {
    return new ParcelableCustomWrapper<>(bundler, type, customWrapper);
  }

  public CustomWrapper<E> get() {
    return customWrapper;
  }

  private ParcelableCustomWrapper(
      Bundler bundler, BundlerType type, CustomWrapper<E> customWrapper) {
    if (bundler == null || type == null) {
      throw new NullPointerException();
    }
    this.bundler = bundler;
    this.type = type;
    this.customWrapper = customWrapper;
  }

  private ParcelableCustomWrapper(Parcel in) {
    bundler = in.readParcelable(Bundler.class.getClassLoader());

    int presentValue = in.readInt();

    if (presentValue == NULL) {
      type = null;
      customWrapper = null;
      return;
    }

    type = (BundlerType) in.readParcelable(Bundler.class.getClassLoader());
    BundlerType valueType = type.typeArguments().get(0);

    @SuppressWarnings("unchecked")
    E value = (E) bundler.readFromParcel(in, valueType);

    customWrapper = new CustomWrapper<>(value);
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeParcelable(bundler, flags);

    if (customWrapper == null) {
      dest.writeInt(NULL);
      return;
    }

    dest.writeInt(NOT_NULL);
    dest.writeParcelable(type, flags);
    BundlerType valueType = type.typeArguments().get(0);
    bundler.writeToParcel(dest, customWrapper.value(), valueType, flags);
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @SuppressWarnings("rawtypes")
  public static final Creator<ParcelableCustomWrapper> CREATOR =
    new Creator<ParcelableCustomWrapper>() {
      @Override
      public ParcelableCustomWrapper createFromParcel(Parcel in) {
        return new ParcelableCustomWrapper(in);
      }

      @Override
      public ParcelableCustomWrapper[] newArray(int size) {
        return new ParcelableCustomWrapper[size];
      }
    };
}

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

بعد إنشاء الحزمة المخصّصة، عليك تسجيلها مع حزمة تطوير البرامج (SDK) لاستخدامها.

لإجراء ذلك، حدِّد parcelableWrappers={YourParcelableWrapper.class} في تعليق توضيحي CustomProfileConnector أو تعليق توضيحي CrossProfile على فئة.

برامج تضمين الصور المستقبلية

إنّ "حِزم Future Wrapper" هي الطريقة التي تضيف بها حزمة تطوير البرامج (SDK) إمكانية استخدام الإصدارات المستقبلية في جميع الملفات التجارية. تتضمّن حزمة تطوير البرامج (SDK) دعمًا لـ ListenableFuture تلقائيًا، ولكن يمكنك إضافة دعم لأنواع Future الأخرى بنفسك.

"حزمة Future" هي فئة مصمّمة لتغليف نوع Future معيّن وإتاحته لحزمة SDK. ويتّبع هذا العقد بنية ثابتة محدّدة ويجب تسجيله في حزمة SDK.

تعليق توضيحي

يجب وضع تعليق توضيحي @CustomFutureWrapper على فئة الغلاف المستقبلية، مع تحديد الفئة المُغلفة على أنّها originalType. على سبيل المثال:

@CustomFutureWrapper(originalType=SettableFuture.class)

التنسيق

يجب أن تمتد الأغلفة المستقبلية com.google.android.enterprise.connectedapps.FutureWrapper.

يجب أن تحتوي الأغلفة المستقبلية على طريقة W create(Bundler, BundlerType) ثابتة تُنشئ مثيلًا للغلاف. وفي الوقت نفسه، من المفترض أن يؤدي ذلك إلى إنشاء مثيل من نوع المستقبل المُغلف. من المفترض أن يتم عرض هذا العنصر من خلال طريقة T getFuture() غير ثابتة. يجب تنفيذ الطريقتَين onResult(E) وonException(Throwable) لتمرير النتيجة أو العنصر الذي يمكن طرحه إلى القيمة المستقبلية المُغلفة.

يجب أن تتضمّن الأغلفة المستقبلية أيضًا طريقة void writeFutureResult(Bundler, BundlerType, T, FutureResultWriter<E>) ثابتة. من المفترض أن يتم تسجيل ذلك باستخدام القيمة passed في المستقبل للحصول على النتائج، وعند ظهور نتيجة، يجب الاتصال بالرقم resultWriter.onSuccess(value). في حال حدوث استثناء، يجب استدعاء resultWriter.onFailure(exception).

أخيرًا، يجب أن تتضمّن الأغلفة المستقبلية أيضًا طريقة T<Map<Profile, E>> groupResults(Map<Profile, T<E>> results) ثابتة تحوّل خريطة من الملف الشخصي إلى المستقبل، إلى مستقبل خريطة من الملف الشخصي إلى النتيجة. يمكن استخدام CrossProfileCallbackMultiMerger لتسهيل هذا المنطق.

على سبيل المثال:

/** A basic implementation of the future pattern used to test custom future
wrappers. */
public class SimpleFuture<E> {
  public static interface Consumer<E> {
    void accept(E value);
  }
  private E value;
  private Throwable thrown;
  private final CountDownLatch countDownLatch = new CountDownLatch(1);
  private Consumer<E> callback;
  private Consumer<Throwable> exceptionCallback;

  public void set(E value) {
    this.value = value;
    countDownLatch.countDown();
    if (callback != null) {
      callback.accept(value);
    }
  }

  public void setException(Throwable t) {
    this.thrown = t;
    countDownLatch.countDown();
    if (exceptionCallback != null) {
      exceptionCallback.accept(thrown);
    }
  }

  public E get() {
    try {
      countDownLatch.await();
    } catch (InterruptedException e) {
      eturn null;
    }
    if (thrown != null) {
      throw new RuntimeException(thrown);
    }
    return value;
  }

  public void setCallback(Consumer<E> callback, Consumer<Throwable>
exceptionCallback) {
    if (value != null) {
      callback.accept(value);
    } else if (thrown != null) {
      exceptionCallback.accept(thrown);
    } else {
      this.callback = callback;
      this.exceptionCallback = exceptionCallback;
    }
  }
}
/** Wrapper for adding support for {@link SimpleFuture} to the Connected Apps SDK.
*/
@CustomFutureWrapper(originalType = SimpleFuture.class)
public final class SimpleFutureWrapper<E> extends FutureWrapper<E> {

  private final SimpleFuture<E> future = new SimpleFuture<>();

  public static <E> SimpleFutureWrapper<E> create(Bundler bundler, BundlerType
bundlerType) {
    return new SimpleFutureWrapper<>(bundler, bundlerType);
  }

  private SimpleFutureWrapper(Bundler bundler, BundlerType bundlerType) {
    super(bundler, bundlerType);
  }

  public SimpleFuture<E> getFuture() {
    return future;
  }

  @Override
  public void onResult(E result) {
    future.set(result);
  }

  @Override
  public void onException(Throwable throwable) {
    future.setException(throwable);
  }

  public static <E> void writeFutureResult(
      SimpleFuture<E> future, FutureResultWriter<E> resultWriter) {

    future.setCallback(resultWriter::onSuccess, resultWriter::onFailure);
  }

  public static <E> SimpleFuture<Map<Profile, E>> groupResults(
      Map<Profile, SimpleFuture<E>> results) {
    SimpleFuture<Map<Profile, E>> m = new SimpleFuture<>();

    CrossProfileCallbackMultiMerger<E> merger =
        new CrossProfileCallbackMultiMerger<>(results.size(), m::set);
    for (Map.Entry<Profile, SimpleFuture<E>> result : results.entrySet()) {
      result
        .getValue()
        .setCallback(
          (value) -> merger.onResult(result.getKey(), value),
          (throwable) -> merger.missingResult(result.getKey()));
    }
    return m;
  }
}

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

بعد إنشاء الحزمة المُعدَّلة المستقبلية، عليك تسجيلها باستخدام حزمة تطوير البرامج (SDK).

لإجراء ذلك، حدِّد futureWrappers={YourFutureWrapper.class} في أحد التعليقَين التوضيحيَين CustomProfileConnector أو CrossProfile على فئة.

وضع التشغيل المباشر

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

لتغيير هذا السلوك، إذا كنت تستخدم أداة ربط ملفات تعريف مخصّصة، عليك تحديد availabilityRestrictions=AvailabilityRestrictions.DIRECT_BOOT_AWARE:

@GeneratedProfileConnector
@CustomProfileConnector(availabilityRestrictions=AvailabilityRestrictions.DIRECT_BO
OT_AWARE)
public interface MyProfileConnector extends ProfileConnector {
  public static MyProfileConnector create(Context context) {
    return GeneratedMyProfileConnector.builder(context).build();
  }
}

إذا كنت تستخدم CrossProfileConnector، استخدِم .setAvailabilityRestrictions(AvailabilityRestrictions.DIRECT_BOOT _AWARE في أداة الإنشاء.

من خلال هذا التغيير، سيتم إعلامك بمدى التوفّر، وستتمكّن من إجراء مكالمات بين مختلف الملفات الشخصية، حتى إذا لم يكن الملف الشخصي الآخر غير مقفل. تقع على عاتقك مسؤولية التأكّد من أنّ مكالماتك لا تحصل إلا على مساحة التخزين المشفَّرة على الجهاز.