Tematy zaawansowane

Te sekcje mają charakter informacyjny i nie musisz ich czytać. od góry do dołu.

Użyj interfejsów API platformy:

Te interfejsy API zostaną dołączone do pakietu SDK, aby zapewnić większą spójność interfejsu API (np. unikanie obiektów UserHandle), ale na razie możesz je wywoływać bezpośrednio.

Implementacja jest prosta: jeśli możesz wejść w interakcję, nie zwlekaj. Jeśli nie, ale możesz o to poprosić, a następnie wyświetlać użytkownikowi prompt, baner, etykietkę itp. Jeśli użytkownik zgadza się przejść do Ustawień, stworzyć intencję żądania i wykorzystać Context#startActivity, aby przekierować do niego użytkownika. Możesz użyć transmisji aby wykryć, kiedy dana umiejętność się zmieni, lub sprawdzić ponownie, gdy użytkownik z powrotem.

Aby to sprawdzić, musisz otworzyć TestDPC w swoim profilu służbowym, przejść do na dole i wybierz, aby dodać nazwę pakietu do listy dozwolonych połączonych aplikacji. Ten imituje atrybut „lista dozwolonych” administratora do aplikacji.

Słowniczek

W tej sekcji zdefiniowano kluczowe terminy związane z programowaniem obejmującym wiele profili.

Konfiguracja wielu profili

Konfiguracja wielu profili grupuje powiązanych dostawców dla wielu profili Klasy i zapewniają ogólną konfigurację na potrzeby funkcji dostępnych na różnych profilach. Zwykle istnieje 1 adnotacja @CrossProfileConfiguration na bazę kodu , ale w niektórych złożonych aplikacjach może być ich kilka.

Oprogramowanie sprzęgające profilu

Oprogramowanie sprzęgające zarządza połączeniami między profilami. Zwykle każdy profil wieloprofilowy type będzie wskazywać konkretne oprogramowanie sprzęgające. Wszystkie typy wielu profili w jednym musi używać tego samego oprogramowania sprzęgającego.

Klasa dostawcy różnych profili

Klasa dostawcy wielu profili grupuje powiązane typy różnych profili.

Mediator

Mediacja pośredniczy między kwestią wysokiego i niskiego poziomu, rozdzielając połączenia do poprawne profile i scalanie wyników. To jedyny kod, który musi być z uwzględnieniem profilu użytkownika. To koncepcja architektury, a nie coś wbudowanego pakietu SDK.

Typ profilu krzyżowego

Typ wielu profili to klasa lub interfejs zawierające metody z adnotacjami @CrossProfile Kod tego typu nie musi być oparty na profilu i powinien w miarę możliwości będzie działać na podstawie danych lokalnych.

Typy profili

Typ profilu
BieżąceAktywny profil, na którym wykonujemy pracę.
InneProfil, na którym nie wykonujemy wykonania zadania (jeśli istnieje).
Osobisty charakterUżytkownik 0, profil na urządzeniu, którego nie można Wyłączono.
PracaZwykle użytkownik 10, ale może mieć wyższy poziom, można go włączyć wyłączone, używane do przechowywania aplikacji i danych służbowych.
GłównaOpcjonalnie zdefiniowane przez aplikację. Profil, który powoduje połączenie widoku obu profili.
DodatkowyJeśli zdefiniowany jest główny, pomocniczy to profil, nie jest główny.
DostawcaDostawcy profilu podstawowego to obydwa profile, i dostawcy do profilu dodatkowego jest tylko tym profilem.

Identyfikator profilu

Zajęcia reprezentujące typ profilu (osobisty lub służbowy). Będą to zwracanych przez metody, które działają na wielu profilach i mogą być używane do uruchamiania w tych profilach. Możesz je zserializować do int, aby ułatwić pamięci masowej.

W tym przewodniku przedstawiamy zalecane konstrukcje do tworzenia efektywnych funkcje obejmujące wiele profili w aplikacji na Androida.

Zamień CrossProfileConnector na singleton

W cyklu życia instancji należy używać tylko jednej instancji W przeciwnym razie utworzysz połączenia równoległe. Można to zrobić za pomocą platformy wstrzykiwania zależności, takiej jak Dagger, albo klasyczny singleton , w nowych lub istniejących zajęciach.

Wstrzyknij wygenerowane wystąpienie profilu do klasy lub przekaż je do klasy na potrzeby wywołania, zamiast tworzyć je w metodzie

Dzięki temu możesz później przekazać automatycznie wygenerowaną instancję FakeProfile w testach jednostkowych.

Weź pod uwagę wzór mediatora

Ten wspólny wzorzec polega na utworzeniu jednego z istniejących interfejsów API (np. getEvents()) z profilami wszystkich swoich rozmówców. W takim przypadku istniejący interfejs API może po prostu zostań „mediatorem” metoda lub klasa, która zawiera nowe wywołanie do do śledzenia w wielu profilach.

Dzięki temu nie zmuszamy do połączenia każdego rozmówcy, aby do niego zadzwonił. stanie się częścią Twojego interfejsu API.

Zastanów się, czy zamiast tego dodać do metody interfejsu adnotacja @CrossProfile, aby uniknąć konieczności udostępniania klas implementacji u dostawcy

Świetnie to działa ze platformami wstrzykiwania zależności.

Jeśli otrzymujesz dane z połączenia z różnych profili, zastanów się, czy możesz dodać pole odwołujące się do tego profilu, z którego pochodzą.

To dobra praktyka, bo warto znać ten temat w warstwie interfejsu (np. dodając ikonę plakietki do zadań). Może być też wymagana, jeśli dane identyfikatory, np. nazwy pakietów, nie są już unikalne.

Pojedyncze profile

Z tej sekcji dowiesz się, jak tworzyć własne interakcje w różnych profilach.

Profile główne

Większość wywołań w przykładach w tym dokumencie zawiera wyraźne instrukcje dotyczące wybrać profile, na których chcesz uruchomić aplikację – służbowy, prywatny itp.

W przypadku aplikacji, które mają połączone środowisko tylko na jednym profilu, chcesz, aby ta decyzja zależała od profilu, na którym pracujesz, są też podobne, wygodne metody, które uwzględniają to, aby uniknąć zaśmiecona bazą kodu z warunkami profilu „if-else”.

Podczas tworzenia instancji oprogramowania sprzęgającego możesz określić typ profilu główna (np. „PRACA”). Umożliwi to korzystanie z dodatkowych opcji, takich jak :

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();

Wiele typów profili

Klasy i interfejsy zawierające metodę opisaną w metodzie @CrossProfile są tzw. różnych typów profili.

Implementacja typów profili na wielu profilach powinna być niezależna od profilu, na którym są one używane. Mogą wywoływać inne metody oraz powinny działać tak, jakby były uruchamiane na jednym profilu. Będą mają dostęp tylko do informacji o stanie w swoim profilu.

Przykładowy typ profilu obejmującego:

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

Adnotacja do zajęć

Aby udostępnić najsilniejszy interfejs API, musisz określić oprogramowanie sprzęgające dla każdego krzyża typ profilu, czyli:

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

Jest to opcjonalne, ale oznacza, że wygenerowany interfejs API będzie bardziej precyzyjny w przypadku określonych typów i bardziej rygorystycznie podczas kompilowania.

Interfejsy

Dodając adnotacje do metod w interfejsie jako @CrossProfile, stwierdzasz, że może istnieć jakaś implementacja tej metody, która powinna być dostępna we wszystkich profilach.

Możesz zwrócić dowolną implementację interfejsu wielu profili w funkcji Podsumowanie dostawcy profilu. ta implementacja powinna być dostępna na wielu profilach. Nie musisz dodaj adnotacje do klas implementacji.

Dostawcy różnych profili

Każdy typ profilu krzyżowego musi być podany za pomocą metody dodano adnotację @CrossProfileProvider. Metody te będą wywoływane przy każdym wykonano wywołanie wielu profili, więc zaleca się zachowanie pojedynczych dla każdego typu reklamy.

Zespół

Dostawca musi mieć publiczny konstruktor, który nie przyjmuje żadnych argumentów lub pojedynczy argument Context.

Metody dostawcy

Metody dostawcy nie mogą przyjmować żadnych argumentów lub pojedynczy argument Context.

Wstrzykiwanie zależności

Jeśli do zarządzania używasz platformy wstrzykiwania zależności, takiej jak Dagger zależności, zalecamy użycie platformy do tworzenia jak zwykle, a następnie wstaw je na klasy dostawcy. Metody @CrossProfileProvider mogą wtedy zwrócić te wartości wstrzykiwane instancje.

Oprogramowanie sprzęgające profilu

Każda konfiguracja wielu profili musi mieć jedno oprogramowanie sprzęgające profilu, które odpowiada za zarządzanie połączeniem z innym profilem.

Domyślne oprogramowanie sprzęgające profilu

Jeśli w bazie kodu jest tylko jedna konfiguracja wielu profili, możesz unikaj tworzenia własnego oprogramowania sprzęgającego profilu i używaj com.google.android.enterprise.connectedapps.CrossProfileConnector To jest jest używane domyślnie, jeśli nie określono żadnej wartości.

Podczas tworzenia oprogramowania sprzęgającego obejmującego różne profile możesz określić niektóre opcje w kreator:

  • Zaplanowana usługa wykonawcy

    Jeśli chcesz mieć kontrolę nad wątkami utworzonymi przez pakiet SDK, używaj #setScheduledExecutorService(),

  • Segregator

    Jeśli masz konkretne potrzeby związane z powiązaniem profili, użyj zasady #setBinder. Ten jest prawdopodobnie używany tylko przez kontrolery zasad dotyczących urządzeń.

Niestandardowe oprogramowanie sprzęgające profilu

Aby ustawić pewną konfigurację, musisz mieć niestandardowe oprogramowanie sprzęgające profilu (używasz usługi CustomProfileConnector) – będzie potrzebny, jeśli potrzebujesz kilku w jednej bazie kodu (jeśli na przykład masz wiele procesów, zalecać jedno oprogramowanie sprzęgające na proces).

Gdy tworzysz dane ProfileConnector, powinien on wyglądać tak:

@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

    Aby zmienić nazwę wygenerowanej usługi (do której należy się odwoływać w AndroidManifest.xml), użyj usługi serviceClassName=.

  • primaryProfile

    Aby określić profil główny, użyj formatu primaryProfile.

  • availabilityRestrictions

    Aby zmienić ograniczenia: które SDK umieszcza na temat połączeń i dostępności profilu, użyj availabilityRestrictions

Kontrolery zasad dotyczących urządzeń

Jeśli Twoja aplikacja jest kontrolerem zasad dotyczących urządzeń, musisz określić instancję DpcProfileBinder odnoszące się do: DeviceAdminReceiver.

Jeśli implementujesz własne oprogramowanie sprzęgające profilu:

@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();
  }
}

lub użyj domyślnej wartości CrossProfileConnector:

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

Konfiguracja wielu profili

Adnotacja @CrossProfileConfiguration służy do łączenia ze sobą wszystkich krzyży za pomocą oprogramowania sprzęgającego w celu prawidłowego wysyłania wywołań metody. Do do tego celu dodamy do klasy adnotację @CrossProfileConfiguration, która wskazuje każdego dostawcę, na przykład:

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

Spowoduje to sprawdzenie, czy we wszystkich profilach typy mają takie samo oprogramowanie sprzęgające profilu lub nie określono żadnego oprogramowania sprzęgającego.

  • serviceSuperclass

    Domyślnie wygenerowana usługa będzie używać android.app.Service jako klasa nadrzędna. Jeśli potrzebujesz innej klasy (która musi być podklasą z android.app.Service) na klasę nadrzędną, a następnie określ serviceSuperclass=

  • serviceClass

    Jeśli określisz wartość, usługa nie zostanie wygenerowana. Ta wartość musi być zgodna z serviceClassName w używanym oprogramowaniu sprzęgającym profilu. Twój niestandardowy usługa powinna wysyłać wywołania za pomocą wygenerowanej klasy _Dispatcher jako np.:

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

Możesz tego użyć, jeśli chcesz wykonać dodatkowe działania przed lub po połączenia między profilami.

  • Oprogramowanie sprzęgające

    Jeśli używasz oprogramowania sprzęgającego innego niż domyślny CrossProfileConnector, musisz podać go w connector=.

Widoczność

Każda część aplikacji, która wchodzi w interakcje z innymi profilami, musi mieć możliwość swojego oprogramowania sprzęgającego profilu.

Twoje zajęcia z adnotacjami @CrossProfileConfiguration muszą mieć możliwość wyświetlania wszystkich dostawcy usług.

Wywołania synchroniczne

Pakiet SDK połączonych aplikacji obsługuje synchroniczne (blokujące) wywołania w przypadkach, gdy jest nie do uniknięcia. Stosowanie tego formatu ma jednak wiele wad: tych połączeń (np. potencjał zablokowania połączeń na dłuższy czas), zalecamy unikanie wywołań synchronicznych w miarę możliwości. Do używania asynchroniczne wywołania asynchroniczne połączeń .

Gniazda łączące

Jeśli używasz wywołań synchronicznych, upewnij się, że właściciel połączenia został zarejestrowany przed wykonaniem wywołań w wielu profilach, w przeciwnym razie zostanie zgłoszony. Więcej informacji znajdziesz w sekcji Blokady połączeń.

Aby dodać posiadacza połączenia, zadzwoń pod numer ProfileConnector#addConnectionHolder(Object) z dowolnym obiektem (potencjalnie instancja obiektu, która tworzy połączenia między profilami). Spowoduje to zarejestrowanie, że ten obiekt używa i spróbuje nawiązać połączenie. Ta metoda musi być wywoływana przed wszystkie wywołania synchroniczne. Jest to połączenie nieblokujące, więc jest możliwe, że połączenie nie będzie gotowe (lub może nie być możliwe), dopóki nie przez połączenie telefoniczne. W takim przypadku obowiązuje typowa obsługa błędów.

Jeśli podczas połączenia nie masz odpowiednich uprawnień dotyczących różnych profili ProfileConnector#addConnectionHolder(Object) lub żaden profil nie jest dostępny dla połączenie, nie wystąpi żaden błąd, ale połączone wywołanie zwrotne . Jeśli uprawnienia zostaną przyznane później lub inny profil stanie się połączenie jest dostępne i nawiązywane jest połączenie zwrotne.

Z kolei ProfileConnector#connect(Object) to metoda blokowania, która doda obiekt jako posiadacz połączenia i albo nawiąże połączenie, rzut UnavailableProfileException. Nie można wywołać tej metody z wątek UI.

Połączenia z numerem ProfileConnector#connect(Object) i podobnymi ProfileConnector#connect zwraca obiekty automatycznie zamykające się, które automatycznie wyjmij uchwyt połączenia po zamknięciu. Dzięki temu możesz na przykład:

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

Po zakończeniu wykonywania wywołań synchronicznych wywołaj ProfileConnector#removeConnectionHolder(Object) Gdy wszyscy posiadacze połączeń zostaną usunięte, połączenie zostanie zamknięte.

Połączenia

Detektor połączeń może być używany do informowania o stanie połączenia zmian i connector.utils().isConnected może służyć do określenia, czy połączenie jest dostępne. Na przykład:

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

Wywołania asynchroniczne

Każda metoda ujawniona w ramach podziału profili musi być oznaczona jako blokująca (synchroniczne) lub nieblokujące (asynchroniczne). Każda metoda, która zwraca asynchroniczny typ danych (np. ListenableFuture) lub akceptuje wywołanie zwrotne jest oznaczony jako nieblokujący. Wszystkie inne metody są oznaczone jako blokowanie.

Zalecane są wywołania asynchroniczne. Jeśli musisz używać wywołań synchronicznych, zapoznaj się z artykułem Synchroniczny Połączenia.

Wywołania zwrotne

Najbardziej podstawowym typem wywołania nieblokującego jest niepusta metoda, która akceptuje jego parametrów interfejsu, który zawiera metodę wywoływania wynik. Aby te interfejsy działały z pakietem SDK, musi być dodał adnotacje @CrossProfileCallback. Na przykład:

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

Tego interfejsu można następnie używać jako parametru w elemencie @CrossProfile z adnotacjami i być wywoływane jak zwykle. Na przykład:

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

Jeśli ten interfejs zawiera jedną metodę, która przyjmuje 0 lub 1 , może być również używany w wywołaniach wielu profili jednocześnie.

Przy użyciu wywołania zwrotnego można przekazać dowolną liczbę wartości, ale połączenie pozostają otwarte tylko dla pierwszej wartości. Więcej informacji znajdziesz w sekcji Łączniki. pozostawiając otwarte połączenie w celu otrzymania większej liczby wartości.

Metody synchroniczne z wywołaniami zwrotnymi

Nietypową cechą stosowania wywołań zwrotnych z pakietem SDK jest to, że możesz Technicznie rzecz biorąc, napisz metodę synchroniczną wykorzystującą wywołanie zwrotne:

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

W tym przypadku metoda jest w rzeczywistości synchroniczna mimo wywołania zwrotnego. Ten kod będzie działał prawidłowo:

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

Jednak w przypadku wywołania za pomocą pakietu SDK działa to inaczej. Jest brak gwarancji, że metoda instalacji zostanie wywołana przed komunikatem „Ten kod trzeci”. . Wszelkie zastosowania metody oznaczonej przez SDK jako asynchronicznej muszą nie przyjmuje żadnych założeń dotyczących czasu wywołania metody.

Proste wywołania zwrotne

„Proste wywołania zwrotne” to bardziej restrykcyjna forma wywołania zwrotnego, która pozwala dodatkowe funkcje przy wykonywaniu połączeń między profilami. Proste interfejsy muszą jedną metodę, która może przyjmować zero lub jeden parametr.

Możesz wymusić, aby interfejs wywołania zwrotnego musi pozostawać, określając simple=true w adnotacji @CrossProfileCallback.

Prostych wywołań zwrotnych można używać z różnymi metodami, takimi jak .both(), .suppliers(), i inne.

Gniazda łączące

Podczas wykonywania asynchronicznego wywołania (z użyciem wywołań zwrotnych lub kontraktów terminowych) Obiekt połączenia jest dodawany podczas nawiązywania połączenia i usunięty, gdy lub wartość jest przekazywana.

Jeśli spodziewasz się, że wywołanie zwrotne będzie przekazywane przy użyciu więcej niż jednego wyniku, ręcznie dodaj wywołanie zwrotne jako właściciela połączenia:

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

  profileMyClass.other().registerListener(b);

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

Tego kodu można też użyć z blokiem try-with-resources:

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

  // Other things running while we expect results
}

Gdy zadzwonimy lub nawiążemy połączenie w przyszłości, połączenie pozostanie otwarte. aż do przekazania wyniku. Jeśli ustalimy, że wynik nie zostanie zaliczony, powinien usunąć wywołanie zwrotne (lub przyszłe) jako właściciela połączenia:

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

Więcej informacji znajdziesz w artykule Etui na połączenia.

Transakcje terminowe

Pakiet SDK obsługuje transakcje terminowe natywnie. Jedyna natywnie obsługiwana Typ przyszłego typu to ListenableFuture, ale w przypadku niestandardowej przyszłości typów może i sposobu ich wykorzystania. Aby używać transakcji terminowych, wystarczy zadeklarować obsługiwany typ przyszły jako zwrot. metody obejmującej kilka profili i używaj jej jak zwykle.

Ma tę samą „nietypową cechę” jako wywołania zwrotne, a metoda synchroniczna , który zwraca przyszłość (np.przy użyciu funkcji immediateFuture), będzie działać inaczej. w bieżącym profilu i w innym profilu. Dowolne użycie metoda oznaczona przez pakiet SDK jako asynchroniczna nie może przyjmować żadnych założeń .

Wątki

Nie blokuj w związku z przejściem na różne profile ani w związku z wywołaniem zwrotnym w elemencie głównym wątek. Jeśli to zrobisz, w pewnych sytuacjach kod zostanie zablokowany bez ograniczeń czasowych. Dzieje się tak, ponieważ połączenie z innym profilem jest również ustanowione w wątku głównym, co nigdy nie wystąpi, jeśli jest zablokowane; wyniku z wielu profili.

Dostępność

Detektor dostępności może być używany do informowania o stanie dostępności zmian i connector.utils().isAvailable może służyć do określenia, czy profil jest dostępny do użycia. Na przykład:

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

Gniazda łączące

Posiadacze połączeń to dowolne obiekty, które są rejestrowane jako mające i zainteresowanie nawiązywaniem i utrzymywaniem połączenia między profilami.

Podczas wykonywania wywołań asynchronicznych domyślnie dodawany jest posiadacz połączenia po rozpoczęciu wywołania i usuwany po wystąpieniu jakiegokolwiek wyniku lub błędu.

Posiadacze połączeń można również dodawać i usuwać ręcznie, aby mieć większą kontrolę. w sieci komórkowej. Posiadacze połączeń można dodać za pomocą connector.addConnectionHolder i usunięto za pomocą connector.removeConnectionHolder

Gdy zostanie dodany co najmniej 1 właściciel połączenia, pakiet SDK spróbuje utrzymywania połączenia. Jeśli nie ma dodanych żadnych właścicieli połączeń, można zamknąć połączenie.

Musisz zachować odniesienie do każdego dodanego właściciela połączenia – i je usunąć gdy nie jest już stosowny.

Wywołania synchroniczne

Przed wykonywaniem wywołań synchronicznych należy dodać posiadacza połączenia. Może to spowodować przy użyciu dowolnego obiektu, ale trzeba go śledzić, aby można było usuwane, gdy nie trzeba już wykonywać wywołań synchronicznych.

Wywołania asynchroniczne

Podczas wykonywania połączeń asynchronicznych posiadacze połączeń są automatycznie zarządzane aby zapewnić otwarte połączenie między połączeniem a pierwszą odpowiedzią lub błędem. Jeśli chcesz, aby połączenie przetrwało dłużej (np. aby otrzymywać odpowiedzi za pomocą pojedynczego wywołania zwrotnego), musisz dodać samo wywołanie zwrotne jako i usuń go, gdy nie będziesz już otrzymywać i skalowalnych danych.

Obsługa błędów

Domyślnie wszystkie połączenia nawiązane z innym profilem, gdy nie jest on używany skutkuje zwróceniem wartości UnavailableProfileException (lub przekazywane do przyszłości lub błędne wywołanie zwrotne dla wywołania asynchronicznego).

Aby tego uniknąć, deweloperzy mogą używać metod #both() lub #suppliers() i zapisywać kod do obsługi dowolnej liczby pozycji na wynikowej liście (będzie to 1, jeśli drugi profil jest niedostępny lub 2, jeśli jest dostępny).

Wyjątki

Wszystkie niezaznaczone wyjątki mające miejsce po wywołaniu bieżącego profilu zostaną zostaną rozpowszechnione w zwykły sposób. Ma to zastosowanie niezależnie od metody użytej do utworzenia połączenie (#current(), #personal, #both itp.).

niezaznaczone wyjątki, które nastąpią po połączeniu z innym profilem; w zgłoszeniu ProfileRuntimeException z oryginalnym wyjątkiem, przyczyna. Ma to zastosowanie niezależnie od metody wywołania (#other(), #personal, #both itp.).

ifAvailable

Jako alternatywa dla wyłapywania UnavailableProfileException instancji, możesz użyć metody .ifAvailable(), aby podać wartość domyślną który zostanie zwrócony, a nie UnavailableProfileException.

Na przykład:

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

Testowanie

Aby przetestować kod, musisz wstrzykiwać wystąpienia profilu z dowolnym kodem, który z niego korzysta (sprawdzanie dostępności profilu, ręcznie itp.). Musisz też wstrzykiwać wystąpienia profilu typów miejsc, w których są używane.

Udostępniamy fałszywe typy i treści oprogramowania sprzęgającego, które można wykorzystać w testach.

Najpierw dodaj zależności testowe:

  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'

Następnie dodaj do zajęć testowych adnotacje w polu @CrossProfileTest, identyfikując @CrossProfileConfiguration klasa z adnotacjami do przetestowania:

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

}

Spowoduje to generowanie fałszywych informacji dla wszystkich typów i oprogramowań sprzęgających używanych konfiguracji.

Utwórz wystąpienia tych fałszywych w teście:

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();

Skonfiguruj stan profilu:

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

Przekaż fałszywe oprogramowanie sprzęgające i klasę crossprofile do testowanego kodu oraz a potem dzwonić.

Połączenia będą kierowane do właściwego miejsca docelowego, a wyjątki będą zgłaszane, nawiązywania połączeń z rozłączonymi lub niedostępnymi profilami.

Obsługiwane typy

Wymienione niżej typy reklam są obsługiwane bez dodatkowych działań z Twojej strony. Mogą one mogą być używane jako argumenty lub zwracane typy we wszystkich wywołaniach obejmujących różne profile.

  • Elementy podstawowe (byte, short, int, long, float, double, char, boolean),
  • Elementy podstawowe w pakiecie (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,
  • Wszystkiego, co implementuje dyrektywę android.os.Parcelable,
  • Wszystkiego, co implementuje dyrektywę java.io.Serializable,
  • jednowymiarowe tablice niepodstawowe,
  • java.util.Optional,
  • java.util.Collection,
  • java.util.List,
  • java.util.Map,
  • java.util.Set,
  • android.util.Pair,
  • com.google.common.collect.ImmutableMap.

Wszystkie obsługiwane typy ogólne (na przykład java.util.Collection) mogą zawierać dowolne . Na przykład:

java.util.Collection<java.util.Map<java.lang.String,MySerializableType[]>> to jest poprawnym typem.

Transakcje terminowe

Te typy są obsługiwane tylko jako zwracane:

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

Niestandardowe folie do pakowania

Jeśli Twojego typu nie ma na wcześniejszej liście, najpierw zastanów się, czy może zostać prawidłowo stosuj android.os.Parcelable lub java.io.Serializable. Jeśli nie będzie więc widzieć działek otoki na dodaj obsługę swojego typu.

Niestandardowe opakowania w przyszłości

Jeśli chcesz użyć przyszłego typu, którego nie ma na wcześniejszej liście, zobacz przyszły typ , aby dodać obsługę.

Folia spożywcza

Pakiety Parcelable to sposób, w jaki pakiet SDK dodaje obsługę pakietów typów, których nie można modyfikować. Pakiet SDK zawiera kody do wielu type, ale jeśli Nie uwzględniono typu, którego chcesz użyć, musisz wpisać własny.

Parcelable Wrapper to klasa, która służy do pakowania innych zajęć i ułatwia im parlamentarny. Jest zgodny z określoną umową statyczną i jest zarejestrowany w pakiecie SDK której można używać do konwertowania określonego typu na papierowy wyodrębnienia go z typu parcelable.

Adnotacja

Klasa otoki umożliwiającej parowanie musi mieć adnotację @CustomParcelableWrapper, określając opakowaną klasę jako originalType. Na przykład:

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

Zarejestruj się za pomocą pakietu SDK

Po utworzeniu niestandardowego opakowania paczki musisz je zarejestrować wraz z pakietem SDK.

Aby to zrobić, wpisz parcelableWrappers={YourParcelableWrapper.class} w jednej z tych lokalizacji adnotacji CustomProfileConnector lub CrossProfile w klasie.

Przyszłe opakowania

Pakiet SDK dodaje obsługę przyszłości w profilach. Pakiet SDK obejmuje domyślnie obsługę ListenableFuture, ale w przyszłości które możesz samodzielnie dodać.

A Future Wrapper to zajęcia, które mają na celu opakowanie konkretnego typu Future i dostępnych dla pakietu SDK. Jest zgodny z określoną, statyczną umową i musi być zarejestrowany w pakiecie SDK.

Adnotacja

Przyszła klasa otoki musi zawierać adnotację @CustomFutureWrapper określającą opakowaną klasę jako originalType. Na przykład:

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

Zarejestruj się za pomocą pakietu SDK

Po utworzeniu niestandardowego opakowania musisz go zarejestrować za pomocą pakietu SDK.

Aby to zrobić, wpisz futureWrappers={YourFutureWrapper.class} w Adnotacja CustomProfileConnector lub CrossProfile w klasie.

Tryb bezpośredniego rozruchu

Jeśli aplikacja obsługuje rozruch bezpośredni tryb , przed odblokowaniem profilu może być konieczne nawiązanie połączenia między profilem. Domyślnie pakiet SDK zezwala na połączenia tylko wtedy, gdy drugi profil jest odblokowany.

Aby zmienić to zachowanie, jeśli korzystasz z niestandardowego oprogramowania sprzęgającego profilu, powinien określić availabilityRestrictions=AvailabilityRestrictions.DIRECT_BOOT_AWARE:

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

Jeśli korzystasz z CrossProfileConnector, ustaw .setAvailabilityRestrictions(AvailabilityRestrictions.DIRECT_BOOT _AWARE wł. dla konstruktora.

Dzięki tej zmianie będziesz otrzymywać informacje o dostępności i będziesz mieć możliwość gdy drugi profil nie jest odblokowany. To Ty odpowiadasz aby mieć pewność, że połączenia będą miały dostęp tylko do pamięci zaszyfrowanej na urządzeniu.