موضوعات پیشرفته

این بخش ها برای مرجع در نظر گرفته شده اند و لازم نیست که آنها را از بالا به پایین بخوانید.

از API های چارچوب استفاده کنید:

این APIها برای یک سطح API سازگارتر (مثلاً اجتناب از اشیاء UserHandle) در SDK پیچیده می‌شوند، اما در حال حاضر، می‌توانید مستقیماً با آنها تماس بگیرید.

پیاده سازی ساده است: اگر می توانید تعامل داشته باشید، ادامه دهید. اگر نه، اما می توانید درخواست کنید، سپس درخواست/بنر/توصیه ابزار/و غیره کاربر خود را نشان دهید. اگر کاربر با رفتن به تنظیمات موافقت کرد، قصد درخواست را ایجاد کنید و Context#startActivity برای ارسال کاربر به آنجا استفاده کنید. می‌توانید از پخش برای تشخیص اینکه چه زمانی این توانایی تغییر می‌کند استفاده کنید، یا فقط وقتی کاربر برگشت دوباره بررسی کنید.

برای آزمایش این، باید TestDPC را در نمایه کاری خود باز کنید، به پایین بروید و نام بسته خود را به لیست مجاز برنامه های متصل اضافه کنید. این کار از مدیر برنامه شما تقلید می کند.

واژه نامه

این بخش اصطلاحات کلیدی مربوط به توسعه توسعه پروفایل متقابل را تعریف می کند.

پیکربندی پروفایل متقابل

یک پیکربندی نمایه متقاطع، کلاس‌های ارائه‌دهنده پروفایل متقابل مرتبط را با هم گروه‌بندی می‌کند و پیکربندی کلی برای ویژگی‌های پروفایل متقابل ارائه می‌کند. معمولاً یک حاشیه نویسی @CrossProfileConfiguration در هر پایگاه کد وجود دارد، اما در برخی برنامه های پیچیده ممکن است چندین مورد وجود داشته باشد.

رابط پروفایل

یک رابط اتصالات بین پروفایل ها را مدیریت می کند. معمولاً هر نوع پروفایل متقاطع به یک اتصال دهنده خاص اشاره می کند. هر نوع نمایه متقاطع در یک پیکربندی باید از همان رابط استفاده کند.

کلاس ارائه دهنده پروفایل متقابل

یک کلاس ارائه دهنده پروفایل متقابل، انواع پروفایل های متقابل مرتبط را با هم گروه بندی می کند.

میانجی

یک واسطه بین کدهای سطح بالا و سطح پایین قرار می گیرد و تماس ها را به پروفایل های صحیح توزیع می کند و نتایج را ادغام می کند. این تنها کدی است که باید از نمایه آگاه باشد. این یک مفهوم معماری است نه چیزی که در SDK تعبیه شده است.

نوع پروفایل متقابل

نوع پروفایل متقاطع یک کلاس یا رابط است که حاوی روش‌هایی است که @CrossProfile مشروح شده است. کد در این نوع نیازی نیست که از نمایه آگاه باشد و در حالت ایده آل باید فقط بر روی داده های محلی خود عمل کند.

انواع پروفایل

نوع پروفایل
فعلی نمایه فعالی که روی آن اجرا می کنیم.
دیگر (در صورت وجود) نمایه ای که ما روی آن اجرا نمی کنیم.
شخصی کاربر 0، نمایه روی دستگاه که نمی توان آن را خاموش کرد.
کار کنید معمولاً کاربر 10 اما ممکن است بالاتر باشد، می توان آن را روشن و خاموش کرد، برای حاوی برنامه های کاری و داده ها استفاده می شود.
اولیه به صورت اختیاری توسط برنامه تعریف شده است. نمایه ای که نمای ادغام شده هر دو نمایه را نشان می دهد.
ثانویه اگر اولیه تعریف شده باشد، ثانویه پروفایلی است که اولیه نیست.
تامین کننده تامین کنندگان مشخصات اولیه هر دو پروفایل هستند، تامین کنندگان مشخصات ثانویه فقط خود پروفایل ثانویه است.

شناسه پروفایل

کلاسی که نمایانگر نوعی پروفایل (شخصی یا کاری) است. اینها با روش‌هایی که روی چندین پروفایل اجرا می‌شوند بازگردانده می‌شوند و می‌توان از آنها برای اجرای کدهای بیشتر روی آن پروفایل‌ها استفاده کرد. برای ذخیره سازی راحت، می توان آنها را به یک int سریال کرد.

این راهنما ساختارهای توصیه شده را برای ایجاد عملکردهای بین پروفایل کارآمد و قابل نگهداری در برنامه Android شما شرح می دهد.

CrossProfileConnector خود را به singleton تبدیل کنید

فقط یک نمونه باید در طول چرخه عمر برنامه شما استفاده شود، در غیر این صورت اتصالات موازی ایجاد خواهید کرد. این را می توان با استفاده از یک چارچوب تزریق وابستگی مانند Dagger یا با استفاده از یک الگوی کلاسیک Singleton ، چه در یک کلاس جدید و چه در کلاس موجود، انجام داد.

به جای ایجاد آن در متد، نمونه نمایه تولید شده را برای زمانی که تماس برقرار می کنید، به کلاس خود تزریق کنید یا ارسال کنید.

این به شما امکان می‌دهد در آزمون‌های واحد خود بعداً در نمونه FakeProfile تولید شده به‌طور خودکار قبول شوید.

الگوی واسطه را در نظر بگیرید

این الگوی رایج این است که یکی از APIهای موجود شما (به عنوان مثال getEvents() ) را برای همه تماس‌گیرندگانش آگاه کنید. در این مورد، API موجود شما فقط می‌تواند به یک متد یا کلاس «میانجی» تبدیل شود که حاوی فراخوانی جدید به کد پروفایل متقابل تولید شده است.

به این ترتیب، شما هر تماس‌گیرنده‌ای را مجبور نمی‌کنید که بداند یک تماس بین‌نمایه‌ای برقرار کند، آن فقط بخشی از API شما می‌شود.

برای جلوگیری از افشای کلاس‌های پیاده‌سازی خود در یک ارائه‌دهنده، در نظر بگیرید که آیا یک روش رابط را به‌عنوان @CrossProfile حاشیه‌نویسی کنید یا خیر.

این به خوبی با چارچوب های تزریق وابستگی کار می کند.

اگر داده‌ای از یک تماس با نمایه متقابل دریافت می‌کنید، در نظر بگیرید که آیا فیلدی را اضافه کنید که از کدام نمایه است.

این می تواند تمرین خوبی باشد زیرا ممکن است بخواهید این را در لایه UI بدانید (مثلاً اضافه کردن نماد نشان به موارد کار). همچنین اگر شناسه‌های داده دیگر بدون آن منحصربه‌فرد نیستند، مانند نام بسته، ممکن است لازم باشد.

نمایه متقاطع

این بخش نحوه ایجاد تعاملات Cross Profile خود را شرح می دهد.

پروفایل های اولیه

بیشتر تماس‌های موجود در نمونه‌های موجود در این سند حاوی دستورالعمل‌های صریح در مورد نمایه‌هایی است که شامل کار، شخصی و هر دو می‌شود.

در عمل، برای برنامه‌هایی که تنها در یک نمایه تجربه ادغام شده دارند، احتمالاً می‌خواهید این تصمیم به نمایه‌ای که روی آن اجرا می‌کنید بستگی داشته باشد، بنابراین روش‌های راحت مشابهی وجود دارد که این را نیز در نظر می‌گیرند تا از پر شدن پایگاه کد شما جلوگیری شود. if-else مشخصات مشروط.

هنگام ایجاد نمونه رابط، می‌توانید نوع نمایه اصلی خود را مشخص کنید (مثلاً «WORK»). این گزینه های اضافی را فعال می کند، مانند موارد زیر:

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 هستند، به‌عنوان انواع پروفایل متقابل شناخته می‌شوند.

پیاده سازی Cross Profile Types باید مستقل از نمایه باشد، نمایه ای که روی آن اجرا می شود. آنها مجاز به برقراری تماس با روش های دیگر هستند و به طور کلی باید مانند آنها در یک نمایه واحد کار کنند. آنها فقط به حالت در نمایه خود دسترسی خواهند داشت.

نمونه ای از نوع پروفایل متقابل:

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

حاشیه نویسی کلاس

برای ارائه قوی‌ترین API، باید کانکتور را برای هر نوع پروفایل متقاطع مشخص کنید، به این ترتیب:

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

این اختیاری است اما به این معنی است که API تولید شده در مورد انواع خاص تر و در بررسی زمان کامپایل سخت تر خواهد بود.

رابط ها

با حاشیه‌نویسی روش‌ها در یک رابط به‌عنوان @CrossProfile ، بیان می‌کنید که می‌توان این روش را پیاده‌سازی کرد که باید در بین پروفایل‌ها قابل دسترسی باشد.

شما می‌توانید هر پیاده‌سازی یک رابط Cross Profile را در یک ارائه‌دهنده Cross Profile برگردانید و با انجام این کار می‌گویید که این پیاده‌سازی باید به صورت متقابل قابل دسترسی باشد. شما نیازی به حاشیه نویسی کلاس های پیاده سازی ندارید.

ارائه دهندگان پروفایل متقابل

هر نوع نمایه متقاطع باید با روشی ارائه شود که @CrossProfileProvider توضیح داده شده است. این روش‌ها هر بار که یک تماس با پروفایل متقابل برقرار می‌شود، فراخوانی می‌شوند، بنابراین توصیه می‌شود که برای هر نوع تک‌تن‌ها را حفظ کنید.

سازنده

یک ارائه دهنده باید یک سازنده عمومی داشته باشد که هیچ آرگومان یا آرگومان Context واحدی را دریافت نکند.

روش های ارائه دهنده

متدهای ارائه دهنده باید یا بدون آرگومان یا آرگومان Context منفرد استفاده کنند.

تزریق وابستگی

اگر از یک چارچوب تزریق وابستگی مانند Dagger برای مدیریت وابستگی‌ها استفاده می‌کنید، توصیه می‌کنیم که آن فریم‌ورک انواع پروفایل‌های متقاطع شما را همانطور که معمولاً انجام می‌دهید ایجاد کند و سپس آن انواع را به کلاس ارائه‌دهنده خود تزریق کنید. سپس متدهای @CrossProfileProvider می توانند نمونه های تزریق شده را برگردانند.

رابط پروفایل

هر پیکربندی پروفایل متقاطع باید دارای یک رابط پروفایل باشد که مسئول مدیریت اتصال به نمایه دیگر است.

رابط پیش فرض نمایه

اگر تنها یک پیکربندی متقابل نمایه در یک پایگاه کد وجود دارد، می‌توانید از ایجاد رابط نمایه خود اجتناب کنید و از com.google.android.enterprise.connectedapps.CrossProfileConnector استفاده کنید. اگر هیچ یک مشخص نشده باشد، این پیش‌فرض است.

هنگام ساخت رابط Cross Profile، می توانید برخی از گزینه ها را در سازنده مشخص کنید:

  • سرویس مجری برنامه ریزی شده

    اگر می‌خواهید روی رشته‌های ایجاد شده توسط SDK کنترل داشته باشید، از #setScheduledExecutorService() استفاده کنید.

  • کلاسور

    اگر نیازهای خاصی در مورد صحافی پروفایل دارید، از #setBinder استفاده کنید. این احتمالاً فقط توسط Device Policy Controller ها استفاده می شود.

رابط پروفایل سفارشی

برای اینکه بتوانید تنظیماتی را تنظیم کنید (با استفاده از 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 استفاده کنید.

کنترلرهای سیاست دستگاه

اگر برنامه شما یک Device Policy Controller است، باید یک نمونه از 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 {
}

این تأیید می کند که برای همه انواع Cross Profile یا همان رابط پروفایل دارند یا هیچ کانکتوری مشخص نشده است.

  • serviceSuperclass

    به طور پیش فرض، سرویس ایجاد شده از android.app.Service به عنوان سوپرکلاس استفاده می کند. اگر به کلاس دیگری نیاز دارید (که خود باید زیر کلاس android.app.Service باشد) تا superclass باشد، سپس 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 شما باید بتواند همه ارائه دهندگان مورد استفاده در برنامه شما را ببیند.

تماس های همزمان

Connected Apps SDK از تماس‌های همزمان (مسدودکننده) برای مواردی که غیرقابل اجتناب هستند، پشتیبانی می‌کند. با این حال، استفاده از این تماس ها معایبی دارد (مانند احتمال مسدود شدن تماس ها برای مدت طولانی) بنابراین توصیه می شود در صورت امکان از تماس های همزمان خودداری کنید . برای استفاده از تماس های ناهمزمان به تماس های ناهمزمان مراجعه کنید.

دارندگان اتصال

اگر از تماس‌های همزمان استفاده می‌کنید، باید مطمئن شوید که قبل از برقراری تماس‌های متقاطع، یک دارنده اتصال ثبت شده است، در غیر این صورت یک استثنا ایجاد می‌شود. برای اطلاعات بیشتر به نگهدارنده اتصال مراجعه کنید.

برای افزودن نگهدارنده اتصال، ProfileConnector#addConnectionHolder(Object) با هر شی (به طور بالقوه، نمونه شی که تماس بین پروفایل را برقرار می کند) فراخوانی کنید. این نشان می دهد که این شی از اتصال استفاده می کند و سعی می کند اتصال برقرار کند. این باید قبل از برقراری تماس همزمان فراخوانی شود. این یک تماس غیرمسدود است، بنابراین ممکن است تا زمانی که تماس خود را برقرار می کنید، اتصال آماده نباشد (یا ممکن است امکان پذیر نباشد)، در این صورت رفتار معمول رسیدگی به خطا اعمال می شود.

اگر هنگام تماس ProfileConnector#addConnectionHolder(Object) فاقد مجوزهای مربوط به پروفایل متقابل هستید یا هیچ نمایه ای برای اتصال در دسترس نیست، در این صورت هیچ خطایی ایجاد نخواهد شد اما تماس پاسخ متصل هرگز فراخوانی نخواهد شد. اگر مجوز بعداً اعطا شود یا نمایه دیگر در دسترس قرار گیرد، سپس اتصال برقرار می‌شود و تماس برگشتی فراخوانی می‌شود.

از طرف دیگر، ProfileConnector#connect(Object) یک روش مسدود کننده است که شی را به عنوان نگهدارنده اتصال اضافه می کند و یا یک اتصال برقرار می کند یا یک UnavailableProfileException ایجاد می کند. این روش را نمی توان از UI Thread فراخوانی کرد.

فراخوانی های 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 ) یا پارامتر برگشت تماس را می‌پذیرد، به‌عنوان غیرمسدود علامت‌گذاری می‌شود. همه روش های دیگر به عنوان مسدود کننده علامت گذاری شده اند.

تماس های ناهمزمان توصیه می شود. اگر باید از تماس‌های همزمان استفاده کنید، به تماس‌های همزمان مراجعه کنید.

تماس های تلفنی

ابتدایی ترین نوع فراخوانی غیرمسدود کننده، متد void است که به عنوان یکی از پارامترهای خود، رابطی را می پذیرد که حاوی متدی است که باید با نتیجه فراخوانی شود. برای اینکه این رابط‌ها با SDK کار کنند، رابط باید حاشیه‌نویسی شود @CrossProfileCallback . به عنوان مثال:

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

سپس این رابط می تواند به عنوان یک پارامتر در یک روش حاشیه نویسی @CrossProfile استفاده شود و طبق معمول فراخوانی شود. به عنوان مثال:

@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 پشتیبانی می شوند. تنها نوع Future که به صورت بومی پشتیبانی می شود ListenableFuture است، اگرچه می توان از انواع آتی سفارشی استفاده کرد. برای استفاده از قراردادهای آتی، فقط یک نوع Future پشتیبانی شده را به عنوان نوع بازگشتی یک روش پروفایل متقاطع اعلام کرده و سپس از آن به طور معمول استفاده کنید.

این همان "ویژگی غیرمعمول" به عنوان callback ها را دارد، که در آن یک روش همزمان که یک آینده را برمی گرداند (مثلاً با استفاده از 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 (یا ارسال به آینده، یا پاسخ به تماس خطا برای تماس غیرهمگام) می‌شود.

برای جلوگیری از این امر، توسعه‌دهندگان می‌توانند از #both() یا #suppliers() استفاده کنند و کد خود را برای مقابله با هر تعداد ورودی در لیست حاصل بنویسند (اگر نمایه دیگر در دسترس نباشد، 1 خواهد بود، یا اگر نمایه دیگر در دسترس باشد، 2 خواهد بود) .

استثنائات

هر استثنای علامت‌خورده‌ای که پس از تماس با نمایه فعلی اتفاق می‌افتد، طبق معمول منتشر می‌شود. این بدون توجه به روشی که برای برقراری تماس استفاده می‌شود ( #current() ، #personal ، #both ، و غیره) اعمال می‌شود.

استثناهای بدون علامت که پس از تماس با نمایه دیگر اتفاق می‌افتند، منجر به ایجاد یک ProfileRuntimeException با استثنای اصلی به عنوان علت می‌شوند. این بدون توجه به روشی که برای برقراری تماس استفاده می‌شود ( #other() ، #personal ، #both ، و غیره) اعمال می‌شود.

در صورت موجود بودن

به عنوان جایگزینی برای گرفتن و برخورد با نمونه‌های 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

لفاف های قابل بسته بندی سفارشی

اگر نوع شما در لیست قبلی نیست، ابتدا در نظر بگیرید که آیا می‌توان آن را به‌طور صحیح android.os.Parcelable یا java.io.Serializable را پیاده‌سازی کرد. اگر نمی تواند بسته بندی های قابل بسته بندی را ببیند تا از نوع شما پشتیبانی کند.

بسته بندی های آینده سفارشی

اگر می‌خواهید از یک نوع آینده استفاده کنید که در لیست قبلی نیست، برای افزودن پشتیبانی به wrapper‌های آینده مراجعه کنید.

لفاف های قابل بسته بندی

بسته‌بندی‌های بسته‌پذیر راهی هستند که SDK پشتیبانی از انواع غیرقابل بسته‌بندی را اضافه می‌کند که قابل تغییر نیستند. SDK شامل بسته‌بندی‌هایی برای بسیاری از انواع است، اما اگر نوع مورد نیاز برای استفاده در آن گنجانده نشده است، باید نوع خود را بنویسید.

Parcelable Wrapper کلاسی است که برای بسته بندی یک کلاس دیگر و قابل حمل کردن آن طراحی شده است. از یک قرارداد ثابت تعریف شده پیروی می کند و با SDK ثبت می شود، بنابراین می توان از آن برای تبدیل یک نوع معین به یک نوع parcelable و همچنین استخراج آن نوع از نوع parcelable استفاده کرد.

حاشیه نویسی

کلاس wrapper parcelable باید حاشیه نویسی شود @CustomParcelableWrapper ، و کلاس wrapped را به عنوان originalType مشخص می کند. به عنوان مثال:

@CustomParcelableWrapper(originalType=ImmutableList.class)

قالب

بسته‌بندی‌های Parcelable باید Parcelable به‌درستی پیاده‌سازی کنند، و باید دارای یک متد استاتیک W of(Bundler, BundlerType, T) باشند که نوع wrapped را می‌پیچد و یک روش T get() که نوع پیچیده شده را برمی‌گرداند.

SDK از این روش ها برای ارائه پشتیبانی یکپارچه از نوع استفاده می کند.

باندلر

برای اجازه دادن به بسته بندی انواع عمومی (مانند لیست ها و نقشه ها)، of از یک Bundler ارسال می شود که قادر به خواندن (با استفاده از #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 Wrappers نحوه‌ی اضافه کردن پشتیبانی SDK برای قراردادهای آتی در بین نمایه‌ها است. SDK به طور پیش‌فرض از ListenableFuture پشتیبانی می‌کند، اما برای سایر انواع Future می‌توانید خودتان پشتیبانی اضافه کنید.

Future Wrapper کلاسی است که برای بسته بندی یک نوع Future خاص و در دسترس قرار دادن آن برای SDK طراحی شده است. از یک قرارداد ثابت تعریف شده پیروی می کند و باید در SDK ثبت شود.

حاشیه نویسی

کلاس wrapper آینده باید حاشیه نویسی شود @CustomFutureWrapper ، و کلاس wrapped را به عنوان originalType مشخص می کند. به عنوان مثال:

@CustomFutureWrapper(originalType=SettableFuture.class)

قالب

wrapper های آینده باید com.google.android.enterprise.connectedapps.FutureWrapper را گسترش دهند.

Wrapper های آینده باید دارای یک روش استاتیک W create(Bundler, BundlerType) باشند که نمونه ای از wrapper را ایجاد می کند. در عین حال این باید یک نمونه از نوع آینده پیچیده ایجاد کند. این باید توسط یک متد غیراستاتیک T getFuture() برگردانده شود. متدهای onResult(E) و onException(Throwable) باید برای انتقال نتیجه یا قابل پرتاب به آینده پیچیده پیاده سازی شوند.

Wrapper های آینده نیز باید دارای روش استاتیک void writeFutureResult(Bundler, BundlerType, T, FutureResultWriter<E>) باشند. این باید در آینده برای نتایج با گذرنامه ثبت شود، و هنگامی که نتیجه داده شد، resultWriter.onSuccess(value) را فراخوانی کنید. اگر استثنا داده شود، resultWriter.onFailure(exception) باید فراخوانی شود.

در نهایت، wrapper های آینده نیز باید دارای یک روش 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 مشخص کنید.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 در سازنده استفاده کنید.

با این تغییر، زمانی که نمایه دیگر قفل نیست، از در دسترس بودن مطلع می شوید و می توانید تماس های متقابل پروفایل برقرار کنید. این مسئولیت شماست که اطمینان حاصل کنید که تماس های شما فقط به حافظه رمزگذاری شده دستگاه دسترسی دارند.

،

این بخش ها برای مرجع در نظر گرفته شده اند و لازم نیست که آنها را از بالا به پایین بخوانید.

از API های چارچوب استفاده کنید:

این APIها برای یک سطح API سازگارتر (مثلاً اجتناب از اشیاء UserHandle) در SDK پیچیده می‌شوند، اما در حال حاضر، می‌توانید مستقیماً با آنها تماس بگیرید.

پیاده سازی ساده است: اگر می توانید تعامل داشته باشید، ادامه دهید. اگر نه، اما می توانید درخواست کنید، سپس درخواست/بنر/توصیه ابزار/و غیره کاربر خود را نشان دهید. اگر کاربر با رفتن به تنظیمات موافقت کرد، قصد درخواست را ایجاد کنید و Context#startActivity برای ارسال کاربر به آنجا استفاده کنید. می‌توانید از پخش برای تشخیص اینکه چه زمانی این توانایی تغییر می‌کند استفاده کنید، یا فقط وقتی کاربر برگشت دوباره بررسی کنید.

برای آزمایش این، باید TestDPC را در نمایه کاری خود باز کنید، به پایین بروید و نام بسته خود را به لیست مجاز برنامه های متصل اضافه کنید. این کار از مدیر برنامه شما تقلید می کند.

واژه نامه

این بخش اصطلاحات کلیدی مربوط به توسعه توسعه پروفایل متقابل را تعریف می کند.

پیکربندی پروفایل متقابل

یک پیکربندی نمایه متقاطع، کلاس‌های ارائه‌دهنده پروفایل متقابل مرتبط را با هم گروه‌بندی می‌کند و پیکربندی کلی برای ویژگی‌های پروفایل متقابل ارائه می‌کند. معمولاً یک حاشیه نویسی @CrossProfileConfiguration در هر پایگاه کد وجود دارد، اما در برخی برنامه های پیچیده ممکن است چندین مورد وجود داشته باشد.

رابط پروفایل

یک رابط اتصالات بین پروفایل ها را مدیریت می کند. معمولاً هر نوع پروفایل متقاطع به یک اتصال دهنده خاص اشاره می کند. هر نوع نمایه متقاطع در یک پیکربندی باید از همان رابط استفاده کند.

کلاس ارائه دهنده پروفایل متقابل

یک کلاس ارائه دهنده پروفایل متقابل، انواع پروفایل های متقابل مرتبط را با هم گروه بندی می کند.

میانجی

یک واسطه بین کدهای سطح بالا و سطح پایین قرار می گیرد و تماس ها را به پروفایل های صحیح توزیع می کند و نتایج را ادغام می کند. این تنها کدی است که باید از نمایه آگاه باشد. این یک مفهوم معماری است نه چیزی که در SDK تعبیه شده است.

نوع پروفایل متقابل

نوع پروفایل متقاطع یک کلاس یا رابط است که حاوی روش‌هایی است که @CrossProfile مشروح شده است. کد در این نوع نیازی نیست که از نمایه آگاه باشد و در حالت ایده آل باید فقط بر روی داده های محلی خود عمل کند.

انواع پروفایل

نوع پروفایل
فعلی نمایه فعالی که روی آن اجرا می کنیم.
دیگر (در صورت وجود) نمایه ای که ما روی آن اجرا نمی کنیم.
شخصی کاربر 0، نمایه روی دستگاه که نمی توان آن را خاموش کرد.
کار کنید معمولاً کاربر 10 اما ممکن است بالاتر باشد، می توان آن را روشن و خاموش کرد، برای حاوی برنامه های کاری و داده ها استفاده می شود.
اولیه به صورت اختیاری توسط برنامه تعریف شده است. نمایه ای که نمای ادغام شده هر دو نمایه را نشان می دهد.
ثانویه اگر اولیه تعریف شده باشد، ثانویه پروفایلی است که اولیه نیست.
تامین کننده تامین کنندگان مشخصات اولیه هر دو پروفایل هستند، تامین کنندگان مشخصات ثانویه فقط خود پروفایل ثانویه است.

شناسه پروفایل

کلاسی که نمایانگر نوعی پروفایل (شخصی یا کاری) است. اینها با روش‌هایی که روی چندین پروفایل اجرا می‌شوند بازگردانده می‌شوند و می‌توان از آنها برای اجرای کدهای بیشتر روی آن پروفایل‌ها استفاده کرد. برای ذخیره سازی راحت، می توان آنها را به یک int سریال کرد.

این راهنما ساختارهای توصیه شده را برای ایجاد عملکردهای بین پروفایل کارآمد و قابل نگهداری در برنامه Android شما شرح می دهد.

CrossProfileConnector خود را به singleton تبدیل کنید

فقط یک نمونه باید در طول چرخه عمر برنامه شما استفاده شود، در غیر این صورت اتصالات موازی ایجاد خواهید کرد. این را می توان با استفاده از یک چارچوب تزریق وابستگی مانند Dagger یا با استفاده از یک الگوی کلاسیک Singleton ، چه در یک کلاس جدید و چه در کلاس موجود، انجام داد.

به جای ایجاد آن در متد، نمونه نمایه تولید شده را برای زمانی که تماس برقرار می کنید، به کلاس خود تزریق کنید یا ارسال کنید.

این به شما امکان می‌دهد در آزمون‌های واحد خود بعداً در نمونه FakeProfile تولید شده به‌طور خودکار قبول شوید.

الگوی واسطه را در نظر بگیرید

این الگوی رایج این است که یکی از APIهای موجود شما (به عنوان مثال getEvents() ) را برای همه تماس‌گیرندگانش آگاه کنید. در این مورد، API موجود شما فقط می‌تواند به یک متد یا کلاس «میانجی» تبدیل شود که حاوی فراخوانی جدید به کد پروفایل متقابل تولید شده است.

به این ترتیب، شما هر تماس‌گیرنده‌ای را مجبور نمی‌کنید که بداند یک تماس بین‌نمایه‌ای برقرار کند، آن فقط بخشی از API شما می‌شود.

برای جلوگیری از افشای کلاس‌های پیاده‌سازی خود در یک ارائه‌دهنده، در نظر بگیرید که آیا یک روش رابط را به‌عنوان @CrossProfile حاشیه‌نویسی کنید یا خیر.

این به خوبی با چارچوب های تزریق وابستگی کار می کند.

اگر داده‌ای از یک تماس با نمایه متقابل دریافت می‌کنید، در نظر بگیرید که آیا فیلدی را اضافه کنید که از کدام نمایه است.

این می تواند تمرین خوبی باشد زیرا ممکن است بخواهید این را در لایه UI بدانید (مثلاً اضافه کردن نماد نشان به موارد کار). همچنین اگر شناسه‌های داده دیگر بدون آن منحصربه‌فرد نیستند، مانند نام بسته، ممکن است لازم باشد.

نمایه متقاطع

این بخش نحوه ایجاد تعاملات Cross Profile خود را شرح می دهد.

پروفایل های اولیه

بیشتر تماس‌های موجود در نمونه‌های موجود در این سند حاوی دستورالعمل‌های صریح در مورد نمایه‌هایی است که شامل کار، شخصی و هر دو می‌شود.

در عمل ، برای برنامه هایی با تجربه ادغام شده در یک پروفایل ، احتمالاً می خواهید این تصمیم به نمایه ای که در حال اجرا هستید ، وابسته باشید ، بنابراین روشهای مناسب مشابهی وجود دارد که این موضوع را نیز در نظر می گیرد تا از پایبندی کد خود جلوگیری شود شرط بندی مشخصات IF-Else.

هنگام ایجاد نمونه کانکتور خود ، می توانید مشخص کنید که نوع مشخصات "اصلی" شما (به عنوان مثال "کار" است). این گزینه های اضافی مانند موارد زیر را امکان پذیر می کند:

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;
  }
}

حاشیه نویسی کلاس

برای تأمین قویترین API ، باید اتصال را برای هر نوع پروفایل متقاطع مشخص کنید ، همانطور که:

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

این اختیاری است اما به این معنی است که API تولید شده از نظر انواع و سخت تر در بررسی زمان کامپایل خاص تر خواهد بود.

رابط ها

با حاشیه نویسی روشهای روی یک رابط به عنوان @CrossProfile بیان می کنید که می توانید برخی از اجرای این روش وجود داشته باشد که باید در بین پروفایل ها قابل دسترسی باشد.

شما می توانید هرگونه اجرای یک رابط مشخصات متقابل را در یک ارائه دهنده مشخصات متقاطع برگردانید و با این کار می گویید که این اجرای باید در دسترس باشد. نیازی به حاشیه نویسی کلاسهای اجرای نیست.

ارائه دهندگان مشخصات متقابل

هر نوع پروفایل متقاطع باید با روشی که حاشیه نویسی @CrossProfileProvider است ارائه شود. این روشها هر بار که یک تماس متقابل برقرار می شود فراخوانی می شوند ، بنابراین توصیه می شود که برای هر نوع تک آهنگ ها را حفظ کنید.

سازنده

یک ارائه دهنده باید یک سازنده عمومی داشته باشد که هیچ استدلالی یا یک استدلال Context واحد در نظر نمی گیرد.

روشهای ارائه دهنده

روش های ارائه دهنده باید هیچ استدلال یا یک استدلال Context واحد را در نظر بگیرند.

تزریق وابستگی

اگر از یک چارچوب تزریق وابستگی مانند Dagger برای مدیریت وابستگی استفاده می کنید ، توصیه می کنیم که این چارچوب نوع مشخصات متقاطع خود را طبق معمول ایجاد کنید و سپس آن نوع را به کلاس ارائه دهنده خود تزریق کنید. روشهای @CrossProfileProvider می توانند آن موارد تزریق شده را برگردانند.

اتصال مشخصات

هر پیکربندی مشخصات متقابل باید دارای یک کانکتور پروفایل واحد باشد که وظیفه مدیریت اتصال به پروفایل دیگر را بر عهده دارد.

اتصال پروفایل پیش فرض

اگر فقط یک پیکربندی مشخصات متقاطع در یک پایگاه کد وجود دارد ، می توانید از ایجاد کانکتور پروفایل خود خودداری کنید و از com.google.android.enterprise.connectedapps.CrossProfileConnector استفاده کنید. این پیش فرض مورد استفاده در صورت مشخص نیست.

هنگام ساخت کانکتور مشخصات متقابل ، می توانید برخی از گزینه ها را در سازنده مشخص کنید:

  • سرویس مجری برنامه ریزی شده

    اگر می خواهید بر موضوعات ایجاد شده توسط 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 به عنوان SuperClass استفاده می کند. اگر به کلاس دیگری احتیاج دارید (که خود باید زیر کلاس android.app.Service باشد) برای SuperClass ، سپس 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 از تماس های همزمان (مسدود کردن) برای مواردی که اجتناب ناپذیر هستند پشتیبانی می کند. با این وجود ، استفاده از این تماس ها (مانند پتانسیل تماس برای مسدود کردن مدت طولانی) تعدادی از مضرات وجود دارد ، بنابراین توصیه می شود در صورت امکان از تماس های همزمان خودداری کنید . برای استفاده از تماس های ناهمزمان ، تماس های ناهمزمان را ببینید.

دارندگان اتصال

اگر از تماس های همزمان استفاده می کنید ، باید اطمینان حاصل کنید که قبل از برقراری تماس های مشخصات متقاطع ، دارنده اتصال ثبت شده وجود دارد ، در غیر این صورت یک استثناء پرتاب می شود. برای اطلاعات بیشتر به دارندگان اتصال مراجعه کنید.

برای افزودن یک نگهدارنده اتصال ، با ProfileConnector#addConnectionHolder(Object) با هر شی (به طور بالقوه ، نمونه شیء که تماس متقابل را انجام می دهد) تماس بگیرید. این به ثبت می رسد که این شی از اتصال استفاده می کند و سعی در ایجاد اتصال خواهد داشت. این باید قبل از هرگونه تماس همزمان فراخوانی شود. این یک تماس غیر مسدود کننده است ، بنابراین ممکن است تا زمانی که تماس خود را برقرار کنید ، اتصال آماده نشود (یا ممکن است امکان پذیر نباشد).

اگر در هنگام تماس با ProfileConnector#addConnectionHolder(Object) یا مشخصات مشخصی برای اتصال در دسترس نیست ، مجوزهای مناسب متقاطع را نداشته باشید ، پس هیچ خطایی پرتاب نمی شود اما تماس تلفنی متصل هرگز فراخوانی نمی شود. اگر بعداً مجوز اعطا شود یا نمایه دیگر در دسترس باشد ، اتصال آن انجام می شود و پاسخ به تماس.

از طرف دیگر ، ProfileConnector#connect(Object) یک روش مسدود کننده است که به عنوان دارنده اتصال ، شی را اضافه می کند و یا یک اتصال ایجاد می کند یا یک UnavailableProfileException پرتاب می کند. این روش را نمی توان از موضوع UI نامید .

تماس با 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 ) یا یک پارامتر پاسخ به تماس را می پذیرد به عنوان غیر مسدود کننده مشخص می شود. تمام روشهای دیگر به عنوان مسدود کردن مشخص شده اند.

تماس های ناهمزمان توصیه می شود. اگر باید از تماس های هماهنگ استفاده کنید ، تماس های همزمان را مشاهده کنید.

تماس های تلفنی

ابتدایی ترین نوع تماس غیر مسدود کننده ، یک روش خالی است که به عنوان یکی از پارامترهای آن رابط می پذیرد که حاوی روشی است که باید با نتیجه فراخوانی شود. برای اینکه این رابط ها با SDK کار کنند ، رابط کاربری باید @CrossProfileCallback حاشیه نویسی شود. به عنوان مثال:

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

این رابط می تواند به عنوان یک پارامتر در یک روش حاشیه نویسی @CrossProfile استفاده شود و طبق معمول خوانده شود. به عنوان مثال:

@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 خوانده می شود ، این به همان روش رفتار نخواهد کرد. هیچ تضمینی وجود ندارد که روش نصب قبل از چاپ "این چاپ سوم" فراخوانی شود. هرگونه استفاده از روشی که توسط 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);

این همچنین می تواند با یک بلوک امتحان شده با منابع استفاده شود:

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 است ، اگرچه می توان از انواع آینده سفارشی استفاده کرد. برای استفاده از آینده ، شما فقط یک نوع آینده پشتیبانی شده را به عنوان نوع بازگشت یک روش مشخصات متقاطع اعلام می کنید و سپس از آن به عنوان عادی استفاده می کنید.

این همان "ویژگی غیرمعمول" به عنوان برگشتی است ، جایی که یک روش همزمان که آینده ای را برمی گرداند (به عنوان مثال با استفاده از 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 به صورت نشده (یا به آینده منتقل می شود ، یا پاسخ به خطای خطای برای تماس ASYNC).

برای جلوگیری از این امر ، توسعه دهندگان می توانند از #both() یا #suppliers() استفاده کنند و کد خود را برای مقابله با هر تعداد ورودی در لیست حاصل بنویسند (اگر مشخصات دیگر در دسترس نباشد ، یا 2 در صورت موجود بودن ، این 1 خواهد بود) .

استثنائات

هر استثنائی بدون بررسی که پس از تماس با مشخصات فعلی اتفاق می افتد ، طبق معمول پخش می شود. این امر صرف نظر از روشی که برای برقراری تماس ( #current() ، #personal ، #both و غیره) استفاده می شود ، صدق می کند.

استثنائات بدون بررسی که پس از تماس با پروفایل دیگر اتفاق می افتد منجر به ProfileRuntimeException با استثناء اصلی به عنوان علت می شود. این امر صرف نظر از روشی که برای برقراری تماس ( #other() ، #personal ، #both و غیره) استفاده می شود ، صدق می کند.

در صورتی

به عنوان جایگزینی برای صید و برخورد با نمونه های 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();

Connector Fake Connector و Cross Profile را تحت آزمایش قرار دهید و سپس تماس بگیرید.

تماس ها به هدف صحیح هدایت می شوند - و استثنائات هنگام برقراری تماس برای پروفایل های قطع یا در دسترس نیست.

انواع پشتیبانی شده

انواع زیر بدون هیچ تلاشی اضافی از طرف شما پشتیبانی می شود. این موارد می تواند به عنوان آرگومان یا انواع برگشتی برای همه تماس های مشخص استفاده شود.

  • ابتدایی ( byte ، short ، int ، long ، float ، double ، char ، boolean ) ،
  • Boxed Primitives ( 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

بسته بندی های بسته بندی شده سفارشی

اگر نوع شما در لیست قبلی نیست ، ابتدا در نظر بگیرید که آیا می توان آن را به درستی android.os.Parcelable یا java.io.Serializable پیاده سازی کرد. اگر نتواند پس از آن ، بسته بندی های قابل حمل را برای اضافه کردن پشتیبانی از نوع شما مشاهده کنید.

بسته های آینده سفارشی

اگر می خواهید از یک نوع آینده استفاده کنید که در لیست قبلی نیست ، برای افزودن پشتیبانی به Wrouphers Future مراجعه کنید.

بسته های قابل حمل

بسته بندی های بسته بندی شده راهی است که SDK پشتیبانی از انواع غیر قابل حمل را اضافه می کند که قابل تغییر نیست. SDK شامل انواع مختلفی برای انواع مختلفی است اما اگر نوع مورد نیاز برای استفاده از آن گنجانده نشده باشد ، باید خود را بنویسید.

بسته بندی بسته بندی شده یک کلاس است که برای بسته بندی کلاس دیگری طراحی شده و آن را بسته بندی می کند. این یک قرارداد استاتیک تعریف شده را دنبال می کند و در SDK ثبت شده است ، بنابراین می توان از آن برای تبدیل یک نوع معین به یک نوع بسته بندی شده استفاده کرد و همچنین آن نوع را از نوع بسته بندی استخراج کرد.

حاشیه نویسی

کلاس بسته بندی بسته بندی شده باید @CustomParcelableWrapper حاشیه نویسی شود و کلاس بسته بندی شده را به عنوان originalType مشخص کند. به عنوان مثال:

@CustomParcelableWrapper(originalType=ImmutableList.class)

قالب

بسته بندی های بسته بندی شده باید Parcelable به درستی پیاده سازی کنند ، و باید W of(Bundler, BundlerType, T) استفاده کنند که نوع بسته بندی شده و یک روش غیر استاتیک T get() .

SDK از این روش ها برای ارائه پشتیبانی یکپارچه برای نوع استفاده می کند.

باندلر

برای بسته بندی انواع عمومی (مانند لیست ها و نقشه ها) ، of روش یک Bundler عبور می کند که قادر به خواندن (با استفاده از #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 در کلاس مشخص کنید.

بسته های آینده

بسته بندی های آینده چگونه SDK پشتیبانی آینده را در پروفایل ها می افزاید. SDK به طور پیش فرض پشتیبانی از ListenableFuture را شامل می شود ، اما برای سایر انواع آینده ممکن است خود را به خود اضافه کنید.

بسته بندی آینده ، کلاس است که برای بسته بندی یک نوع آینده خاص و در دسترس SDK طراحی شده است. این یک قرارداد استاتیک تعریف شده را دنبال می کند و باید در SDK ثبت شود.

حاشیه نویسی

کلاس Wrapper Future باید @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>) باشند. این باید برای نتایج در آینده برای نتایج ثبت شده باشد و هنگامی که نتیجه ای داده شود ، تماس با 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 در یک کلاس مشخص کنید.

حالت بوت مستقیم

اگر برنامه شما از حالت Boot Direct پشتیبانی می کند ، ممکن است قبل از باز شدن نمایه ، نیاز به برقراری تماس های متقابل داشته باشید. به طور پیش فرض ، 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 در سازنده.

با استفاده از این تغییر ، شما از در دسترس بودن مطلع خواهید شد و قادر به برقراری تماس با پروفایل متقاطع هستید ، در صورت عدم قفل نمایه دیگر. این وظیفه شماست که اطمینان حاصل کنید که تماس های شما فقط به ذخیره سازی رمزگذاری شده دستگاه دسترسی پیدا می کنند.