این بخش ها برای مرجع در نظر گرفته شده اند و لازم نیست که آنها را از بالا به پایین بخوانید.
درخواست رضایت کاربر
از API های چارچوب استفاده کنید:
-
CrossProfileApps.canInteractAcrossProfiles()
-
CrossProfileApps.canRequestInteractAcrossProfiles()
-
CrossProfileApps.createRequestInteractAcrossProfilesIntent()
-
CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED
این 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)
``` ###
Format
Parcelable wrappers must implement `Parcelable` correctly, and must have a
static `W of(Bundler, BundlerType, T)` method which wraps the wrapped type and a
non-static `T get()` method which returns the wrapped type.
The SDK will use these methods to provide seamless support for the type.
### Bundler
To allow for wrapping generic types (such as lists and maps), the `of` method is
passed a `Bundler` which is capable of reading (using `#readFromParcel`) and
writing (using `#writeToParcel`) all supported types to a `Parcel`, and a
`BundlerType` which represents the declared type to be written.
`Bundler` and `BundlerType` instances are themselves parcelable, and should be
written as part of the parcelling of the parcelable wrapper, so that it can be
used when reconstructing the parcelable wrapper.
If the `BundlerType` represents a generic type, the type variables can be found
by calling `.typeArguments()`. Each type argument is itself a `BundlerType`.
For an example, see `ParcelableCustomWrapper`:
```java
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)
``` ### Format
Future wrappers must extend
`com.google.android.enterprise.connectedapps.FutureWrapper`.
Future wrappers must have a static `W create(Bundler, BundlerType)` method which
creates an instance of the wrapper. At the same time this should create an
instance of the wrapped future type. This should be returned by a non-static `T`
`getFuture()` method. The `onResult(E)` and `onException(Throwable)` methods
must be implemented to pass the result or throwable to the wrapped future.
Future wrappers must also have a static `void writeFutureResult(Bundler,`
`BundlerType, T, FutureResultWriter<E>)` method. This should register with the
passed in future for results, and when a result is given, call
`resultWriter.onSuccess(value)`. If an exception is given,
`resultWriter.onFailure(exception)` should be called.
Finally, future wrappers must also have a static `T<Map<Profile, E>>`
`groupResults(Map<Profile, T<E>> results)` method which converts a map from
profile to future, into a future of a map from profile to result.
`CrossProfileCallbackMultiMerger` can be used to make this logic easier.
For example:
```java
/** A very simple 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
در سازنده استفاده کنید.
با این تغییر، زمانی که نمایه دیگر قفل نیست، از در دسترس بودن مطلع می شوید و می توانید تماس های متقابل پروفایل برقرار کنید. این مسئولیت شماست که اطمینان حاصل کنید که تماس های شما فقط به حافظه رمزگذاری شده دستگاه دسترسی دارند.