İleri düzey konular

Bu bölümler referans amaçlıdır ve bunları baştan sona okumanız gerekmez.

Çerçeve API'lerini kullanın:

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şisel0 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.
İkincilbirincil tanımlanırsa ikincil, birincil olmayan profildir.
TedarikçiBirincil 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.

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 varsa serviceSuperclass= 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 bunu connector= 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.