Umieszczanie aktywności

Umieszczenie aktywności pozwala zoptymalizować aplikacje na urządzeniach z dużym ekranem, dzieląc okno zadania aplikacji między 2 działania lub 2 instancje tej samej aktywności.

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

Jeśli aplikacja obejmuje wiele aktywności, dzięki osadzeniu aktywności możesz zapewnić użytkownikom lepsze wrażenia na tabletach, urządzeniach składanych i urządzeniach z ChromeOS.

Umieszczanie aktywności nie wymaga refaktoryzacji kodu. Aby określić, w jaki sposób aplikacja wyświetla swoje działania – równolegle lub warstwowo – możesz utworzyć plik konfiguracyjny XML lub wywołać interfejs API Jetpack WindowManager.

Obsługa małych ekranów zostanie utrzymana automatycznie. Gdy aplikacja działa na urządzeniu z małym ekranem, aktywności są układane jedna nad drugą. 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łęzienie.

Wbudowanie aktywności umożliwia zmianę orientacji urządzenia i płynnie działa na urządzeniach składanych oraz podczas składania i rozkładania urządzenia w stos.

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

Podzielone okno zadania

Wstawianie aktywności powoduje podział okna zadań aplikacji na 2 kontenery: główny i dodatkowy. Kontenery zawierają działania uruchomione z głównej aktywności lub innych działań, które już znajdują się w kontenerach.

Działania są umieszczane w kontenerze dodatkowym w chwili ich uruchamiania, a dodatkowy kontener – nad kontenerami głównym na małych ekranach, więc ich grupowanie i nawigacja wstecz są zgodne z kolejnością aktywności wbudowanych w aplikację.

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

Rysunek 2. Dwie aktywności obok siebie.

Działanie, które zajmuje całe okno zadania, może też spowodować podział przez uruchomienie nowej aktywności razem z tymi:

Rysunek 3. Aktywność A rozpoczyna działanie B na boku.

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

  • Obok innej aktywności:

    Rysunek 4. Aktywność A rozpoczyna działanie C na boku nad aktywnością B.
  • Z boku i przesuń podział na bok, ukrywając poprzednią aktywność główną:

    Rysunek 5. Działanie B rozpoczyna działanie C na bok i przesuwa podział na boki.
  • Uruchom działanie na górze, tzn. w tym samym stosie aktywności:

    Rysunek 6. Aktywność B rozpoczyna działanie C bez dodatkowych flag intencji.
  • Uruchamianie całego okna działania w tym samym zadaniu:

    Rysunek 7. Aktywność A lub działanie B rozpoczyna działanie C, które wypełnia okno zadania.

Wstecz

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

  • Połączenie: jeśli działania są powiązane i jedna z nich nie powinna być widoczna bez drugiej, można skonfigurować nawigację wsteczną tak, by kończyła obie.
  • Jak to się robi? Jeśli działania są w pełni niezależne, wsteczna nawigacja po działaniu nie wpływa na stan innego działania w oknie zadania.

Podczas korzystania z nawigacji przy użyciu przycisków zdarzenie wstecz jest wysyłane do ostatniej zaznaczonej aktywności. W przypadku nawigacji opartej na gestach zdarzenie przejścia wstecz jest wysyłane do aktywności, w której wystąpił gest.

Układ z wieloma panelami

Jetpack WindowManager umożliwia tworzenie aktywności obejmującej układ z wieloma panelami na urządzeniach z dużymi ekranami 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 czy układach opartych na widokach, np. SlidingPaneLayout, mogą zapewnić lepsze wrażenia użytkownikom dużych ekranów bez refaktoryzacji kodu źródłowego.

Typowy przykład to podział na szczegóły listy. Aby zapewnić wysoką jakość prezentacji, system rozpoczyna działanie związane z listą, po czym aplikacja od razu rozpoczyna działanie związane ze szczegółowymi informacjami. System przejścia czeka, aż obie aktywności zostaną narysowane, a potem wyświetli je razem. Dla użytkownika obie aktywności są uruchamiane jako jedno.

Rysunek 8. Dwie działania zostały uruchomione jednocześnie w układzie z wieloma panelami.

Podziel atrybuty

Możesz określić proporcje okna zadania między podzielonymi kontenerami oraz położenie kontenerów względem siebie.

W przypadku reguł zdefiniowanych w pliku konfiguracji XML ustaw następujące atrybuty:

  • splitRatio: ustawia proporcje kontenera. Wartość jest liczbą zmiennoprzecinkową w odstępie otwartym (0,0; 1,0).
  • splitLayoutDirection: określa sposób układania 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 ustawień regionalnych

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

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

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

Rysunek 9. Dwa podziały aktywności ułożone od lewej do prawej, ale z różnymi proporcjami podziału.

Symbole zastępcze

Działania zastępcze to puste aktywności dodatkowe, które zajmują dany obszar podziału aktywności. Mają one zastąpić inne działanie, które zawiera treści. Na przykład aktywność zastępcza może zajmować drugorzędną stronę podziału aktywności w układzie ze szczegółami listy, dopóki nie zostanie wybrany element z listy. Wtedy aktywność zawierającą szczegółowe informacje dotyczące wybranego elementu listy zastąpi obiekt zastępczy.

Domyślnie system wyświetla symbole zastępcze tylko wtedy, gdy jest wystarczająco dużo miejsca na podział aktywności. Symbole zastępcze automatycznie kończą się, gdy rozmiar wyświetlacza zmieni się na szerokość lub wysokość, która jest za mała, aby można było wyświetlić podział. Gdy pozwala na to miejsce, system uruchamia symbol zastępczy z powrotem zainicjowanym.

Rysunek 10. Składane urządzenie składane i rozkładane. Działanie obiektu zastępczego zostało zakończone i odtworzone po zmianie rozmiaru wyświetlacza.

Jednak atrybut stickyPlaceholder metody SplitPlaceholderRule lub setSticky() w SplitPlaceholder.Builder może 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 zostanie zmniejszony do rozmiaru pojedynczego panelu (przykład znajdziesz w sekcji Konfiguracja podziału).

Rysunek 11. Składane urządzenie składane i rozkładane. Działanie obiektu zastępczego jest trwałe.

Zmiana rozmiaru okna

Gdy zmiany w konfiguracji urządzenia zmniejszają szerokość okna zadania, tak aby nie mieściło się ono w układzie z wieloma panelami (np. gdy duży ekran składa się z tabletu do rozmiaru telefonu lub okno aplikacji zmienia się w trybie wielu okien), aktywności inne niż zastępcze w panelu dodatkowym okna zadania są ułożone nad aktywnościami w panelu głównym.

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

Stosowanie działań jest możliwe, ponieważ funkcja WindowManager porządkuje aktywności w panelu dodatkowym nad działaniami w panelu głównym.

Wiele działań w panelu dodatkowym

Aktywność B rozpoczyna działanie C bez dodatkowych flag intencji:

Podział działań zawierający aktywności A, B i C z działaniem C ustawionym na podstawie pozycji B.

daje to działanie w tej kolejności zależnej od kolejności działań w ramach tego samego zadania:

Dodatkowy stos aktywności zawierający aktywność C ustawioną na pierwszym miejscu na liście B.
          Stos dodatkowy jest umieszczony nad stosem aktywności podstawowej, który zawiera działanie A.

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

Małe okno pokazujące tylko aktywność C.

Powrót do mniejszego okna powoduje przechodzenie między działaniami ułożonymi na siebie.

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

Skumulowane podziały

Działanie B rozpoczyna działanie C na bok i przesuwa podział na boki:

Okno zadania zawierające działania A i B, a następnie działania B i C.

W efekcie działania w ramach tego samego zadania mają taką kolejność (z-order):

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

W mniejszym oknie zadania aplikacja zmniejsza się do 1 działania, a u góry znajduje się klawisz C:

Małe okno pokazujące tylko aktywność C.

Stała orientacja pionowa

Ustawienie w pliku manifestu android:screenOrientation umożliwia aplikacjom ograniczenie aktywności do orientacji pionowej lub poziomej. Aby zwiększyć wygodę użytkowników korzystających z urządzeń z dużymi ekranami, takich jak tablety i urządzenia składane, producenci urządzeń (OEM) mogą ignorować prośby o orientację ekranu oraz wyświetlać aplikację w orientacji pionowej na wyświetlaczu poziomym lub poziomą na wyświetlaczu pionowym.

Rysunek 12. Aktywności w trybie letterbox: stała pionowa na urządzeniu poziomym (po lewej), pozioma na urządzeniu pionowo (po prawej).

Podobnie gdy włączone jest umieszczanie aktywności, producenci OEM mogą dostosowywać urządzenia do aktywności w orientacji poziomej w orientacji poziomej na dużych ekranach (szerokość ≥ 600 dp). Gdy aktywność w ustalonym orientacji pionowej uruchamia drugą aktywność, urządzenie może wyświetlić je obok siebie na wyświetlaczu na 2 panelach.

Rysunek 13. Ćwiczenie w ustawionym pionie A rozpoczyna działanie B na bok.

Zawsze dodawaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED do pliku manifestu aplikacji, aby informować urządzenia, że aplikacja obsługuje umieszczanie aktywności (patrz Konfiguracja podziału poniżej). Urządzenia dostosowane do OEM mogą następnie określić, czy mają być wyświetlane aktywności w orientacji pionowej.

Konfiguracja podziału

Reguły podziału określają podziały aktywności. Reguły podziału definiuje się w pliku konfiguracji XML lub przez wywołania interfejsu API usługi Jetpack WindowManager.

W obu przypadkach aplikacja musi mieć dostęp do biblioteki WindowManager i musi informować system o umieszczaniu aktywności w aplikacji.

Wykonaj te czynności:

  1. Dodaj najnowszą zależność biblioteki WindowManagera 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 zaimplementowana umieszczanie aktywności.

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

    <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 systemie WindowManager w wersji 1.1.0-alfa06 i nowszych podziały umieszczania aktywności są wyłączone, chyba że zostanie dodana właściwość do pliku manifestu i ma wartość Prawda.

    Producenci urządzeń włączają też niestandardowe funkcje w aplikacjach, które obsługują umieszczanie aktywności. Urządzenia mogą na przykład wyświetlać aktywność pionową tylko w orientacji pionowej na wyświetlaczu poziomym, aby ułatwić przejście na układ z 2 panelami po rozpoczęciu drugiej aktywności (patrz Stała orientacja pionowa).

Konfiguracja XML

Aby utworzyć implementację osadzoną aktywności opartą na języku XML, wykonaj te czynności:

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

    • Definiuje aktywności wspólne dla
    • Konfigurowanie opcji 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 systemowi. Biblioteka startup Jetpack Initializer udostępnia plik XML RuleController podczas uruchamiania aplikacji. Dzięki temu reguły zaczynają obowiązywać po rozpoczęciu jakichkolwiek działań.

    Aby utworzyć inicjator, wykonaj te czynności:

    1. Dodaj najnowszą zależność z biblioteką startową 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 metodzie RuleController, przekazując do metody RuleController.parseRules() identyfikator pliku konfiguracji XML (main_split_config.xml).

      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 na potrzeby definicji reguł.

    Dodaj androidx.startup.InitializationProvider do pliku manifestu aplikacji jako <provider>. Dołącz odniesienie 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() w aplikacji. W efekcie reguły podziału obowiązują po rozpoczęciu głównej aktywności w aplikacji.

Interfejs API WindowManager

Możesz zaimplementować umieszczanie aktywności automatycznie za pomocą kilku wywołań interfejsu API. Wywołaj metodę onCreate() podklasy Application, aby mieć pewność, że reguły działają przed uruchomieniem jakichkolwiek działań.

Aby automatycznie utworzyć podział działań:

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

    1. Utwórz element SplitPairFilter, który wskaże aktywności wspólne dla tego 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. Aby dodać 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 dla 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 przypisywania dostępnego obszaru wyświetlania do każdego kontenera aktywności. Typ podziału współczynnika określa proporcje 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 (najpierw kontener główny).
    4. Utwórz 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 podzielonej par, które określają, kiedy należy zastosować regułę, identyfikując aktywności mające ten sam podział.
      • setDefaultSplitAttributes: stosuje do reguły atrybuty układu.
      • setMinWidthDp: określa minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości, dp), która umożliwia podział.
      • setMinSmallestWidthDp: określa minimalną wartość (w dp), którą musi mieć mniejszy z dwóch wymiarów wyświetlacza, aby umożliwić podział niezależnie od orientacji urządzenia.
      • setMaxAspectRatioInPortrait: określa maksymalny współczynnik proporcji wyświetlacza (wysokość:szerokość) w orientacji pionowej, dla której wyświetlane są podziały aktywności. Jeśli współczynnik proporcji ekranu pionowego przekracza maksymalny współczynnik proporcji, podziały są wyłączone niezależnie od szerokości wyświetlacza. Uwaga: wartość domyślna to 1,4, co oznacza, że na większości tabletów działania zajmują całe okno zadania w orientacji pionowej. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT i setMaxAspectRatioInLandscape. Domyślna wartość dla orientacji poziomej to ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary: określa, jak zakończenie wszystkich działań w kontenerze dodatkowym wpływa na aktywności w kontenerze głównym. NEVER oznacza, że system nie powinien kończyć działań głównych, gdy wszystkie działania w kontenerze dodatkowym zostaną zakończone (patrz Kończenie działań).
      • setFinishSecondaryWithPrimary: określa, jak zakończenie wszystkich działań w kontenerze głównym wpływa na aktywności w kontenerze dodatkowym. ALWAYS oznacza, że system powinien zawsze zakończyć działania w kontenerze dodatkowym po zakończeniu wszystkich działań w kontenerze podstawowym (patrz Zakończenie działań).
      • setClearTop: określa, czy wszystkie działania w kontenerze dodatkowym mają kończyć się wraz z uruchomieniem w nim nowej aktywności. Wartość false oznacza, że nowe działania są nakładane na działania, które już znajdują się w kontenerze dodatkowym.
    5. Pobierz pojedyncze wystąpienie WindowManagera RuleController i dodaj regułę:

      Kotlin

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

      Java

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

    1. Utwórz element ActivityFilter identyfikujący aktywność, z którą obiekt zastępczy ma wspólne okno zadania:

      Kotlin

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

      Java

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

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
      

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
      
    3. Utwórz 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 działania, z którymi powiązana jest aktywność zastępcza.
      • Intent: określa uruchomienie aktywności zastępczej.
      • setDefaultSplitAttributes: stosuje do reguły atrybuty układu.
      • setMinWidthDp: określa minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości, dp), która umożliwia podział.
      • setMinSmallestWidthDp: określa minimalną wartość (w dp), którą musi mieć mniejszy z dwóch wymiarów wyświetlacza, aby można było podzielić dane niezależnie od orientacji urządzenia.
      • setMaxAspectRatioInPortrait: określa maksymalny współczynnik proporcji wyświetlacza (wysokość:szerokość) w orientacji pionowej, dla której wyświetlane są podziały aktywności. Uwaga: wartość domyślna to 1,4, co na większości tabletów oznacza, że czynności wypełniają okno zadania w orientacji pionowej. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT i setMaxAspectRatioInLandscape. Domyślna wartość dla 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. Opcja ZAWSZE oznacza, że system powinien zawsze zakończyć działania w kontenerze głównym po zakończeniu działania symbolu zastępczego (patrz Zakończenie działań).
      • setSticky: określa, czy aktywność zastępcza pojawia się na górze stosu aktywności na małych ekranach, gdy po raz pierwszy pojawi się ona w części z odpowiednią minimalną szerokością.
    4. Dodaj regułę do WindowManagera 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 element ActivityFilter identyfikujący aktywność, która zawsze powinna zajmować cały obszar wyświetlania zadania:

      Kotlin

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

      Java

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

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)
      

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
      
    3. Utwórz 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 określające, kiedy zastosować regułę, identyfikując aktywności, które chcesz wykluczyć z podziałów.
      • setAlwaysExpand: określa, czy aktywność ma wypełnić całe okno zadania.
    4. Dodaj regułę do WindowManagera RuleController:

      Kotlin

      ruleController.addRule(activityRule)
      

      Java

      ruleController.addRule(activityRule);
      

Umieszczanie w różnych aplikacjach

Na Androidzie 13 (poziom interfejsu API 33) i nowszych aplikacje mogą umieszczać aktywności z innych aplikacji. Umieszczanie aktywności między aplikacjami lub na podstawie identyfikatorów UID – umożliwia wizualną integrację działań z wielu aplikacji na Androida. System wyświetla aktywność aplikacji hosta i umieszczoną aktywność z innej aplikacji na ekranie obok siebie lub u góry, tak jak w przypadku umieszczania aktywności w pojedynczej aplikacji.

Na przykład w aplikacji Ustawienia można umieścić aktywność selektora tapety z aplikacji TapPicker:

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

Model zaufania

Procesy hostujące, które zawierają działania z innych aplikacji, mogą zmienić sposób prezentacji tych działań, w tym rozmiar, położenie, przycięcie i przezroczystość. Szkodliwe hosty mogą wykorzystywać tę możliwość, by wprowadzić użytkowników w błąd i stworzyć tzw. przechwytywanie kliknięć lub inne ataki polegające na odzyskiwaniu dostępu do interfejsu.

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

Zaufane hosty

Aby umożliwić innym aplikacjom umieszczanie i pełną kontrolę nad ich prezentowaniem, podaj certyfikat SHA-256 aplikacji hosta w atrybucie android:knownActivityEmbeddingCerts elementu <activity> lub <application> pliku manifestu aplikacji.

Ustaw wartość android:knownActivityEmbeddingCerts w postaci ciągu znaków:

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

lub, aby określić wiele certyfikatów, użyj tablicy ciągów znaków:

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

który odwołuje się do zasobu takiego jak:

<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 oddzielających. Więcej informacji znajdziesz w artykułach Generowanie raportu podpisywania i Uwierzytelnianie 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 manifeście aplikacji, na przykład:

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

Domyślna wartość tego atrybutu to false (fałsz), co uniemożliwia umieszczanie aktywności w różnych aplikacjach.

Uwierzytelnianie niestandardowe

Aby ograniczyć ryzyko związane z umieszczaniem niezaufanej aktywności, utwórz niestandardowy mechanizm uwierzytelniania, który weryfikuje tożsamość hosta. Jeśli znasz certyfikaty hosta, użyj biblioteki androidx.security.app.authenticator do uwierzytelniania. Jeśli po umieszczeniu aktywności host uwierzytelni się po umieszczeniu przez Ciebie aktywności, możesz wyświetlić rzeczywistą treść. Jeśli nie, możesz poinformować użytkownika, że dane 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 minimalnego rozmiaru

System Android stosuje do osadzonych aktywności minimalną wysokość i szerokość określone w elemencie manifestu aplikacji <layout>. Jeśli aplikacja nie określa minimalnej wysokości i szerokości, stosowane są wartości domyślne systemu (sw220dp).

Jeśli host spróbuje zmienić rozmiar umieszczonego kontenera tak, aby jego rozmiar był mniejszy niż minimalny, zostanie on rozszerzony i zajmie całe granice zadania.

<alias-aktywności>

Aby osadzanie zaufanych lub niezaufanych aktywności z elementem <activity-alias> działało, do docelowej aktywności należy zastosować parametr android:knownActivityEmbeddingCerts lub android:allowUntrustedActivityEmbedding, a nie alias. Zasada sprawdzająca bezpieczeństwo serwera systemowego opiera się na flagach ustawionych na serwerze docelowym, a nie na aliasie.

Aplikacja hosta

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

Jeśli aplikacja hostująca próbuje umieścić aktywność, która nie ma włączonej opcji umieszczania między aplikacjami, aktywność obejmuje wszystkie granice zadania. W związku z tym aplikacje hostujące muszą wiedzieć, czy aktywności docelowe umożliwiają umieszczanie w różnych aplikacjach.

Jeśli umieszczona aktywność będzie rozpoczynać nowe działanie w ramach tego samego zadania, a nowa aktywność nie ma włączonej opcji umieszczania między aplikacjami, aktywność obejmuje wszystkie granice zadania i nie nakłada się na nią w umieszczonym kontenerze.

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

Przykłady podziału

Podziel od całego okna

Rysunek 15. Aktywność A rozpoczyna działanie B na boku.

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

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

Podziel domyślnie

Jeśli strona docelowa aplikacji jest zaprojektowana w taki sposób, aby podzielić ją na 2 kontenery na dużych ekranach, wygoda użytkowników działa najlepiej wtedy, gdy obie aktywności są tworzone i prezentowane jednocześnie. Jednak treść może być niedostępna w przypadku dodatkowego kontenera podziału, dopóki użytkownik nie wejdzie w interakcję z działaniem w kontenerze głównym (np. wybierze element z menu nawigacyjnego). Aktywność zastępcza może wypełnić lukę do czasu, gdy treść pojawi się w dodatkowym kontenerze podziału (patrz Symbole zastępcze powyżej).

Rysunek 16. Podział utworzony przez jednoczesne otwarcie 2 aktywności. Jedna aktywność jest obiektem zastępczym.

Aby utworzyć podział za pomocą obiektu zastępczego, utwórz obiekt zastępczy i powiąż go z aktywnością główną:

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

Gdy aplikacja otrzyma intencję, aktywność docelowa może zostać wyświetlona jako dodatkowa część podziału aktywności, np. żądanie wyświetlenia ekranu z informacjami o elemencie z listy. Na małych ekranach szczegóły są widoczne w pełnym oknie zadania, a na większych urządzeniach – obok listy.

Rysunek 17. Aktywność związana ze szczegółami precyzyjnego linku jest widoczna samodzielnie na małym ekranie, ale razem z listą na dużym ekranie.

Żądanie uruchomienia powinno być kierowane do głównej aktywności, a działanie związane ze szczegółami celu powinno zostać uruchomione w częściach. System automatycznie wybierze odpowiednią prezentację (stojącą lub obok siebie) na podstawie dostępnej szerokości wyświetlacza.

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ć jedyną aktywnością, która powinna być dostępna dla użytkownika w stosie nawigacji wstecznej. Lepiej nie zamykać działania szczegółów i pozostawiać tylko aktywności głównej:

Duży wyświetlacz z funkcją listy i szczegółami obok siebie.
          Wstecz nie może odrzucić aktywności związanej ze szczegółowymi informacjami i zostawić aktywność związaną z listą na ekranie.

Mały wyświetlacz, który wyświetla tylko szczegóły. Nawigacja wstecz nie może odrzucić aktywności związanej ze szczegółami i wyświetlić aktywności listy.

Zamiast tego możesz wykonać obie aktywności jednocześnie, używając atrybutu finishPrimaryWithSecondary:

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

Zobacz Atrybuty konfiguracji poniżej.

Wiele aktywności w podzielonych kontenerach

Umieszczenie wielu działań w podzielonym kontenerze umożliwia użytkownikom dostęp do szczegółowych treści. Na przykład w przypadku podziału szczegółów listy użytkownik musi przejść do sekcji szczegółów podrzędnych, ale zachować główną aktywność:

Rysunek 18. Aktywność została otwarta w dodatkowym panelu 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));
    }
}

Czynność związana ze szczegółami podrzędnymi jest umieszczona nad aktywnością związaną ze szczegółami, aby ją ukryć:

Użytkownik może wtedy wrócić na poprzedni poziom szczegółów, wracając do stosu:

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

Układanie aktywności na stosie to działanie domyślne, gdy aktywność jest uruchamiana z poziomu działania w tym samym kontenerze dodatkowym. Działania uruchomione z kontenera podstawowego w ramach aktywnego podziału trafiają też do dodatkowego kontenera na górze stosu aktywności.

Działania w nowym zadaniu

Gdy działania w podzielonym oknie zadań rozpoczynają działania w nowym zadaniu, nowe zadanie jest niezależne od zadania, które obejmuje podział, i jest wyświetlane w pełnym oknie. Na ekranie Ostatnie są widoczne 2 zadania: w podziale i w nowym.

Rysunek 20. Rozpocznij aktywność C w nowym zadaniu z działania B.

Zastępowanie aktywności

Aktywności można zastępować w dodatkowym stosie kontenerów, np. gdy aktywność główna jest używana do nawigacji najwyższego poziomu, a aktywność dodatkowa jest wybranym miejscem docelowym. Każdy wybór z nawigacji najwyższego poziomu powinien rozpoczynać nowe działanie w kontenerze dodatkowym i usuwać działania, które wcześniej się tam znajdowały.

Rysunek 21. Działania związane z nawigacją najwyższego poziomu w panelu głównym zastępują działania związane z miejscem docelowym w panelu dodatkowym.

Jeśli po zmianie wyboru nawigacji aplikacja nie ukończy działania w kontenerze dodatkowym, nawigacja wsteczna może być dezorientująca, gdy podział jest zwinięty (gdy urządzenie jest złożone). Jeśli na przykład masz menu w panelu głównym, a ekrany A i B w panelu dodatkowym, po złożeniu telefonu przez użytkownika, element B znajduje się na panelu A, a A na górze menu. Gdy użytkownik cofnie się z miejsca B, zamiast menu pojawi się A.

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

Domyślnym działaniem w przypadku uruchamiania z boku w nowym kontenerze w przypadku obecnego podziału jest umieszczenie nowych kontenerów dodatkowych na wierzchu i zachowanie starych kontenerów w stosie wstecznym. Możesz skonfigurować podziały, aby wyczyścić poprzednie kontenery dodatkowe przy użyciu 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ć tej samej dodatkowej aktywności, a z działania głównego (menu) wyślij nowe intencje, które prowadzą do tej samej instancji, ale aktywują stan lub aktualizację interfejsu w kontenerze dodatkowym.

Wiele podziałów

Aplikacje mogą umożliwiać szczegółową nawigację wielopoziomową, udostępniając dodatkowe aktywności z boku.

Gdy działanie w kontenerze dodatkowym wywoła nową aktywność z boku, w miejscu obecnego podziału zostanie utworzony nowy podział.

Rysunek 22. Aktywność B rozpoczyna działanie C na boku.

Stos wsteczny zawiera wszystkie aktywności, które były wcześniej otwarte, więc użytkownicy mogą przejść do podziału A/B po ukończeniu C.

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

Aby utworzyć nowy podział, uruchom nową aktywność obok istniejącego kontenera dodatkowego. Zadeklaruj konfiguracje podziałów A/B i B/C oraz uruchom działanie C, normalnie od 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 aktywności w aplikacji mogą mieć elementy interfejsu, które pełnią tę samą funkcję. Przykładem może być element sterujący, który otwiera okno z ustawieniami konta.

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

Jeśli 2 działania, których elementy interfejsu są wspólne, występują w podziale, wyświetlanie tego elementu w obu z nich może być zbędne i może być mylące.

Rysunek 24. Zduplikowane elementy interfejsu w podziale aktywności.

Aby dowiedzieć się, kiedy działania są podzielone, sprawdź proces SplitController.splitInfoList lub zarejestruj detektor za pomocą SplitControllerCallbackAdapter pod kątem zmian 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 Używanie współprogramów Kotlin ze komponentami rozpoznającymi cykl życia).

Wywołania zwrotne mogą być wykonywane w dowolnym stanie cyklu życia, w tym w przypadku zatrzymania aktywności. Słuchacze zwykle powinni być zarejestrowani w tym kraju (onStart()) i niezarejestrowani w tym kraju: onStop().

Okno modalne

Niektóre działania uniemożliwiają użytkownikom interakcję z aplikacją do momentu wykonania określonego działania. Na przykład aktywność na ekranie logowania, ekran potwierdzenia zasad lub komunikat o błędzie. Działania modalne powinny nie pojawiać się w podziale.

Aktywność można wymusić, aby zawsze wypełniało okno zadania, korzystając z konfiguracji rozwijania:

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

Zakończ ćwiczenia

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

Rysunek 25. Kończenie czynności B gestem przesuwania.
Rysunek 26. Kończenie czynności A gestem przesuwania.

Jeśli urządzenie jest skonfigurowane do używania przycisku Wstecz zamiast nawigacji przy użyciu gestów, dane wejściowe są wysyłane do aktywności, której użytkownik dotknął lub uruchomił jako ostatni.

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, aby skonfigurować, jak zakończenie wszystkich działań po jednej stronie podziału wpływa na aktywności po drugiej stronie podziału. Są to:

  • 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 kończ działań w powiązanym kontenerze
  • adjacent – zakończ działania w powiązanym kontenerze, gdy oba kontenery wyświetlają się obok siebie, ale nie wtedy, gdy są one stosowe.

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 zakończy się wszystkie działania w jednym kontenerze, pozostały kontener zajmie całe okno:

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

Podział zawierający aktywności A i B. Ukończono A, zostawiając B zajmujące całe okno.

Podział zawierający aktywności A i B. Ukończono B, a klasa A zajmuje całe okno.

Zakończcie razem zadania

Automatycznie dokończ działania w kontenerze głównym po zakończeniu wszystkich działań w kontenerze dodatkowym:

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

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

Podział zawierający aktywności A i B. Ukończono A i pozostawia B bez zmian w oknie zadania.

Automatycznie dokończ działania w kontenerze dodatkowym po zakończeniu wszystkich działań w kontenerze głównym:

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

Podział zawierający aktywności A i B. Ukończono zadanie A, które kończy działanie B, pozostawiając puste okno zadania.

Podział zawierający aktywności A i B. Ukończono B, pozostawiając w oknie zadania bez zmian.

Zakończ działania razem po zakończeniu wszystkich działań 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. Ukończono zadanie A, które kończy działanie B, pozostawiając puste okno zadania.

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

Kończenie wielu działań w kontenerach

Jeśli w podzielonym kontenerze znajduje się wiele działań, zakończenie działania u dołu stosu nie spowoduje automatycznego zakończenia działań na górze.

Jeśli np. w kontenerze dodatkowym znajdują się 2 działania, kod C na początku B:

Dodatkowy stos aktywności zawierający aktywność C nałożoną na działanie B jest ułożony na pierwszym stosie aktywności zawierającej aktywność A.

a konfigurację podziału określa konfiguracja działań A i B:

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

zakończenie ostatniej aktywności powoduje zachowanie podziału.

Podziel z aktywnością A w kontenerze głównym, a działaniami B i C w dodatkowym, a działaniami C ustawionymi na podstawie aktywności B. kończy działanie, pozostawiając A i B w podziale działań.

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

Podziel z aktywnością A w kontenerze głównym, a działaniami B i C w dodatkowym, a działaniami C ustawionymi na podstawie aktywności B. B kończy, pozostawiając A i C w podziale działań.

Wszystkie dodatkowe reguły dotyczące wspólnego kończenia działań, takie jak kończenie działania dodatkowego działaniem głównym, również są wykonywane:

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

Podziel z aktywnością A w kontenerze głównym, a działaniami B i C w kontenerze dodatkowym, a aktywnością C w kontenerze B. A kończy się A, a kończy B i C.

Jeśli podział zostanie skonfigurowany tak, aby oba te elementy zostały ukończone:

<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 działaniami B i C w dodatkowym, a działaniami C ustawionymi na podstawie aktywności B. kończy działanie, pozostawiając A i B w podziale działań.

Podziel z aktywnością A w kontenerze głównym, a działaniami B i C w dodatkowym, a działaniami C ustawionymi na podstawie aktywności B. B kończy, pozostawiając A i C w podziale działań.

Podziel z aktywnością A w kontenerze głównym, a działaniami B i C w dodatkowym, a działaniami C ustawionymi na podstawie aktywności B. A kończy B i C.

Zmieniaj 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, dokończ aktywność dodatkową lub działania w ramach podziału i uruchom ponownie z nową konfiguracją.

Wyodrębnianie aktywności z podziału do pełnego okna

Utwórz nową konfigurację, która wyświetla pełne okno aktywności z boku, a następnie ponownie uruchom działanie z intencją, która przejdzie do tej samej instancji.

Sprawdzanie podzielonej obsługi 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 nad stosem aktywności (zgodnie z modelem umieszczania braku aktywności).

Zapobiegaj zastąpieniu systemu

Producenci urządzeń z Androidem (producenci oryginalnego sprzętu lub OEM) mogą wdrożyć umieszczanie aktywności jako funkcję systemu urządzenia. System określa podział reguł w przypadku aplikacji wielozadaniowych, zastępując działanie okien przez te aplikacje. Zastąpienie systemu wymusza przechodzenie aplikacji wielozadaniowych do trybu umieszczania aktywności zdefiniowanego przez system.

Osadzanie aktywności systemu może poprawić prezentację aplikacji za pomocą układów z wieloma panelami, np. list-detail, bez wprowadzania zmian w aplikacji. Umieszczanie aktywności w systemie może też jednak powodować nieprawidłowy układ aplikacji, błędy lub konflikty z umieszczaniem aktywności wdrożonym przez aplikację.

Aplikacja może zapobiegać umieszczaniu aktywności systemu w witrynach lub zezwalać na ich umieszczanie, ustawiając w pliku manifestu usługę, 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 WindowWłaściwości Jetpack WindowManager. Ustaw wartość false, jeśli Twoja aplikacja obsługuje umieszczanie aktywności lub jeśli chcesz w inny sposób uniemożliwić systemowi stosowanie w aplikacji swoich reguł umieszczania aktywności. Ustaw wartość na true, aby umożliwić systemowi stosowanie w aplikacji umieszczania aktywności zdefiniowanej przez system.

Ograniczenia i zastrzeżenia

  • Tylko aplikacja hostująca zadanie może porządkować i umieszczać w zadaniu inne działania jako właściciel głównej aktywności w zadaniu. Jeśli działania obsługujące umieszczanie i podziały są uruchamiane w zadaniu, które należy do innej aplikacji, w przypadku tych aktywności umieszczanie i podziały nie będą działać.
  • Działania można uporządkować tylko w ramach jednego zadania. Uruchomienie działania w nowym zadaniu zawsze umieszcza je w nowym rozwiniętym oknie poza istniejącymi podziałami.
  • Tylko działania w ramach tego samego procesu można uporządkować i podzielić. Wywołanie zwrotne SplitInfo zgłasza tylko działania należące do tego samego procesu, ponieważ nie można dowiedzieć się o działaniach w różnych procesach.
  • Każda reguła dotycząca pary lub pojedynczej aktywności ma zastosowanie tylko do uruchomień, które miały miejsce po jej zarejestrowaniu. Obecnie nie można aktualizować 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 ma miejsce w momencie, gdy nowe działanie jest uruchamiane z procesu aplikacji, więc może nie znać nazw komponentów, które są rozpoznawane później w procesie systemowym przy użyciu intencji niejawnych. Jeśli nazwa komponentu nie jest znana w chwili uruchomienia, zamiast niej można użyć symbolu wieloznacznego („*/*”), a filtrowanie można przeprowadzić na podstawie działania intencji.
  • Obecnie nie można przenosić aktywności między kontenerami ani do nich i poza nimi po ich utworzeniu. Podziały tworzy biblioteka WindowManager tylko po uruchomieniu nowych aktywności z pasującymi regułami, a podziały są niszczone po zakończeniu ostatniej aktywności w podzielonym kontenerze.
  • Działania można uruchomić ponownie po zmianie konfiguracji, więc po utworzeniu lub usunięciu podziału i zmianie granic aktywności aktywność może zostać całkowicie zniszczona przez poprzednią instancję i utworzyć nową. Deweloperzy aplikacji powinni więc ostrożnie uruchamiać nowe działania z wywołań zwrotnych cyklu życia.
  • Aby obsługiwać umieszczanie aktywności, urządzenia muszą zawierać interfejs rozszerzeń okien. Prawie wszystkie urządzenia z Androidem 12L (poziom interfejsu API 32) lub nowszym mają ten interfejs. Jednak niektóre urządzenia z dużym ekranem, na których nie można wykonywać wielu działań, nie zawierają 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