Bu bölümler referans amaçlıdır ve bunları baştan sona okumanız gerekmez.
Kullanıcı izni isteme
Çerçeve API'lerini kullanın:
CrossProfileApps.canInteractAcrossProfiles()
CrossProfileApps.canRequestInteractAcrossProfiles()
CrossProfileApps.createRequestInteractAcrossProfilesIntent()
CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED
Bu API'ler, daha tutarlı bir API yüzeyi için SDK'ya sarmalanır (ör. UserHandle nesnelerinden kaçınılır), ancak şimdilik bunları doğrudan çağırabilirsiniz.
Uygulaması basittir: Etkileşim kurabiliyorsanız devam edin. Aksi takdirde, istekte bulunabilir ve ardından kullanıcı isteminizi/banner'ınızı/ipucunuzu/vb. gösterebilirsiniz. Kullanıcı Ayarlar'a gitmeyi kabul ederse istek niyetini oluşturun ve kullanıcıyı oraya göndermek için Context#startActivity
'ü kullanın. Bu özelliğin ne zaman değiştiğini algılamak için yayını kullanabilir veya kullanıcı geri geldiğinde tekrar kontrol edebilirsiniz.
Bunu test etmek için iş profilinizde TestDPC'yi açmanız, en alta gidip paket adınızı bağlı uygulamalar izin verilenler listesine eklemeyi seçmeniz gerekir. Bu işlem, yöneticinin uygulamanızı "izin verilenler listesine eklemesini" taklit eder.
Sözlük
Bu bölümde, profiller arası geliştirmeyle ilgili temel terimler tanımlanmaktadır.
Profiller Arası Yapılandırma
Profiller arası yapılandırma, ilgili profiller arası sağlayıcı sınıflarını bir araya getirir ve profiller arası özellikler için genel yapılandırma sağlar.
Genellikle kod tabanı başına bir @CrossProfileConfiguration
ek açıklama bulunur ancak bazı karmaşık uygulamalarda birden fazla olabilir.
Profil Bağlantısı
Bağlantılayıcı, profiller arasındaki bağlantıları yönetir. Genellikle her çapraz profil türü belirli bir bağlayıcıyı işaret eder. Tek bir yapılandırmadaki her çapraz profil türü aynı bağlayıcıyı kullanmalıdır.
Profiller Arası Sağlayıcı Sınıfı
Çapraz Profil Sağlayıcı Sınıfı, ilgili Çapraz Profil Türlerini gruplandırır.
Mediator
Aracılar, yüksek düzey ve düşük düzey kod arasında yer alır, çağrıları doğru profillere dağıtır ve sonuçları birleştirir. Profil bilincine sahip olması gereken tek kod budur. Bu, SDK'ya yerleştirilmiş bir şey değil, mimari bir kavramdır.
Profiller Arası Türü
Profiller arası tür, @CrossProfile
ek açıklamalı yöntemler içeren bir sınıf veya arayüzdür. Bu türdeki kodun profil bilincine sahip olması gerekmez ve ideal olarak yalnızca yerel verilerine göre hareket etmelidir.
Profil Türleri
Profil Türü | |
---|---|
Şu anki adı | Çalıştırdığımız etkin profil. |
Diğer | (varsa) Çalıştırmadığımız profil. |
Kişisel | 0 numaralı kullanıcı, cihazdaki devre dışı bırakılamayan profildir. |
İş | Genellikle 10. kullanıcıdır ancak daha yüksek olabilir. Açılıp kapatılabilir. İş uygulamalarını ve verilerini içermek için kullanılır. |
Birincil | İsteğe bağlı olarak uygulama tarafından tanımlanır. Her iki profilin birleştirilmiş görünümünü gösteren profil. |
İkincil | birincil tanımlanırsa ikincil, birincil olmayan profildir. |
Tedarikçi | Birincil profilin tedarikçileri her iki profildir, ikincil profilin tedarikçileri ise yalnızca ikincil profildir. |
Profil tanımlayıcısı
Bir profil türünü (kişisel veya iş) temsil eden sınıf. Bunlar, birden fazla profilde çalışan yöntemler tarafından döndürülür ve bu profillerde daha fazla kod çalıştırmak için kullanılabilir. Bunlar, kolay depolama için int
olarak serileştirilebilir.
Mimari önerilen çözümler
Bu kılavuzda, Android uygulamanızda verimli ve sürdürülebilir profiller arası işlevler oluşturmak için önerilen yapılar özetlenmiştir.
CrossProfileConnector
türünü tekil bir sınıfa dönüştürme
Uygulamanızın yaşam döngüsü boyunca yalnızca tek bir örnek kullanılmalıdır. Aksi takdirde paralel bağlantılar oluşturursunuz. Bu, Dagger gibi bir bağımlılık ekleme çerçevesi kullanılarak veya yeni bir sınıfta ya da mevcut bir sınıfta klasik bir tekil örnek kalıbı kullanılarak yapılabilir.
Oluşturulan Profile örneğini yöntemde oluşturmak yerine, aramayı yaptığınız sırada sınıfınıza ekleyin veya iletin.
Bu sayede, otomatik olarak oluşturulan FakeProfile
örneğini daha sonra birim testlerinize iletebilirsiniz.
Arabulucu kalıbını kullanmayı düşünün
Bu yaygın kalıp, mevcut API'lerinizden birini (ör. getEvents()
) tüm arayanları için profil bilinçli hale getirmektir. Bu durumda, mevcut API'niz, oluşturulan profiller arası koda yönelik yeni çağrıyı içeren bir "aracı" yöntemi veya sınıfı haline gelebilir.
Bu sayede, her arayanı profiller arası çağrı yapmayı bilmesini zorunlu tutmazsınız. Bu işlem, API'nizin bir parçası haline gelir.
Uygulama sınıflarınızı bir sağlayıcıda göstermek zorunda kalmamak için bir arayüz yöntemini @CrossProfile
olarak notlandırmayı düşünebilirsiniz.
Bu, bağımlılık ekleme çerçeveleriyle iyi çalışır.
Profiller arası bir çağrıdan veri alıyorsanız hangi profilden geldiğini belirten bir alan ekleyebilirsiniz.
Bu bilgiyi kullanıcı arayüzü katmanında bilmek isteyebilirsiniz (ör.iş öğelerine rozet simgesi ekleme). Bu nedenle, bu yöntemi kullanmak iyi bir uygulama olabilir. Paket adları gibi veri tanımlayıcıları artık bu olmadan benzersiz değilse de gerekli olabilir.
Profiller arası
Bu bölümde, kendi profiller arası etkileşimlerinizi nasıl oluşturacağınız açıklanmaktadır.
Birincil Profiller
Bu dokümandaki örneklerdeki çağrıların çoğu, iş, kişisel ve her ikisi de dahil olmak üzere hangi profillerde çalıştırılacağıyla ilgili net talimatlar içerir.
Uygulamanızın yalnızca tek bir profilde birleştirilmiş bir deneyim sunması durumunda bu kararı, uygulamayı çalıştırdığınız profile göre belirlemek isteyebilirsiniz. Bu nedenle, kod tabanınızın "if-else" profil koşullarıyla dolu olmasını önlemek için bunu da dikkate alan benzer ve kullanışlı yöntemler vardır.
Bağlantılayıcı örneğinizi oluştururken hangi profil türünün "birincil" olduğunu belirtebilirsiniz (ör. "İŞ"). Bu sayede aşağıdakiler gibi ek seçenekler kullanılabilir:
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();
Profiller Arası Türler
@CrossProfile
ek açıklamalı bir yöntem içeren sınıflar ve arayüzler, Çapraz Profil Türleri olarak adlandırılır.
Profiller arası türlerin uygulanması, çalıştırıldığı profilden bağımsız olmalıdır. Diğer yöntemlere çağrı yapmalarına izin verilir ve genel olarak tek bir profilde çalışıyormuş gibi çalışırlar. Yalnızca kendi profillerindeki eyalete erişebilirler.
Örnek profiller arası tür:
public class Calculator {
@CrossProfile
public int add(int a, int b) {
return a + b;
}
}
Sınıf notu
En güçlü API'yi sağlamak için her profiller arası tür için bağlayıcıyı aşağıdaki gibi belirtmeniz gerekir:
@CrossProfile(connector=MyProfileConnector.class)
public class Calculator {
@CrossProfile
public int add(int a, int b) {
return a + b;
}
}
Bu isteğe bağlıdır ancak oluşturulan API'nin türler konusunda daha spesifik ve derleme zamanındaki kontrol konusunda daha katı olacağı anlamına gelir.
Arayüzler
Bir arayüzdeki yöntemleri @CrossProfile
olarak ekleyerek bu yöntemin profiller arasında erişilebilir olması gereken bir uygulaması olabileceğini belirtirsiniz.
Profiller Arası Sağlayıcı'da bir Profiller Arası arayüzünün herhangi bir uygulamasını döndürebilirsiniz. Bunu yaparak, bu uygulamanın profiller arasında erişilebilir olması gerektiğini belirtirsiniz. Uygulama sınıflarına ek açıklama eklemeniz gerekmez.
Profiller Arası Sağlayıcılar
Her profiller arası tür, @CrossProfileProvider
notu eklenmiş bir yöntemle sağlanmalıdır. Bu yöntemler, her profiller arası çağrı yapıldığında çağrılır. Bu nedenle, her tür için tekil öğeler bulundurmanız önerilir.
Marka
Sağlayıcının, hiçbir bağımsız değişken veya tek bir Context
bağımsız değişkeni alan herkese açık bir kurucusu olmalıdır.
Sağlayıcı Yöntemleri
Sağlayıcı yöntemleri, hiçbir bağımsız değişken veya tek bir Context
bağımsız değişkeni almalıdır.
Bağımlılık Enjeksiyonu
Bağımlılıkları yönetmek için Dagger gibi bir bağımlılık ekleme çerçevesi kullanıyorsanız bu çerçevenin, profiller arası türlerinizi normalde yaptığınız gibi oluşturmasını ve ardından bu türleri sağlayıcı sınıfınıza eklemesini öneririz. @CrossProfileProvider
yöntemleri daha sonra bu yerleştirilmiş örnekleri döndürebilir.
Profil Bağlantısı
Her profiller arası yapılandırmada, diğer profille bağlantıyı yönetmekten sorumlu tek bir Profil Bağlantısı olmalıdır.
Varsayılan Profil Bağlantısı
Bir kod tabanında yalnızca bir profiller arası yapılandırma varsa kendi profil bağlayıcınızı oluşturmaktan kaçınabilir ve com.google.android.enterprise.connectedapps.CrossProfileConnector
kullanabilirsiniz. Hiçbiri belirtilmezse varsayılan olarak bu ayar kullanılır.
Profiller Arası Bağlayıcı'yı oluştururken oluşturucuda bazı seçenekleri belirtebilirsiniz:
Planlanmış Yürütücü Hizmeti
SDK tarafından oluşturulan mesaj dizileri üzerinde kontrol sahibi olmak istiyorsanız
#setScheduledExecutorService()
Cilt
Profil bağlama ile ilgili özel ihtiyaçlarınız varsa
#setBinder
öğesini kullanın. Bu, büyük olasılıkla yalnızca cihaz politikası denetleyicileri tarafından kullanılır.
Özel Profil Bağlayıcısı
Bazı yapılandırmaları (CustomProfileConnector
kullanarak) ayarlayabilmek için özel bir profil bağlayıcısına ihtiyacınız vardır. Ayrıca, tek bir kod tabanında birden fazla bağlayıcıya ihtiyacınız varsa (örneğin, birden fazla işleminiz varsa işlem başına bir bağlayıcı kullanmanızı öneririz) özel bir bağlayıcıya ihtiyacınız vardır.
ProfileConnector
oluştururken aşağıdaki gibi görünmelidir:
@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
Oluşturulan hizmetin adını değiştirmek için (
AndroidManifest.xml
dosyanızda referans verilmelidir)serviceClassName=
değerini kullanın.primaryProfile
Birincil profili belirtmek için
primaryProfile
simgesini kullanın.availabilityRestrictions
SDK'nın bağlantılara ve profil kullanılabilirliğine uyguladığı kısıtlamaları değiştirmek için
availabilityRestrictions
simgesini kullanın.
Cihaz Politikası Denetleyicileri
Uygulamanız bir cihaz politikası denetleyicisiyse DeviceAdminReceiver
'nize referans veren bir DpcProfileBinder
örneği belirtmeniz gerekir.
Kendi profil bağlayıcınızı uyguluyorsanız:
@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();
}
}
veya varsayılan CrossProfileConnector
değerini kullanarak:
CrossProfileConnector connector =
CrossProfileConnector.builder(context).setBinder(new DpcProfileBinder(new
ComponentName("com.google.testdpc", "AdminReceiver"))).build();
Profiller Arası Yapılandırma
@CrossProfileConfiguration
ek açıklama, yöntem çağrılarını doğru şekilde dağıtmak için bir bağlayıcı kullanarak tüm profiller arası türleri birbirine bağlamak için kullanılır. Bunu yapmak için sınıfa her sağlayıcıyı işaret eden @CrossProfileConfiguration
ek açıklamasını ekleriz. Örneğin:
@CrossProfileConfiguration(providers = {TestProvider.class})
public abstract class TestApplication {
}
Bu işlem, tüm profiller arası türlerde aynı profil bağlayıcısının kullanıldığını veya hiçbir bağlayıcının belirtilmediğini doğrular.
serviceSuperclass
Oluşturulan hizmet varsayılan olarak
android.app.Service
üst sınıfını kullanır. Üst sınıf olarak farklı bir sınıfa (android.app.Service
sınıfının alt sınıfı olmalıdır) ihtiyacınız varsaserviceSuperclass=
değerini belirtin.serviceClass
Belirtilirse hizmet oluşturulmaz. Bu, kullandığınız profil bağlayıcısındaki
serviceClassName
ile eşleşmelidir. Özel hizmetiniz, oluşturulan_Dispatcher
sınıfını kullanarak aramaları şu şekilde göndermelidir:
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;
}
}
Bu, profiller arası bir aramadan önce veya sonra ek işlemler yapmanız gerektiğinde kullanılabilir.
Bağlayıcı
Varsayılan
CrossProfileConnector
dışında bir konnektör kullanıyorsanız bunuconnector=
kullanarak belirtmeniz gerekir.
Görünürlük
Uygulamanızın profiller arası etkileşimde bulunan her bölümü, Profil Bağlayıcınızı görebilmelidir.
@CrossProfileConfiguration
notu eklenmiş sınıfınız, uygulamanızda kullanılan her sağlayıcıyı görebilmelidir.
Senkronize Aramalar
Bağlı Uygulamalar SDK'sı, kaçınılmaz olduğu durumlarda senkronize (engelleyici) çağrıları destekler. Ancak bu çağrıların kullanılmasının bazı dezavantajları vardır (ör. çağrıların uzun süre boyunca engellenmesi olasılığı). Bu nedenle, mümkün olduğunda senkronize çağrılardan kaçınmanız önerilir. Eşzamansız aramaları kullanma hakkında bilgi edinmek için Eşzamansız aramalar başlıklı makaleyi inceleyin .
Bağlantı Sahipleri
Senkronize aramalar kullanıyorsanız profiller arası arama yapmadan önce kayıtlı bir bağlantı sahibi olduğundan emin olmanız gerekir. Aksi takdirde bir istisna atılır. Daha fazla bilgi için Bağlantı Sahipleri bölümüne bakın.
Bağlantı sahibi eklemek için herhangi bir nesneyle (potansiyel olarak profiller arası çağrı yapan nesne örneğiyle) ProfileConnector#addConnectionHolder(Object)
işlevini çağırın. Bu işlem, bu nesnenin bağlantıyı kullandığını kaydeder ve bağlantı kurmaya çalışır. Bu işlev, herhangi bir eşzamanlı çağrı yapılmadan önce çağrılmalıdır. Bu, engellenmeyen bir çağrı olduğundan, aramanızı yaptığınızda bağlantının hazır olmaması (veya mümkün olmaması) mümkündür. Bu durumda, normal hata işleme davranışı geçerli olur.
ProfileConnector#addConnectionHolder(Object)
işlevini çağırırken uygun profiller arası izinlere sahip değilseniz veya bağlanacak profil yoksa hata atılmaz ancak bağlı geri çağırma işlevi hiçbir zaman çağrılmaz. İzin daha sonra verilirse veya diğer profil kullanılabilir hale gelirse bağlantı kurulur ve geri arama yapılır.
Alternatif olarak ProfileConnector#connect(Object)
, nesneyi bağlantı tutucusu olarak ekleyecek ve bağlantı kuracak ya da UnavailableProfileException
atacak bir engelleme yöntemidir. Bu yöntem, kullanıcı arayüzü iş parçacığında
çağrılamaz.
ProfileConnector#connect(Object)
ve benzer ProfileConnector#connect
çağrıları, kapatıldıktan sonra bağlantı tutucusunu otomatik olarak kaldıran otomatik kapanan nesneler döndürür. Bu sayede aşağıdaki gibi kullanımlar yapılabilir:
try (ProfileConnectionHolder p = connector.connect()) {
// Use the connection
}
Senkronize aramaları tamamladıktan sonra ProfileConnector#removeConnectionHolder(Object)
numaralı telefonu aramanız gerekir. Tüm bağlantı sahipleri kaldırıldığında bağlantı kapatılır.
Bağlantı
Bağlantı durumu değiştiğinde bilgilendirilmek için bağlantı dinleyicisi kullanılabilir. connector.utils().isConnected
ise bağlantı olup olmadığını belirlemek için kullanılabilir. Örneğin:
// Only use this if using synchronous calls instead of Futures.
crossProfileConnector.connect(this);
crossProfileConnector.registerConnectionListener(() -> {
if (crossProfileConnector.utils().isConnected()) {
// Make cross-profile calls.
}
});
Eşzamansız Aramalar
Profil bölme noktasında sunulan her yöntem, engelleyen (senkron) veya engellemeyen (asynchron) olarak tanımlanmalıdır. Asenkron veri türü (ör. ListenableFuture
) döndüren veya geri çağırma işlevi parametresi kabul eden tüm yöntemler, engellemeyen olarak işaretlenir. Diğer tüm yöntemler engelleme olarak işaretlenir.
Eşzamansız çağrılar önerilir. Senkron çağrılar kullanmanız gerekiyorsa Senkron Çağrıları başlıklı makaleyi inceleyin.
Geri aramalar
Engellemeyen çağrının en temel türü, parametrelerinden biri olarak sonuçla birlikte çağrılacak bir yöntem içeren bir arayüz kabul eden boş bir yöntemdir. Bu arayüzlerin SDK ile çalışabilmesi için arayüzün @CrossProfileCallback
notu eklenmelidir. Örneğin:
@CrossProfileCallback
public interface InstallationCompleteListener {
void installationComplete(int state);
}
Bu arayüz daha sonra @CrossProfile
ek açıklamalı bir yöntemde parametre olarak kullanılabilir ve her zamanki gibi çağrılabilir. Örneğin:
@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
});
Bu arayüz, sıfır veya bir parametre alan tek bir yöntem içeriyorsa aynı anda birden fazla profile yapılan çağrılarda da kullanılabilir.
Geri çağırma işlevi kullanılarak herhangi bir sayıda değer iletilebilir ancak bağlantı yalnızca ilk değer için açık tutulur. Daha fazla değer almak için bağlantıyı açık tutma hakkında bilgi edinmek üzere Bağlantı Tutucular'a bakın.
Geri çağırma içeren senkronize yöntemler
SDK ile geri çağırma işlevlerini kullanmanın alışılmışın dışında bir özelliği, teknik olarak geri çağırma işlevi kullanan bir eşzamanlı yöntem yazabilmenizdir:
public void install(InstallationCompleteListener callback) {
callback.installationComplete(1);
}
Bu durumda, geri çağırma işlevine rağmen yöntem aslında eşzamanlıdır. Aşağıdaki kod doğru şekilde yürütülür:
System.out.println("This prints first");
installer.install(() -> {
System.out.println("This prints second");
});
System.out.println("This prints third");
Ancak SDK kullanılarak çağrıldığında bu işlev aynı şekilde çalışmaz. "Bu üçüncü baskıyı yapar" yazdırılmadan önce yükleme yönteminin çağrılacağı garanti edilmez. SDK tarafından asenkron olarak işaretlenen bir yöntemin kullanımlarında, yöntemin ne zaman çağrılacağı hakkında hiçbir varsayım yapılmamalıdır.
Basit Geri Çağırmalar
"Basit geri aramalar", profiller arası arama yaparken ek özelliklere olanak tanıyan daha kısıtlayıcı bir geri arama biçimidir. Basit arayüzler, sıfır veya bir parametre alabilen tek bir yöntem içermelidir.
@CrossProfileCallback
ek açıklamalarında simple=true
belirterek geri çağırma arayüzünün kalmasını zorunlu kılabilirsiniz.
Basit geri çağırmalar .both()
, .suppliers()
ve diğer yöntemlerle kullanılabilir.
Bağlantı Sahipleri
Asenkron çağrı yapılırken (geri çağırma veya gelecekler kullanılarak) çağrı yapılırken bir bağlantı tutucusu eklenir ve bir istisna veya değer iletildiğinde kaldırılır.
Geri çağırma kullanarak birden fazla sonucun iletilmesini bekliyorsanız geri çağırmayı bağlantı tutucusu olarak manuel olarak eklemeniz gerekir:
MyCallback b = //...
connector.addConnectionHolder(b);
profileMyClass.other().registerListener(b);
// Now the connection will be held open indefinitely, once finished:
connector.removeConnectionHolder(b);
Bu, try-with-resources bloğuyla da kullanılabilir:
MyCallback b = //...
try (ProfileConnectionHolder p = connector.addConnectionHolder(b)) {
profileMyClass.other().registerListener(b);
// Other things running while we expect results
}
Geri arama veya gelecekte arama seçeneğiyle bir arama yaparsak bağlantı, bir sonuç döndürülene kadar açık tutulur. Bir sonucun iletilmeyeceğini belirlersek geri çağırma işlevini veya future işlevini bağlantı tutucusu olarak kaldırırız:
connector.removeConnectionHolder(myCallback);
connector.removeConnectionHolder(future);
Daha fazla bilgi için Bağlantı Sahipleri başlıklı makaleyi inceleyin.
Vadeli İşlemler
Gelecek sözleşmeleri de SDK tarafından yerel olarak desteklenir. Yerel olarak desteklenen tek gelecek türü ListenableFuture
olsa da özel gelecek türleri kullanılabilir.
Gelecekleri kullanmak için desteklenen bir Future türünü, çapraz profil yönteminin dönüş türü olarak tanımlamanız ve ardından normal şekilde kullanmanız yeterlidir.
Bu, geri çağırmalarla aynı "olağan dışı özelliğe" sahiptir.Gelecek döndüren eşzamanlı bir yöntem (ör. immediateFuture
kullanılarak), mevcut profilde çalıştırıldığında başka bir profilde çalıştırıldığından farklı davranır. SDK tarafından asenkron olarak işaretlenen bir yöntemin tüm kullanımlarında, yöntemin ne zaman çağrılacağı hakkında hiçbir varsayım yapılmamalıdır.
Sohbetler
Ana mesaj dizisinde profiller arası gelecek veya geri çağırma sonucunu engellemeyin . Bunu yaparsanız bazı durumlarda kodunuz süresiz olarak engellenir. Bunun nedeni, diğer profille bağlantının da ana mesaj dizisinde kurulmasıdır. Profiller arası sonuç beklenirken engellenirse bu işlem hiçbir zaman gerçekleşmez.
Kullanılabilirlik
Müsaitlik durumu dinleyicisi, müsaitlik durumu değiştiğinde bilgilendirilmek için kullanılabilir. connector.utils().isAvailable
ise başka bir profilin kullanılıp kullanılamayacağını belirlemek için kullanılabilir. Örneğin:
crossProfileConnector.registerAvailabilityListener(() -> {
if (crossProfileConnector.utils().isAvailable()) {
// Show cross-profile content
} else {
// Hide cross-profile content
}
});
Bağlantı Sahipleri
Bağlantı sahipleri, profiller arası bağlantının kurulduğunu ve etkin tutulduğunu belirten ve bu konuda ilgisi olan rastgele nesnelerdir.
Varsayılan olarak, asenkron çağrılar yapılırken arama başladığında bir bağlantı tutucusu eklenir ve herhangi bir sonuç veya hata oluştuğunda kaldırılır.
Bağlantı üzerinde daha fazla kontrol sahibi olmak için bağlantı sahipleri manuel olarak da eklenebilir ve kaldırılabilir. Bağlantı sahipleri connector.addConnectionHolder
kullanılarak eklenebilir ve connector.removeConnectionHolder
kullanılarak kaldırılabilir.
En az bir bağlantı tutucusu eklendiğinde SDK, bağlantıyı sürdürmeye çalışır. Hiçbir bağlantı sahibi eklenmemişse bağlantı kapatılabilir.
Eklediğiniz tüm bağlantı sahiplerinin referansını tutmanız ve artık alakalı olmadığında kaldırmanız gerekir.
Eşzamanlı çağrılar
Senkronize çağrılar yapmadan önce bir bağlantı tutucusu eklenmelidir. Bu işlem herhangi bir nesne kullanılarak yapılabilir. Ancak artık senkronize çağrı yapmanız gerekmediğinde kaldırılabilmesi için bu nesneyi takip etmeniz gerekir.
Eşzamansız çağrılar
Asenkron çağrılar yapılırken bağlantı tutucular otomatik olarak yönetilir. Böylece, çağrı ile ilk yanıt veya hata arasında bağlantı açık olur. Bağlantının bundan sonra da devam etmesini istiyorsanız (ör. tek bir geri çağırma kullanarak birden fazla yanıt almak için) geri çağırmayı bağlantı tutucusu olarak eklemeniz ve artık daha fazla veri almanız gerekmediğinde kaldırmanız gerekir.
Hata İşleme
Varsayılan olarak, diğer profil müsait olmadığında diğer profile yapılan tüm çağrılar UnavailableProfileException
atılmasına (veya Future'a geçirilmesine ya da asynkron çağrı için hata geri çağırma işlevine) neden olur.
Geliştiriciler bu durumu önlemek için #both()
veya #suppliers()
kullanabilir ve kodlarını, sonuçta elde edilen listedeki herhangi bir sayıda girişle (diğer profil kullanılamıyorsa 1, kullanılabiliyorsa 2) işlem yapacak şekilde yazabilir.
İstisnalar
Geçerli profile yapılan bir çağrıdan sonra gerçekleşen ve işaretlenmemiş istisnalar her zamanki gibi dağıtılır. Bu, çağrıyı yapmak için kullanılan yöntemden (#current()
, #personal
, #both
vb.) bağımsız olarak geçerlidir.
Diğer profile yapılan bir çağrıdan sonra gerçekleşen ve işaretlenmemiş istisnalar, neden olarak orijinal istisnayla birlikte bir ProfileRuntimeException
atılmasına neden olur. Bu, arama yapmak için kullanılan yöntemden (#other()
, #personal
, #both
vb.) bağımsız olarak geçerlidir.
ifAvailable
UnavailableProfileException
örneklerini yakalayıp bunlarla uğraşmak yerine, UnavailableProfileException
atma yerine döndürülecek bir varsayılan değer sağlamak için .ifAvailable()
yöntemini kullanabilirsiniz.
Örneğin:
profileNotesDatabase.other().ifAvailable().getNumberOfNotes(/* defaultValue= */ 0);
Test
Kodunuzu test edilebilir hale getirmek için profil bağlayıcınızın örneklerini, onu kullanan tüm kodlara eklemeniz gerekir (ör. profil kullanılabilirliğini kontrol etmek, manuel olarak bağlanmak için). Ayrıca, profil bilinçli türlerinizin örneklerini, kullanıldıkları yerlere eklemeniz gerekir.
Testlerde kullanılabilecek, bağlayıcınızın ve türlerinizin sahte örneklerini sağlarız.
Öncelikle test bağımlılıkları ekleyin:
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'
Ardından, test sınıfınızı @CrossProfileTest
ile ekleyerek test edilecek @CrossProfileConfiguration
ek açıklamalı sınıfı tanımlayın:
@CrossProfileTest(configuration = MyApplication.class)
@RunWith(RobolectricTestRunner.class)
public class NotesMediatorTest {
}
Bu, yapılandırmada kullanılan tüm tür ve bağlayıcılar için sahte öğelerin oluşturulmasına neden olur.
Testinizde bu sahte öğelerin örneklerini oluşturun:
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();
Profil durumunu ayarlayın:
connector.setRunningOnProfile(PERSONAL);
connector.createWorkProfile();
connector.turnOffWorkProfile();
Sahte bağlayıcıyı ve çapraz profil sınıfını test altındaki kodunuza aktarın ve ardından çağrı yapın.
Aramalar doğru hedefe yönlendirilir ve bağlantısı kesilmiş veya müsait olmayan profillere arama yapıldığında istisnalar atılır.
Desteklenen Türler
Aşağıdaki türler, sizden herhangi bir çaba gerektirmeden desteklenir. Bunlar, tüm profiller arası çağrılar için bağımsız değişken veya dönüş türü olarak kullanılabilir.
- Temel öğeler (
byte
,short
,int
,long
,float
,double
,char
,boolean
), - Kutu içinde temel öğeler (
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
'ü uygulayan her şey,java.io.Serializable
'ü uygulayan her şey,- Tek boyutlu ilkel olmayan diziler,
java.util.Optional
,java.util.Collection
,java.util.List
,java.util.Map
,java.util.Set
,android.util.Pair
,com.google.common.collect.ImmutableMap
.
Desteklenen tüm genel türler (örneğin, java.util.Collection
), tür parametresi olarak desteklenen herhangi bir türe sahip olabilir. Örneğin:
java.util.Collection<java.util.Map<java.lang.String,MySerializableType[]>>
geçerli bir türdür.
Vadeli İşlemler
Aşağıdaki türler yalnızca döndürme türü olarak desteklenir:
com.google.common.util.concurrent.ListenableFuture
Özel Paketlenebilir Sarmalayıcılar
Türü listede yoksa önce android.os.Parcelable
veya java.io.Serializable
'ı doğru şekilde uygulayıp uygulayamayacağınızı düşünün. Bu durumda, türünüze destek eklemek için paketlenebilir sarmalayıcıları göremez.
Özel Gelecek Sarmalayıcıları
Önceki listede bulunmayan bir gelecek türünü kullanmak istiyorsanız destek eklemek için gelecek sarmalayıcılarına bakın.
Paketlenebilir sarmalayıcılar
Paketlenebilir sarmalayıcılar, SDK'nın değiştirilemeyen, paketlenemeyen türler için destek ekleme yöntemidir. SDK, birçok tür için sarmalayıcılar içerir ancak kullanmanız gereken tür dahil edilmemişse kendi sarmalayıcınızı yazmanız gerekir.
Paketlenebilir sarmalayıcı, başka bir sınıfı sarmalamak ve paketlenebilir hale getirmek için tasarlanmış bir sınıftır. Tanımlanmış statik bir sözleşmeyi takip eder ve SDK'ya kaydedilir. Bu sayede, belirli bir türü paketlenebilir bir türe dönüştürmek ve bu türü paketlenebilir türden ayıklamak için kullanılabilir.
Ek Açıklama
Paketlenebilir sarmalayıcı sınıfı, @CustomParcelableWrapper
olarak ek açıklamaya sahip olmalıdır. Sarmalanmış sınıf originalType
olarak belirtilmelidir. Örneğin:
@CustomParcelableWrapper(originalType=ImmutableList.class)
Biçim
Paketlenebilir sarmalayıcılar Parcelable
'ü doğru şekilde uygulamalı, sarmalanmış türü sarmalayan statik bir W of(Bundler, BundlerType, T)
yöntemine ve sarmalanmış türü döndüren statik olmayan bir T get()
yöntemine sahip olmalıdır.
SDK, türe sorunsuz destek sağlamak için bu yöntemleri kullanır.
Paketleyici
Genel türlerin (liste ve harita gibi) sarmalanmasına izin vermek için of
yöntemine, tüm desteklenen türleri bir Parcel
'e okuyabilen (#readFromParcel
kullanarak) ve yazabilen (#writeToParcel
kullanarak) bir Bundler
ve yazılacak şekilde tanımlanan türü temsil eden bir BundlerType
iletilir.
Bundler
ve BundlerType
örnekleri de paketlenebilirdir ve paketlenebilir sarmalayıcıyı yeniden oluştururken kullanılabilmesi için paketlenebilir sarmalayıcının paketlenmesi kapsamında yazılmalıdır.
BundlerType
genel bir türü temsil ediyorsa .typeArguments()
çağrılarak tür değişkenleri bulunabilir. Her tür bağımsız değişkeni de bir BundlerType
bağımsız değişkenidir.
Örnek için ParcelableCustomWrapper
bölümüne bakın:
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'ya kaydolun
Oluşturulduktan sonra özel paketlenebilir sarmalayıcınızı kullanmak için SDK'ya kaydetmeniz gerekir.
Bunu yapmak için sınıftaki bir CustomProfileConnector
ekinde veya CrossProfile
ekinde parcelableWrappers={YourParcelableWrapper.class}
belirtin.
Future Wrappers
Gelecek sarmalayıcıları, SDK'nın profiller genelinde gelecekler için destek ekleme şeklidir. SDK, varsayılan olarak ListenableFuture
için destek içerir ancak diğer gelecek türleri için desteği kendiniz ekleyebilirsiniz.
Future Wrapper, belirli bir Future türünü sarmalamak ve SDK'ya sunmak için tasarlanmış bir sınıftır. Tanımlanmış statik bir sözleşmeye uyar ve SDK'ya kaydedilmelidir.
Ek Açıklama
Gelecekteki sarmalayıcı sınıfı, @CustomFutureWrapper
olarak ek açıklamayla belirtilmeli ve sarmalanmış sınıf originalType
olarak belirtilmelidir. Örneğin:
@CustomFutureWrapper(originalType=SettableFuture.class)
Biçim
Gelecekteki sarmalayıcılar com.google.android.enterprise.connectedapps.FutureWrapper
öğesini genişletmelidir.
Gelecekteki sarmalayıcılarda, sarmalayıcı örneği oluşturan statik bir W create(Bundler, BundlerType)
yöntemi bulunmalıdır. Aynı zamanda, sarmalanmış gelecek türü örneği oluşturulur. Bu değer, statik olmayan bir T
getFuture()
yöntemi tarafından döndürülmelidir. Sonuç veya throwable'ı sarmalanmış geleceğe iletmek için onResult(E)
ve onException(Throwable)
yöntemleri uygulanmalıdır.
Gelecekteki sarmalayıcılarda statik bir void writeFutureResult(Bundler,
BundlerType, T, FutureResultWriter<E>)
yöntemi de olmalıdır. Bu, gelecekte sonuçlar için geçirilen ile kaydedilir ve bir sonuç verildiğinde resultWriter.onSuccess(value)
çağrılır. İstisna varsa resultWriter.onFailure(exception)
çağrılmalıdır.
Son olarak, gelecekteki sarmalayıcılarda, profilden geleceğe bir eşlemeyi profilden sonuca bir eşlemenin geleceğine dönüştüren statik bir T<Map<Profile, E>>
groupResults(Map<Profile, T<E>> results)
yöntemi de olmalıdır.
CrossProfileCallbackMultiMerger
, bu mantığı kolaylaştırmak için kullanılabilir.
Örneğin:
/** 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'ya kaydolun
Oluşturulan özel gelecek sarmalayıcınızı kullanmak için SDK'ya kaydetmeniz gerekir.
Bunu yapmak için sınıftaki bir CustomProfileConnector
ekinde veya CrossProfile
ekinde futureWrappers={YourFutureWrapper.class}
belirtin.
Doğrudan önyükleme modu
Uygulamanız doğrudan önyükleme modunu destekliyorsa profilin kilidi açılmadan önce profiller arası çağrılar yapmanız gerekebilir. SDK varsayılan olarak yalnızca diğer profilin kilidi açıldığında bağlantılara izin verir.
Bu davranışı değiştirmek için özel profil bağlayıcısı kullanıyorsanız availabilityRestrictions=AvailabilityRestrictions.DIRECT_BOOT_AWARE
şunları belirtmeniz gerekir:
@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
kullanıyorsanız oluşturucuda .setAvailabilityRestrictions(AvailabilityRestrictions.DIRECT_BOOT
_AWARE
seçeneğini kullanın.
Bu değişiklikle birlikte, diğer profilin kilidi açılmadığında müsaitlik durumu hakkında bilgilendirilir ve profiller arası arama yapabilirsiniz. Aramalarınızın yalnızca cihazda şifrelenmiş depolama alanına erişmesini sağlamak sizin sorumluluğunuzdadır.