Umieszczanie aktywności

Umieszczanie aktywności optymalizuje aplikacje na urządzeniach z dużym ekranem, rozdzielając okno zadań aplikacji między 2 działania lub 2 wystąpienia tej samej aktywności.

Rysunek 1. Aplikacja Ustawienia z aktywnościami obok siebie.

Jeśli aplikacja składa się z wielu aktywności, umieszczanie aktywności umożliwia zwiększenie wygody użytkowników na tabletach, urządzeniach składanych i urządzeniach z ChromeOS.

Umieszczanie aktywności nie wymaga refaktoryzacji kodu. Sposób wyświetlania działań w aplikacji – obok siebie lub w stosunku do siebie – możesz określić, tworząc plik konfiguracji XML lub wywołując interfejs API Jetpack WindowManager.

Obsługa małych ekranów jest obsługiwana automatycznie. Gdy aplikacja działa na urządzeniu z małym ekranem, aktywności układają się jeden na drugim. Na dużych ekranach aktywności są wyświetlane obok siebie. System określa prezentację na podstawie utworzonej przez Ciebie konfiguracji – nie jest wymagane rozgałęzianie.

Umieszczanie aktywności pozwala uwzględnić zmianę orientacji urządzenia i płynnie działa na urządzeniach składanych, układaj je i rozkładaj podczas składania i rozkładania.

Umieszczanie aktywności jest obsługiwane na większości urządzeń z dużymi ekranami z Androidem 12L (poziom interfejsu API 32) lub nowszym.

Okno podziału zadania

Osadzanie aktywności dzieli okno zadań aplikacji na 2 kontenery: główny i dodatkowy. Kontenery zawierają działania uruchomione z głównej aktywności lub innych działań znajdujących się już w kontenerach.

Działania są grupowane w kontenerze dodatkowym po ich uruchomieniu, a kontener dodatkowy jest umieszczony nad głównym kontenerem na małych ekranach. Dzięki temu grupowanie działań i nawigacja wstecz zapewnia kolejność działań już wbudowanych w aplikację.

Umieszczanie aktywności umożliwia prezentowanie aktywności na różne sposoby. Aplikacja może podzielić okno zadania, uruchamiając 2 działania jednocześnie:

Rysunek 2. Dwie aktywności obok siebie.

Z kolei działanie, które zajmuje całe okno zadania, może spowodować podział, uruchamiając nową aktywność:

Rysunek 3. Aktywność A rozpoczyna aktywność B z boku.

Działania, które są już podzielone i udostępniają okno zadania, mogą uruchamiać inne działania na następujące sposoby:

  • Obok innej aktywności:

    Rysunek 4. Aktywność A rozpoczyna aktywność C na boku względem aktywności B.
  • Przesuń podział na bok, ukrywając poprzednią aktywność główną:

    Rysunek 5. Aktywność B rozpoczyna aktywność C na bok i przesuwa podział w bok.
  • Uruchom działanie u góry, czyli w tym samym stosie aktywności:

    Rysunek 6. Aktywność B rozpoczyna działanie C bez dodatkowych flag intencji.
  • Uruchom pełne okno aktywności w ramach tego samego zadania:

    Rysunek 7. Aktywność A lub B rozpoczyna aktywność C, która wypełnia okno zadania.

Nawigacja wstecz

Różne rodzaje aplikacji mogą mieć różne reguły przechodzenia wstecz w stanie podzielonego okna zadania w zależności od zależności między działaniami lub sposobu wywoływania zdarzenia wstecznego przez użytkowników, np.:

  • Łączenie: jeśli działania są ze sobą powiązane i jeden z nich nie powinien być wyświetlany bez drugiego, można skonfigurować nawigację wstecz, aby zakończyć oba.
  • Jeśli działania są w pełni niezależne, przejście wstecz po aktywności nie ma wpływu na stan innej aktywności w oknie zadania.

Podczas korzystania z nawigacji przy użyciu przycisków zdarzenie wstecz jest wysyłane do ostatniego zaznaczonego działania. W przypadku nawigacji przy użyciu gestów zdarzenie Wstecz jest wysyłane do działania, w którym został wykonany gest.

Układ z kilkoma panelami

Jetpack WindowManager umożliwia budowanie układu aktywności w kilku panelach na urządzeniach z dużym ekranem z Androidem 12L (poziom interfejsu API 32) lub nowszym oraz na niektórych urządzeniach z wcześniejszymi wersjami platformy. Istniejące aplikacje, które opierają się na wielu działaniach, a nie na fragmentach, lub w układach opartych na widokach (np. SlidingPaneLayout), mogą zapewnić użytkownikom lepsze wrażenia na dużym ekranie bez refaktoryzacji kodu źródłowego.

Typowym przykładem jest podział według listy. Aby zapewnić wysoką jakość prezentacji, system rozpoczyna działanie związane z listą, a następnie aplikacja natychmiast rozpoczyna aktywność związaną z szczegółami. System przejścia oczekuje na narysowanie obu działań, a następnie wyświetla je razem. Z punktu widzenia użytkownika te 2 działania są uruchamiane jako jedna.

Rysunek 8. 2 działania rozpoczęły się jednocześnie w układzie z wieloma panelami.

Atrybuty podziału

Możesz określić proporcje okna zadania między podzielonymi kontenerami i rozmieszczenie kontenerów.

W przypadku reguł zdefiniowanych w pliku konfiguracji XML ustaw te atrybuty:

  • splitRatio: ustawia proporcje kontenera. Wartość jest liczbą zmiennoprzecinkową w otwartym przedziale czasu (0,0, 1,0).
  • splitLayoutDirection: określa układ podzielonych kontenerów względem siebie. Dostępne wartości:
    • ltr: od lewej do prawej
    • rtl: od prawej do lewej
    • locale: wartość ltr lub rtl jest określana na podstawie ustawienia lokalnego.

Przykłady znajdziesz w sekcji Konfiguracja XML poniżej.

W przypadku reguł utworzonych przy użyciu interfejsów WindowManager API utwórz obiekt SplitAttributes za pomocą SplitAttributes.Builder i wywołaj te metody kreatora:

Przykłady znajdziesz poniżej w sekcji WindowManager API.

Rysunek 9. 2 części aktywności ułożone od lewej do prawej, ale z różnymi współczynnikami podziału.

Obiekty zastępcze

Aktywności zastępcze to puste działania dodatkowe, które zajmują obszar podziału aktywności. Mają one zostać zastąpione innym działaniem, które zawiera treści. Na przykład aktywność zastępcza może zajmować drugą stronę podziału aktywności w układzie z poziomem listy, dopóki nie zostanie wybrany element z listy. Wtedy działanie zawierające informacje o danym elemencie listy zastępuje obiekt zastępczy.

Domyślnie system wyświetla obiekty zastępcze tylko wtedy, gdy jest wystarczająco dużo miejsca na podział aktywności. Obiekty zastępcze są automatycznie uzupełniane, gdy rozmiar wyświetlania zmieni się na szerokość lub wysokość zbyt małą, aby można było wyświetlić podział. Jeśli jest wystarczająco dużo miejsca, system ponownie uruchamia obiekt zastępczy w stanie inicjowania.

Rysunek 10. Składanie i rozkładanie urządzenia. Działanie obiektu zastępczego zostało zakończone i odtworzone wraz ze zmianą rozmiaru interfejsu.

Atrybut stickyPlaceholder metody SplitPlaceholderRule lub setSticky() SplitPlaceholder.Builder może jednak zastąpić działanie domyślne. Gdy atrybut lub metoda określa wartość true, system wyświetla obiekt zastępczy jako najwyższą aktywność w oknie zadania, gdy rozmiar wyświetlacza z wyświetlacza z jednym panelem zostanie zmniejszony do wyświetlacza z 2 panelami (przykład znajdziesz w sekcji Konfiguracja podziału).

Rysunek 11. Składanie i rozkładanie urządzenia. Aktywność zastępcza jest przyklejona.

Zmiana rozmiaru okna

Zmiany w konfiguracji urządzenia zmniejszają szerokość okna zadań, przez co nie wystarcza na układ z kilkoma panelami (np. gdy składany ekran składa się z dużego ekranu do rozmiaru telefonu lub gdy rozmiar okna aplikacji zmienia się w trybie wielu okien), aktywności niezastępcze w obszarze dodatkowym okna zadania są układane nad działaniami w panelu głównym.

Działania zastępcze są wyświetlane tylko wtedy, gdy szerokość wyświetlacza jest wystarczająca do podziału. Na mniejszych ekranach symbol zastępczy jest automatycznie zamykany. Gdy obszar wyświetlania znów stanie się wystarczająco duży, obiekt zastępczy zostanie odtworzony. (Patrz sekcja Obiekty zastępcze powyżej).

Grupowanie aktywności jest możliwe, ponieważ WindowManager ustawia na Z

Wiele działań w panelu dodatkowym

Działanie B rozpoczyna działanie C bez dodatkowych flag intencji:

Podział aktywności obejmujący aktywności A, B i C z C nałożonym na B.

w kolejności wykonywania działań w tej kolejności:

Dodatkowy stos aktywności zawierający aktywność C nałożony na działanie B.
          Stos dodatkowy jest ułożony na stosie działań podstawowych zawierających aktywność A.

W mniejszym oknie zadania aplikacja zmniejsza się do pojedynczego działania z C na górze stosu:

Małe okno, w którym widać tylko aktywność C.

Przejście z powrotem w mniejszym oknie powoduje przejście między aktywnościami nakładanymi na siebie.

Jeśli konfiguracja okna zadań zostanie przywrócona do większego rozmiaru, który może pomieścić wiele okien, działania będą ponownie wyświetlane obok siebie.

Skumulowane podziały

Ćwiczenie B rozpoczyna aktywność C na bok i przesuwa podział w bok:

Okno zadania z działaniami A i B oraz zadaniami B i C.

Efektem jest wykonywanie działań w tej kolejności w tej kolejności:

Aktywności A, B i C w jednym stosie. Działania są układane w stos w tej kolejności (od góry do dołu): C, B, A.

W mniejszym oknie zadania aplikacja zmniejsza się do pojedynczego działania z C na górze:

Małe okno, w którym widać tylko aktywność C.

Stała orientacja pionowa

Ustawienie w pliku manifestu android:screenOrientation pozwala aplikacjom ograniczać czynności do orientacji pionowej lub poziomej. Aby zwiększyć wygodę użytkowników korzystających z urządzeń z dużym ekranem, takich jak tablety i urządzenia składane, producenci urządzeń (OEM) mogą ignorować żądania orientacji ekranu oraz letterbox, gdy aplikacja ma orientację pionową lub poziomą na wyświetlaczach pionowych.

Rysunek 12. Ćwiczenia z czarnymi pasami: obraz ustawiony w orientacji pionowej na urządzeniu poziomym (po lewej) lub w orientacji poziomej na urządzeniu pionowym (po prawej).

Podobnie po włączeniu umieszczania aktywności na dużych ekranach (szerokość ≥ 600 dp) dostawcy mogą dostosowywać urządzenia do zdjęć poziomych i pionowych. Gdy stały portret powoduje uruchomienie drugiej aktywności, urządzenie może wyświetlić te aktywności obok siebie na wyświetlaczu z 2 panelami.

Rysunek 13. Ćwiczenie A o stałej orientacji pionowej rozpoczyna aktywność B z boku.

Zawsze dodawaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED do pliku manifestu aplikacji, aby poinformować urządzenia, że aplikacja obsługuje umieszczanie aktywności (zobacz Konfiguracja podziału poniżej). Urządzenia dostosowane do potrzeb OEM mogą wtedy określić, czy mają być używane stałe pasy pionowe.

Podziel konfigurację

Reguły podziału służą do konfigurowania podziałów aktywności. Reguły podziału definiuje się w pliku konfiguracji XML lub przez wywołania interfejsu API Jetpack WindowManager.

W obu przypadkach aplikacja musi mieć dostęp do biblioteki WindowManager i musi informować system, że aplikacja ma zaimplementowane umieszczanie aktywności.

Wykonaj te czynności:

  1. Dodaj najnowszą zależność biblioteki WindowManager do pliku build.gradle na poziomie modułu aplikacji, na przykład:

    implementation 'androidx.window:window:1.1.0-beta02'

    Biblioteka WindowManager zawiera wszystkie komponenty wymagane do umieszczania aktywności.

  2. Poinformuj system, że w Twojej aplikacji jest zaimplementowane umieszczanie aktywności.

    Dodaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED do elementu <application> w pliku manifestu aplikacji i ustaw wartość „true”, na przykład:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <property
                android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
                android:value="true" />
        </application>
    </manifest>
    

    W usłudze WindowManager w wersji 1.1.0-alpha06 i nowszych podziały osadzonych aktywności są wyłączone, chyba że właściwość została dodana do pliku manifestu i ma wartość true (prawda).

    Oprócz tego producenci urządzeń używają tego ustawienia do włączania niestandardowych funkcji w aplikacjach, które obsługują umieszczanie aktywności. Na przykład urządzenia mogą wyświetlać aktywność w trybie poziomym tylko w orientacji pionowej na wyświetlaczu poziomym, aby ukierunkować aktywność na potrzeby przejścia na układ z 2 panelami po rozpoczęciu drugiej aktywności (patrz Stała orientacja pionowa).

Konfiguracja XML

Aby utworzyć opartą na XML implementację wektora dystrybucyjnego aktywności, wykonaj te czynności:

  1. Utwórz plik zasobów XML, który:

    • Definiuje działania o wspólnym podziale
    • Konfiguruje opcje podziału
    • Tworzy obiekt zastępczy dla dodatkowego kontenera podziału, gdy treść jest niedostępna
    • Określa działania, które nigdy nie powinny być częścią podziału

    Na przykład:

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. Utwórz inicjator.

    Komponent WindowManager RuleController analizuje plik konfiguracji XML i udostępnia reguły w systemie. Biblioteka startowa Initializer Jetpacka udostępnia plik XML RuleController podczas uruchamiania aplikacji, dzięki czemu reguły są stosowane po rozpoczęciu dowolnych działań.

    Aby utworzyć inicjator, wykonaj te czynności:

    1. Dodaj najnowszą zależność biblioteki startowej Jetpack do pliku build.gradle na poziomie modułu, na przykład:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. Utwórz klasę, która implementuje interfejs Initializer.

      Inicjator udostępnia reguły podziału w usłudze RuleController, przekazując identyfikator pliku konfiguracji XML (main_split_config.xml) do metody RuleController.parseRules().

      Kotlin

      class SplitInitializer : Initializer<RuleController> {
      
       override fun create(context: Context): RuleController {
           return RuleController.getInstance(context).apply {
               setRules(RuleController.parseRules(context, R.xml.main_split_config))
           }
       }
      
       override fun dependencies(): List<Class<out Initializer<*>>> {
           return emptyList()
       }
      }
      

      Java

      public class SplitInitializer implements Initializer<RuleController> {
      
        @NonNull
        @Override
        public RuleController create(@NonNull Context context) {
            RuleController ruleController = RuleController.getInstance(context);
            ruleController.setRules(
                RuleController.parseRules(context, R.xml.main_split_config)
            );
            return ruleController;
        }
      
        @NonNull
        @Override
        public List<Class<? extends Initializer<?>>> dependencies() {
            return Collections.emptyList();
        }
      }
      
  3. Utwórz dostawcę treści dla definicji reguł.

    Dodaj androidx.startup.InitializationProvider do pliku manifestu aplikacji jako <provider>. Dodaj odwołanie do implementacji inicjatora RuleController (SplitInitializer):

    <!-- AndroidManifest.xml -->
    
    <provider android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- Make SplitInitializer discoverable by InitializationProvider. -->
        <meta-data android:name="${applicationId}.SplitInitializer"
            android:value="androidx.startup" />
    </provider>
    

    InitializationProvider wykrywa i inicjuje SplitInitializer przed wywołaniem metody onCreate() aplikacji. Z tego powodu reguły podziału obowiązują po rozpoczęciu głównej aktywności w aplikacji.

Interfejs API WindowManager

Umieszczanie aktywności możesz zaimplementować automatycznie za pomocą kilku wywołań interfejsu API. Wywołuj metodę onCreate() w podklasie Application, aby mieć pewność, że reguły zaczną obowiązywać przed uruchomieniem działań.

Aby automatycznie utworzyć podział aktywności:

  1. Utwórz regułę podziału:

    1. Utwórz SplitPairFilter wskazujący działania, które należą do tego samego podziału:

      Kotlin

      val splitPairFilter = SplitPairFilter(
         ComponentName(this, ListActivity::class.java),
         ComponentName(this, DetailActivity::class.java),
         null
      )
      

      Java

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );
      
    2. Dodaj filtr do zestawu filtrów:

      Kotlin

      val filterSet = setOf(splitPairFilter)
      

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
      
    3. Utwórz atrybuty układu na potrzeby podziału:

      Kotlin

      val splitAttributes: SplitAttributes = SplitAttributes.Builder()
          .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
          .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
          .build()
      

      Java

      final SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
      

      SplitAttributes.Builder tworzy obiekt zawierający atrybuty układu:

      • setSplitType: określa sposób przydzielania dostępnego obszaru do każdego kontenera aktywności. Typ podziału współczynnika określa odsetek dostępnego obszaru wyświetlania przydzielonego do kontenera głównego. Kontener dodatkowy zajmuje pozostałą część dostępnego obszaru wyświetlania.
      • setLayoutDirection: określa układ kontenerów aktywności względem siebie nawzajem – kontenera głównego.
    4. Tworzenie SplitPairRule:

      Kotlin

      val splitPairRule = SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build()
      

      Java

      SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build();
      

      SplitPairRule.Builder tworzy i konfiguruje regułę:

      • filterSet: zawiera filtry par podzielonych, które określają, kiedy zastosować regułę, identyfikując aktywności o takim samym podziale.
      • setDefaultSplitAttributes: stosuje atrybuty układu do reguły.
      • setMinWidthDp: ustawia minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości, dp), która umożliwia podział danych.
      • setMinSmallestWidthDp: określa minimalną wartość (w dp), jaką musi mieć mniejsza z tych 2 wymiarów wyświetlania, aby umożliwić podział bez względu na orientację urządzenia.
      • setMaxAspectRatioInPortrait: ustawia maksymalny format obrazu (wysokość:szerokość) w orientacji pionowej, przy którym są wyświetlane podziały aktywności. Jeśli format obrazu pionowego przekracza maksymalny współczynnik proporcji, podziały są wyłączone niezależnie od szerokości ekranu. Uwaga: wartość domyślna to 1,4, co na większości tabletów zajmuje całe okno zadania w orientacji pionowej. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT i setMaxAspectRatioInLandscape. Wartość domyślna w przypadku orientacji poziomej to ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary: określa, jak ukończenie wszystkich działań w kontenerze dodatkowym wpływa na działania w kontenerze głównym. NEVER wskazuje, że system nie powinien kończyć działań głównych po zakończeniu wszystkich działań w kontenerze dodatkowym (patrz Kończenie działań).
      • setFinishSecondaryWithPrimary: określa, jak ukończenie wszystkich działań w kontenerze głównym wpływa na działania w kontenerze dodatkowym. ALWAYS oznacza, że system powinien zawsze kończyć działania w kontenerze dodatkowym po zakończeniu wszystkich działań w kontenerze głównym (patrz Kończenie działań).
      • setClearTop: określa, czy wszystkie działania w kontenerze dodatkowym zostaną zakończone po uruchomieniu w kontenerze nowej aktywności. Wartość Fałsz oznacza, że nowe działania są nakładane na działania znajdujące się już w kontenerze dodatkowym.
    5. Pobierz instancję usługi WindowManager RuleController i dodaj do niej regułę:

      Kotlin

      val ruleController = RuleController.getInstance(this)
      ruleController.addRule(splitPairRule)
      

      Java

      RuleController ruleController = RuleController.getInstance(this);
      ruleController.addRule(splitPairRule);
      
  2. Utwórz obiekt zastępczy kontenera dodatkowego, gdy treść jest niedostępna:

    1. Utwórz obiekt ActivityFilter identyfikujący aktywność, z którą obiekt zastępczy ma wspólny podział okien zadań:

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )
      

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
      
    2. Dodaj filtr do zestawu filtrów:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
      

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
      
    3. Tworzenie SplitPlaceholderRule:

      Kotlin

      val splitPlaceholderRule = SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            Intent(context, PlaceholderActivity::class.java)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build()
      

      Java

      SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            new Intent(context, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();
      

      SplitPlaceholderRule.Builder tworzy i konfiguruje regułę:

      • placeholderActivityFilterSet: zawiera filtry aktywności, które określają, kiedy zastosować regułę, identyfikując aktywności, z którymi powiązana jest aktywność zastępcza.
      • Intent: określa uruchomienie działania związanego z obiektem zastępczym.
      • setDefaultSplitAttributes: stosuje atrybuty układu do reguły.
      • setMinWidthDp: ustawia minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości, dp), która umożliwia podział danych.
      • setMinSmallestWidthDp: określa minimalną wartość (w dp), jaką musi być mniejszy z 2 wymiarów wyświetlacza, by umożliwić podział bez względu na orientację urządzenia.
      • setMaxAspectRatioInPortrait: ustawia maksymalny format obrazu (wysokość:szerokość) w orientacji pionowej, przy którym są wyświetlane podziały aktywności. Uwaga: wartość domyślna to 1,4, co na większości tabletów powoduje, że zadania wypełniają okno zadania w orientacji pionowej. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT i setMaxAspectRatioInLandscape. Wartość domyślna w przypadku orientacji poziomej to ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder: określa, jak zakończenie działania obiektu zastępczego wpływa na działania w kontenerze głównym. ZAWSZE wskazuje, że system powinien zawsze kończyć działania w kontenerze głównym po zakończeniu działania symbolu zastępczego (patrz Kończenie działań).
      • setSticky: określa, czy aktywność zastępcza pojawia się na stosu aktywności na małych ekranach, gdy obiekt zastępczy pojawi się po raz pierwszy w części podziału z wystarczającą minimalną szerokością.
    4. Dodaj regułę do interfejsu WindowManager RuleController:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)
      

      Java

      ruleController.addRule(splitPlaceholderRule);
      
  3. Określ działania, które nigdy nie powinny być częścią podziału:

    1. Utwórz obiekt ActivityFilter wskazujący aktywność, która zawsze powinna zajmować cały obszar wyświetlania zadań:

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )
      

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
      
    2. Dodaj filtr do zestawu filtrów:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)
      

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
      
    3. Tworzenie ActivityRule:

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()
      

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();
      

      ActivityRule.Builder tworzy i konfiguruje regułę:

      • expandedActivityFilterSet: zawiera filtry aktywności, które określają, kiedy zastosować regułę, identyfikując aktywności, które chcesz wykluczyć z podziałów.
      • setAlwaysExpand: określa, czy aktywność powinna wypełniać całe okno zadania.
    4. Dodaj regułę do interfejsu WindowManager RuleController:

      Kotlin

      ruleController.addRule(activityRule)
      

      Java

      ruleController.addRule(activityRule);
      

Umieszczanie treści z wielu aplikacji

Na Androidzie 13 (poziom interfejsu API 33) i nowszym aplikacje można umieszczać aktywności z innych aplikacji. Umieszczanie aktywności w różnych aplikacjach lub przez UID umożliwia wizualną integrację działań z wielu aplikacji na Androida. System wyświetla na ekranie obok siebie lub u góry i u dołu aktywność aplikacji hostującej i umieszczoną na niej aktywność z innej aplikacji, tak jak w przypadku umieszczania aktywności w pojedynczej aplikacji.

Na przykład aplikacja Ustawienia może umieścić aktywność selektora tapet z aplikacji TapPicker:

Rysunek 14. Aplikacja Ustawienia (menu po lewej) z selektorem tapety jako aktywnością umieszczoną (po prawej).

Model zaufania

Procesy hosta, w których umieszczone są działania z innych aplikacji, mogą na nowo zdefiniować sposób, w jaki te działania są prezentowane, w tym zmieniać rozmiar, położenie, przycinanie i przezroczystość. Złośliwe hosty mogą korzystać z tej funkcji, aby wprowadzać użytkowników w błąd i tworzyć clickjacking lub inne ataki mające na celu korygowanie działania interfejsu użytkownika.

Aby zapobiec niewłaściwemu używaniu umieszczania danych o aktywności w różnych aplikacjach, Android wymaga, aby aplikacje zezwalały na umieszczanie takich danych. Aplikacje mogą oznaczać hosty jako zaufane lub niezaufane.

Zaufane hosty

Aby umożliwić innym aplikacjom umieszczanie aktywności z Twojej aplikacji i pełną kontrolę nad ich prezentacją, podaj certyfikat SHA-256 aplikacji hosta w atrybucie android:knownActivityEmbeddingCerts elementów <activity> lub <application> w pliku manifestu aplikacji.

Ustaw wartość android:knownActivityEmbeddingCerts jako ciąg znaków:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

lub, aby określić wiele certyfikatów, tablica ciągów znaków:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

odwołujący się do zasobu takiego jak ten:

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

Właściciele aplikacji mogą uzyskać podsumowanie certyfikatu SHA, uruchamiając zadanie signingReport Gradle. Skrót certyfikatu to odcisk cyfrowy SHA-256 bez dwukropków rozdzielających. Więcej informacji znajdziesz w artykułach na temat wygenerowania raportu podpisywania i uwierzytelniania klienta.

Niezaufane hosty

Aby umożliwić dowolnej aplikacji umieszczanie aktywności z aplikacji i kontrolowanie ich prezentacji, określ atrybut android:allowUntrustedActivityEmbedding w elementach <activity> lub <application> w pliku manifestu aplikacji, na przykład:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

Domyślna wartość tego atrybutu to „false”, co uniemożliwia umieszczanie aktywności w różnych aplikacjach.

Uwierzytelnianie niestandardowe

Aby ograniczyć ryzyko związane z umieszczaniem niezaufanych aktywności, utwórz niestandardowy mechanizm uwierzytelniania, który zweryfikuje tożsamość hosta. Jeśli znasz certyfikaty hosta, użyj biblioteki androidx.security.app.authenticator do uwierzytelnienia. Jeśli host uwierzytelni się po umieszczeniu Twojej aktywności, możesz wyświetlić rzeczywiste treści. W przeciwnym razie możesz poinformować użytkownika, że działanie jest niedozwolone, i zablokować treści.

Użyj metody ActivityEmbeddingController#isActivityEmbedded() z biblioteki Jetpack WindowManager, aby sprawdzić, czy host umieszcza Twoją aktywność, na przykład:

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

Ograniczenie dotyczące minimalnego rozmiaru

Do umieszczonych aktywności system Android stosuje minimalną wysokość i szerokość określoną w elemencie <layout> manifestu aplikacji. Jeśli aplikacja nie określa minimalnej wysokości i szerokości, obowiązują systemowe wartości domyślne (sw220dp).

Jeśli host spróbuje zmienić rozmiar umieszczonego kontenera do rozmiaru mniejszego niż minimalny, ten kontener zostanie rozwinięty i zajmie całe granice zadania.

<alias-aktywności>

Aby umieszczanie zaufanych lub niezaufanych aktywności działało z elementem <activity-alias>, do aktywności docelowej, a nie aliasu, należy zastosować android:knownActivityEmbeddingCerts lub android:allowUntrustedActivityEmbedding. Zasada sprawdzająca bezpieczeństwo serwera systemowego jest oparta na flagach ustawionych w środowisku docelowym, a nie na aliasie.

Aplikacja hosta

Aplikacje hostujące implementują osadzanie aktywności w różnych aplikacjach w taki sam sposób, w jaki implementują osadzanie aktywności w pojedynczej aplikacji. Obiekty SplitPairRule i SplitPairFilter lub ActivityRule i ActivityFilter określają umieszczone działania i podziały okien zadań. Reguły podziału definiuje się statycznie w formacie XML lub w czasie działania za pomocą wywołań interfejsu API Jetpack WindowManager.

Jeśli aplikacja hosta próbuje umieścić działanie, które nie ma włączonej opcji umieszczania w różnych aplikacjach, aktywność ta zajmuje cały obszar zadania. W związku z tym aplikacje hosta muszą wiedzieć, czy działania docelowe umożliwiają umieszczanie w różnych aplikacjach.

Jeśli osadzona aktywność rozpoczyna nowe działanie w ramach tego samego zadania, a nowe działanie nie ma włączonej opcji umieszczania w różnych aplikacjach, aktywność zajmuje cały zakres zadania, zamiast nakładać się na działanie w umieszczonym kontenerze.

Aplikacja hosta może osadzać własne działania bez ograniczeń, o ile działania są uruchamiane w tym samym zadaniu.

Przykłady podziału

Podziel od całego okna

Rysunek 15. Aktywność A rozpoczyna aktywność B z boku.

Refaktoryzacja nie jest wymagana. Konfigurację podziału możesz zdefiniować statycznie lub w czasie działania, a następnie wywoływać metodę Context#startActivity() bez dodatkowych parametrów.

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podziel domyślnie

Gdy strona docelowa aplikacji jest zaprojektowana na 2 kontenery na dużych ekranach, największą wygodę użytkowników zapewnia utworzenie i prezentowanie obu działań jednocześnie. Treść może być jednak niedostępna w przypadku dodatkowego kontenera podziału, dopóki użytkownik nie wejdzie w interakcję z działaniem w kontenerze głównym (np. nie wybierze elementu z menu nawigacyjnego). Działanie tymczasowe może wypełnić lukę, dopóki jej treść nie wyświetli się w dodatkowym kontenerze podziału (patrz Obiekty zastępcze powyżej).

Rysunek 16. Podział utworzony przez otwarcie 2 zadań jednocześnie. Jedna aktywność jest obiektem zastępczym.

Aby to zrobić, utwórz obiekt zastępczy i powiąż go z głównym działaniem:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

Gdy aplikacja otrzyma intencję, działanie docelowe może być wyświetlane jako dodatkowa część podziału aktywności; na przykład żądanie wyświetlenia ekranu ze szczegółami z informacjami o elemencie z listy. Na małych ekranach szczegóły są wyświetlane w pełnym oknie zadań, a na większych urządzeniach – obok listy.

Rysunek 17. Szczegółowe informacje o precyzyjnych linkach wyświetlane osobno na małym ekranie, razem z aktywnością związaną z listą na dużym ekranie.

Prośba o uruchomienie powinna być kierowana do głównego działania, a docelowe działanie szczegółowe powinno zostać uruchomione w części. System automatycznie wybierze właściwą prezentację – ułożone w stos lub obok siebie – na podstawie dostępnej szerokości wyświetlania.

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    . . .
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

Miejsce docelowe precyzyjnego linku może być jedynym działaniem, które powinno być dostępne dla użytkownika w stosie nawigacji wstecz. Lepiej nie zamykać działania szczegółów i pozostawić tylko działania głównego:

Duży wyświetlacz z aktywnością na liście i szczegółami obok siebie.
          Nawigacja wstecz nie może odrzucić aktywności szczegółów i zostawić aktywność związaną z listą na ekranie.

Mały wyświetlacz tylko z aktywnością szczegółów. Nawigacja wstecz nie może odrzucić aktywności szczegółów i ujawnić aktywności listy.

Zamiast tego możesz zakończyć obie aktywności w tym samym czasie, używając atrybutu finishPrimaryWithSecondary:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

Patrz sekcja Atrybuty konfiguracji poniżej.

Wiele aktywności w podzielonych kontenerach

Gromadzenie wielu aktywności w podzielnym kontenerze umożliwia użytkownikom dostęp do szczegółowych treści. Na przykład podział według szczegółów listy może sprawić, że użytkownik będzie musiał przejść do sekcji szczegółów podrzędnych, ale zachować główne działanie:

Rysunek 18. Działanie otwarte w panelu dodatkowym okna zadania.

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

Działanie podrzędne jest umieszczone nad działaniem szczegółów i jest ukrywane:

Użytkownik może potem wrócić do poprzedniego poziomu szczegółów, wracając do stosu:

Rysunek 19. Aktywność została usunięta z góry stosu.

Układanie działań na siebie to domyślne zachowanie, gdy działania są uruchamiane z poziomu aktywności w tym samym kontenerze dodatkowym. Działania uruchomione z kontenera głównego w ramach aktywnego podziału również trafiają do kontenera dodatkowego u góry stosu aktywności.

Działania w nowym zadaniu

Gdy działania w podzielonym oknie zadania rozpoczynają działania w nowym zadaniu, nowe zadanie jest oddzielone od zadania, które obejmuje podział, i wyświetla się w pełnym oknie. Ekran Ostatnie pokazuje 2 zadania: zadanie w podziale i nowe zadanie.

Rysunek 20. Rozpocznij zadanie C w nowym zadaniu na podstawie aktywności B.

Zastępowanie aktywności

Działania można zastępować w stosie dodatkowych kontenerów, np. gdy działanie główne jest używane do nawigacji najwyższego poziomu, a działanie dodatkowe jest wybranym miejscem docelowym. Każda opcja nawigacji najwyższego poziomu powinna rozpoczynać nowe działanie w kontenerze dodatkowym i usunąć dotychczasowe.

Rysunek 21. Aktywność nawigacji najwyższego poziomu w panelu głównym zastępuje działania miejsca docelowego w panelu dodatkowym.

Jeśli aplikacja nie zakończy działania w kontenerze dodatkowym po zmianie wyboru nawigacji, nawigacja wstecz może być myląca, gdy podział jest zwinięty (po złożeniu urządzenia). Jeśli na przykład w panelu głównym masz menu, a ekrany A i B w panelu dodatkowym, gdy użytkownik złożysz telefon, B znajduje się na górze A, a A – u góry menu. Gdy użytkownik wróci z poziomu B, zamiast menu pojawi się A.

W takich przypadkach ekran A musi zostać usunięty z tylnego stosu.

Domyślnym zachowaniem przy uruchamianiu z boku w nowym kontenerze w stosunku do istniejącego podziału jest umieszczenie nowych kontenerów dodatkowych na początku i zachowanie starych kontenerów. Możesz skonfigurować podziały, aby wyczyścić poprzednie kontenery dodatkowe za pomocą funkcji clearTop i normalnie uruchamiać nowe działania.

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

Kotlin

class MenuActivity {
    . . .
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity {
    . . .
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

Możesz też użyć tego samego działania dodatkowego, a z działania głównego (menu) wysyłać nowe intencje, które rozwiązują problem z tą samą instancją, ale wywołują stan lub aktualizację interfejsu użytkownika w kontenerze dodatkowym.

Wiele podziałów

Aplikacje mogą zapewniać głęboką nawigację wielopoziomową poprzez uruchamianie dodatkowych działań z boku strony.

Gdy działanie w kontenerze dodatkowym uruchamia nową aktywność z boku, w miejsce istniejącego podziału tworzony jest nowy podział.

Rysunek 22. Aktywność B rozpoczyna aktywność C z boku.

Stos tylny zawiera wszystkie otwarte wcześniej działania, więc użytkownicy mogą przejść do podziału A/B po skończeniu pracy C.

Działania A, B i C w grupie. Działania są ułożone w kolejności od góry do dołu: C, B, A.

Aby utworzyć nowy podział, uruchom nową aktywność z boku istniejącego kontenera dodatkowego. Zadeklaruj konfiguracje dla przydziałów A/B i B/C i uruchom działanie C, normalnie z poziomu B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

Kotlin

class B {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

Reagowanie na zmiany stanu podziału

Różne działania w aplikacji mogą mieć elementy interfejsu, które pełnią tę samą funkcję, np. element sterujący, który otwiera okno z ustawieniami konta.

Rysunek 23. Różne działania z funkcjonalnie identycznymi elementami interfejsu.

Jeśli 2 działania, które mają wspólny element interfejsu, są częścią podziału, pokazanie tego elementu w obu działaniach może powodować nadmiarowość i dezorientację.

Rysunek 24. Zduplikowane elementy interfejsu w przypadku podziału aktywności.

Aby dowiedzieć się, kiedy działania są podzielone, sprawdź proces SplitController.splitInfoList lub zarejestruj detektor w SplitControllerCallbackAdapter, aby sprawdzić zmiany stanu podziału. Następnie odpowiednio dostosuj interfejs:

Kotlin

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance.
            .collect { list ->
                view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
            }
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    new SplitControllerCallbackAdapter(SplitController.getInstance(this))
        .addSplitListener(
            this,
            Runnable::run,
            splitInfoList -> {
                View layout = getLayoutInflater().inflate(R.layout.activity_main, null);
                layout.findViewById(R.id.infoButton).setVisibility(
                    splitInfoList.isEmpty() ? View.VISIBLE : View.GONE);
            });
}

Korutyny można uruchamiać w dowolnym stanie cyklu życia, ale zwykle są uruchamiane w stanie STARTED, aby oszczędzać zasoby (więcej informacji znajdziesz w artykule o używaniu współrzędnych Kotlina z komponentami dostosowanymi do cyklu życia).

Wywołania zwrotne mogą być wykonywane w dowolnym stanie cyklu życia, w tym po zatrzymaniu działania. Słuchacze powinni zwykle być zarejestrowani w kraju onStart(), a niezarejestrowani w onStop().

Widok modalny z pełnym oknem

Niektóre działania blokują użytkownikom możliwość interakcji z aplikacją do czasu wykonania określonego działania. Są to na przykład działania na ekranie logowania, ekran potwierdzenia zgodności z zasadami lub komunikat o błędzie. Aktywności modalne powinny być zapobiegane wyświetlaniu się w podziale.

Za pomocą konfiguracji rozwijania działanie może być wymuszone zawsze wypełnienie okna zadania:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

Kończenie działań

Użytkownicy mogą dokończyć czynności po obu stronach podziału, przesuwając palcem od krawędzi wyświetlacza:

Rysunek 25. Kończenie ćwiczenia za pomocą gestu przesuwania B.
Rysunek 26. Gest przesuwania na końcu ćwiczenia A.

Jeśli urządzenie zostało skonfigurowane do używania przycisku Wstecz zamiast nawigacji przy użyciu gestów, dane wejściowe są wysyłane do zaznaczonego działania, czyli do aktywności, która została dotknięta lub uruchomiona jako ostatnia.

Wpływ zakończenia wszystkich działań w kontenerze na kontener przeciwny zależy od konfiguracji podziału.

Atrybuty konfiguracji

Możesz określić atrybuty reguły podziału pary, aby określić, jak zakończenie wszystkich działań po jednej stronie podziału wpływa na aktywności po drugiej stronie podziału. Te atrybuty:

  • window:finishPrimaryWithSecondary – jak zakończenie wszystkich działań w kontenerze dodatkowym wpływa na działania w kontenerze głównym.
  • window:finishSecondaryWithPrimary – jak zakończenie wszystkich działań w kontenerze głównym wpływa na działania w kontenerze dodatkowym.

Możliwe wartości atrybutów:

  • always – zawsze kończ działania w powiązanym kontenerze.
  • never – nigdy nie dokończ działań w powiązanym kontenerze.
  • adjacent – dokończ działania w powiązanym kontenerze, gdy 2 kontenery są wyświetlane obok siebie, ale nie wtedy, gdy dwa kontenery są ustawione w stosie

Na przykład:

<SplitPairRule
    <!-- Do not finish primary container activities when all secondary container activities finish. -->
    window:finishPrimaryWithSecondary="never"
    <!-- Finish secondary container activities when all primary container activities finish. -->
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Konfiguracja domyślna

Gdy wszystkie działania w jednym kontenerze w ramach podzielonego zakończenia zajmują cały kontener, pozostały kontener zajmuje całe okno:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podział zawierający aktywności A i B. klasa A kończy się, a element B zajmuje całe okno.

Podział zawierający aktywności A i B. koniec B, a A zajmuje całe okno.

Wspólne kończenie działań

Automatycznie dokończ działania w kontenerze głównym, gdy wszystkie działania w kontenerze dodatkowym zostaną zakończone:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podział zawierający aktywności A i B. Zakończony B, który także kończy A, pozostawiając okno zadania puste.

Podział zawierający aktywności A i B. Kończy zadanie A, a w oknie zadania pozostaje tylko B.

Automatycznie dokończ działania w kontenerze dodatkowym, gdy wszystkie działania w kontenerze głównym zostaną zakończone:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podział zawierający aktywności A i B. Działanie A się zakończyło, co kończy pracę B, pozostawiając okno zadania puste.

Podział zawierający aktywności A i B. Działanie B zostało zakończone, a w oknie zadania pozostaje tylko A.

Zakończ działania razem, gdy zakończą się wszystkie działania w kontenerze głównym lub dodatkowym:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podział zawierający aktywności A i B. Działanie A się zakończyło, co kończy pracę B, pozostawiając okno zadania puste.

Podział zawierający aktywności A i B. Zakończony B, który także kończy A, pozostawiając okno zadania puste.

Kończenie wielu działań w kontenerach

Jeśli wiele aktywności znajduje się w podzielonym kontenerze, wykonanie jednej z nich na dole stosu nie spowoduje automatycznie zakończenia aktywności u góry.

Jeśli na przykład w kontenerze dodatkowym znajdują się 2 działania, nadrzędne są działania C nad nimi:

Dodatkowy stos aktywności zawierający aktywność C nałożony na obszar B jest układany na stosie aktywności podstawowych, który zawiera aktywność A.

a konfigurację podziału definiuje się przez konfigurację działań A i B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

do zakończenia głównej aktywności zachowuje podział.

Podziel z aktywnością A w kontenerze głównym, a aktywności B i C w aktywności dodatkowej
          oraz aktywnościach C skumulowanych na pierwszym planie. Kończy zadanie C, pozostawiając A i B w podziale aktywności.

Zakończenie działania dolnego (głównego) kontenera dodatkowego nie powoduje usunięcia znajdujących się na nim działań, więc zachowuje się podział.

Podziel z aktywnością A w kontenerze głównym, a aktywności B i C w aktywności dodatkowej
          oraz aktywnościach C skumulowanych na pierwszym planie. Kończy zadanie B, pozostawiając A i C w podziale aktywności.

Wszelkie dodatkowe reguły dotyczące kończenia działań razem, np. zakończenie aktywności dodatkowej z aktywnością główną, również są realizowane:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podziel z aktywnością A w kontenerze głównym, a aktywności B i C w kontenerze dodatkowym, przy czym działania C nałożone do kontenera B. na końcu A, a także na końcu B i C.

Gdy podział zostanie skonfigurowany tak, aby zakończyć razem: główną i dodatkową:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Podziel z aktywnością A w kontenerze głównym, a aktywności B i C w aktywności dodatkowej
          oraz aktywnościach C skumulowanych na pierwszym planie. Kończy zadanie C, pozostawiając A i B w podziale aktywności.

Podziel z aktywnością A w kontenerze głównym, a aktywności B i C w aktywności dodatkowej
          oraz aktywnościach C skumulowanych na pierwszym planie. Kończy zadanie B, pozostawiając A i C w podziale aktywności.

Podziel z aktywnością A w kontenerze głównym, a aktywności B i C w aktywności dodatkowej
          oraz aktywnościach C skumulowanych na pierwszym planie. Kończy się na końcu A, a na koniec B i C.

Zmiana właściwości podziału w czasie działania

Nie można zmienić właściwości obecnie aktywnego i widocznego podziału. Zmiana reguł podziału wpływa na dodatkowe uruchamianie aktywności i nowe kontenery, ale nie na istniejące i aktywne podziały.

Aby zmienić właściwości aktywnych podziałów, zakończ aktywność dodatkową w tym przydziale i uruchom jeszcze raz z nową konfiguracją.

Wyodrębnij aktywność z podzielonego do pełnego okna

Utwórz nową konfigurację, która wyświetla pełne okno aktywności bocznej, a następnie ponownie uruchom aktywność z intencją kierującą do tej samej instancji.

Sprawdzanie obsługi podziału w czasie działania

Umieszczanie aktywności jest obsługiwane na Androidzie 12L (poziom interfejsu API 32) i nowszych, ale jest też dostępne na niektórych urządzeniach z wcześniejszymi wersjami platformy. Aby sprawdzić dostępność funkcji w czasie działania, użyj właściwości SplitController.splitSupportStatus lub metody SplitController.getSplitSupportStatus():

Kotlin

if (SplitController.getInstance(this).splitSupportStatus ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Java

if (SplitController.getInstance(this).getSplitSupportStatus() ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Jeśli podziały nie są obsługiwane, aktywności są uruchamiane na początku stosu aktywności (zgodnie z modelem umieszczania aktywności bez aktywności).

Zapobiegaj zastąpieniu systemu

Producenci urządzeń z Androidem (producentom oryginalnego sprzętu lub OEM) mogą wdrożyć umieszczanie aktywności jako funkcję systemu urządzeń. System określa reguły podziału dla aplikacji o wielu działaniach, które zastępują zachowanie w przypadku wyświetlania okien. Zastąpienie przez system wymusza na aplikacjach o wielu aktywnościach zdefiniowany przez system tryb umieszczania aktywności.

Umieszczanie aktywności w systemie może poprawić prezentację aplikacji za pomocą wielu paneli, takich jak szczegóły listy, bez wprowadzania zmian w aplikacji. Umieszczanie aktywności w systemie może jednak powodować nieprawidłowe układy aplikacji, błędy lub konflikty z implementowanymi przez aplikację umieszczaniem aktywności.

Aplikacja może zezwolić na umieszczanie aktywności systemu lub na to zezwolić, ustawiając właściwość w pliku manifestu aplikacji, na przykład:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
            android:value="true|false" />
    </application>
</manifest>

Nazwa właściwości jest zdefiniowana w obiekcie Jetpack WindowManager WindowWłaściwości. Ustaw wartość false, jeśli Twoja aplikacja stosuje umieszczanie aktywności lub jeśli chcesz w inny sposób uniemożliwić systemowi stosowanie w niej reguł tego typu. Ustaw wartość true, aby system mógł zastosować w Twojej aplikacji zdefiniowane przez system umieszczanie aktywności.

Ograniczenia, ograniczenia i zastrzeżenia

  • Tylko aplikacja hostująca zadanie, określona jako właściciel działania głównego w zadaniu, może organizować i umieszczać w zadaniu inne działania. Jeśli działania obsługujące umieszczanie i podziały są uruchamiane w zadaniu należącym do innej aplikacji, w przypadku tych działań umieszczanie i podziały nie będą działać.
  • Czynności można uporządkować tylko w ramach jednego zadania. Uruchomienie działania w nowym zadaniu zawsze powoduje umieszczenie go w nowym, rozszerzonym oknie poza istniejącymi podziałami.
  • Tylko działania w ramach tego samego procesu mogą być porządkowane i podzielone na segmenty. Wywołanie zwrotne SplitInfo zgłasza tylko działania, które należą do tego samego procesu, ponieważ nie ma możliwości sprawdzenia działań w różnych procesach.
  • Każda para lub pojedyncza reguła związana z aktywnością ma zastosowanie tylko do uruchomień działań, które nastąpiły po zarejestrowaniu reguły. Obecnie nie można zaktualizować istniejących podziałów ani ich właściwości wizualnych.
  • Konfiguracja filtra pary podzielonej musi być zgodna z intencjami używanymi podczas całkowitego uruchamiania działań. Dopasowywanie następuje w momencie, gdy rozpoczyna się nowe działanie w ramach procesu aplikacji, więc może nie wiedzieć o nazwach komponentów, które są rozpoznawane w dalszej części procesu systemowego przy użyciu intencji niejawnych. Jeśli w momencie uruchomienia aplikacji nie jest znana nazwa komponentu, można zamiast niej użyć symbolu wieloznacznego („*/*”), a filtrowanie można wykonać na podstawie działania intencji.
  • Obecnie nie ma możliwości przenoszenia działań między kontenerami ani do i z przydziałów po ich utworzeniu. Podziały są tworzone przez bibliotekę WindowManager tylko po uruchomieniu nowych działań z pasującymi regułami, a podziały są niszczone po zakończeniu ostatniej aktywności w podzielonym kontenerze.
  • Działania można uruchamiać ponownie po zmianie konfiguracji, więc po utworzeniu lub usunięciu podziału oraz zmianie granic aktywności może dojść do całkowitego zniszczenia poprzedniej instancji i utworzenia nowej. W związku z tym deweloperzy aplikacji powinni zachować ostrożność podczas uruchamiania nowych działań z wywołań zwrotnych cyklu życia.
  • Urządzenia muszą mieć interfejs rozszerzeń okien, aby obsługiwać umieszczanie aktywności. Prawie wszystkie urządzenia z dużym ekranem z Androidem 12L (poziom interfejsu API 32) lub nowszym mają interfejs. Jednak niektóre urządzenia z dużym ekranem, które nie obsługują wielu aktywności, nie mają interfejsu rozszerzeń okien. Jeśli urządzenie z dużym ekranem nie obsługuje trybu wielu okien, może nie obsługiwać umieszczania aktywności.

Dodatkowe materiały