Umieszczenie aktywności

Osadzanie aktywności optymalizuje aplikacje na urządzeniach z dużym ekranem, dzieląc okno zadania aplikacji na 2 aktywności lub 2 wystąpienia tej samej aktywności.

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

Jeśli Twoja aplikacja składa się z wielu aktywności, osadzanie aktywności umożliwia zapewnienie lepszego komfortu użytkowania na tabletach, urządzeniach składanych i urządzeniach z ChromeOS.

Osadzanie aktywności nie wymaga refaktoryzacji kodu. Sposób wyświetlania aktywności aplikacji – obok siebie lub jedna na drugiej – możesz określić, tworząc plik konfiguracji XML lub wywołując interfejs Jetpack WindowManager API.

Obsługa małych ekranów jest utrzymywana automatycznie. Gdy aplikacja jest uruchomiona na urządzeniu z małym ekranem, aktywności są ułożone jedna na drugiej. Na dużych ekranach aktywności są wyświetlane obok siebie. System określa sposób prezentacji na podstawie utworzonej przez Ciebie konfiguracji. Nie wymaga to logiki rozgałęzienia.

Osadzanie aktywności dostosowuje się do zmian orientacji urządzenia i działa bezproblemowo na urządzeniach składanych, układając i rozdzielając aktywności w miarę składania i rozkładania urządzenia.

Osadzanie aktywności jest obsługiwane na większości urządzeń z dużym ekranem z Androidem 12L (API na poziomie 32) lub nowszym.

Podziel okno zadania

Osadzanie aktywności dzieli okno zadania aplikacji na 2 kontenery: główny i dodatkowy. Kontenery zawierają działania uruchomione z głównego działania lub z innych działań, które już się w nich znajdują.

Aktywności są układane w kontenerze dodatkowym w kolejności ich uruchamiania, a kontener dodatkowy jest układany nad kontenerem głównym na małych ekranach, więc układanie aktywności i nawigacja wstecz są zgodne z kolejnością aktywności wbudowaną już w aplikację.

Osadzanie aktywności umożliwia wyświetlanie aktywności na różne sposoby. Aplikacja może podzielić okno zadania, uruchamiając 2 aktywności jednocześnie obok siebie lub jedną nad drugą:

Rysunek 2. Dwie aktywności obok siebie i jedna nad drugą.

Aktywność zajmująca całe okno zadania może utworzyć podział, uruchamiając nową aktywność obok:

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

Aktywności, które są już w oknie podzielonym i współdzielą okno zadania, mogą uruchamiać inne aktywności w następujący sposób:

  • Obok innej aktywności:

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

    Rysunek 5. Aktywność B uruchamia aktywność C z boku i przesuwa podział na bok.
  • Uruchom działanie w miejscu na górze, czyli w tym samym stosie działań:

    Rysunek 6. Aktywność B uruchamia aktywność C bez dodatkowych flag intencji.
  • Uruchom aktywność w pełnym oknie w ramach tego samego zadania:

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

Przechodzenie wstecz

Różne typy aplikacji mogą mieć różne reguły nawigacji wstecznej w stanie okna podzielonego zadania w zależności od zależności między aktywnościami lub sposobu, w jaki użytkownicy wywołują zdarzenie powrotu, np.:

  • Powiązane działania: jeśli działania są powiązane i jedno nie powinno być wyświetlane bez drugiego, można skonfigurować nawigację wstecz, aby zakończyć oba działania.
  • Samodzielne działanie: jeśli aktywności są całkowicie niezależne, nawigacja wstecz w jednej z nich nie wpływa na stan innej aktywności w oknie zadania.

Zdarzenie wstecz jest wysyłane do ostatniej aktywności, na której skupiono uwagę, gdy używana jest nawigacja za pomocą przycisków.

W przypadku nawigacji przy użyciu gestów:

  • Android 14 (poziom 34 interfejsu API) i starszy – zdarzenie powrotu jest wysyłane do aktywności, w której wykonano gest. Gdy użytkownicy przesuną palcem od lewej strony ekranu, do aktywności w lewym okienku podzielonego ekranu zostanie wysłane zdarzenie wstecz. Gdy użytkownicy przesuną palcem od prawej strony ekranu, zdarzenie wstecz zostanie wysłane do aktywności w panelu po prawej stronie.

  • Android 15 (poziom 35 interfejsu API) lub nowszy

    • W przypadku wielu aktywności z tej samej aplikacji gest kończy aktywność na górze niezależnie od kierunku przesunięcia, co zapewnia bardziej spójne działanie.

    • W przypadku 2 aktywności z różnych aplikacji (nakładki) zdarzenie powrotu jest kierowane do ostatniej aktywności, która była w centrum uwagi, co jest zgodne z działaniem nawigacji za pomocą przycisków.

Układ wielopanelowy

Biblioteka Jetpack WindowManager umożliwia tworzenie układów z wieloma panelami osadzonymi w aktywności 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 są oparte na wielu działaniach, a nie na fragmentach lub układach opartych na widokach, takich jak SlidingPaneLayout, mogą zapewnić lepsze wrażenia użytkownika na dużym ekranie bez refaktoryzacji kodu źródłowego.

Jednym z częstych przykładów jest podział na listę i szczegóły. Aby zapewnić wysoką jakość prezentacji, system uruchamia aktywność listy, a następnie aplikacja natychmiast uruchamia aktywność szczegółów. System przejścia czeka, aż obie aktywności zostaną narysowane, a następnie wyświetla je razem. Dla użytkownika te 2 działania uruchamiają się jako jedno.

Rysunek 8. Dwie aktywności uruchomione jednocześnie w układzie z wieloma panelami.

Dzielenie atrybutów

Możesz określić, jak okno zadania jest podzielone między kontenery i jak są one ułożone względem siebie.

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

  • splitRatio: określa proporcje kontenera. Wartość jest liczbą zmiennoprzecinkową z przedziału otwartego (0,0, 1,0).
  • splitLayoutDirection: określa sposób rozmieszczenia 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 w sekcji Konfiguracja XML.

W przypadku reguł utworzonych przy użyciu interfejsów API WindowManager utwórz obiekt SplitAttributesSplitAttributes.Builder i wywołaj te metody narzędzia do tworzenia:

Przykłady znajdziesz 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.

Orientacja podzielonego ekranu

Wymiary i proporcje wyświetlacza określają położenie aktywności w podziałach osadzania aktywności. Na dużych wyświetlaczach w orientacji poziomej aktywności są wyświetlane obok siebie, a na wysokich wyświetlaczach w orientacji pionowej lub na urządzeniach składanych w pozycji stołowej – jedna nad drugą.

Orientację podziału możesz określić za pomocą kalkulatora SplitController SplitAttributes. Kalkulator oblicza SplitAttributes dla aktywnego SplitRule.

Użyj kalkulatora, aby podzielić kontener nadrzędny w różnych kierunkach dla różnych stanów urządzenia, np.:

Kotlin

if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator { params ->
        val parentConfiguration = params.parentConfiguration
        val builder = SplitAttributes.Builder()
        return@setSplitAttributesCalculator if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build()
        } else if (parentConfiguration.screenHeightDp >= 600) {
            // Horizontal split for tall displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
                .build()
        } else {
            // Fallback to expand the secondary container.
            builder
                .setSplitType(SPLIT_TYPE_EXPAND)
                .build()
        }
    }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
        Configuration parentConfiguration = params.getParentConfiguration();
        SplitAttributes.Builder builder = new SplitAttributes.Builder();
        if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build();
        } else if (parentConfiguration.screenHeightDp >= 600) {
            // Horizontal split for tall displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
                .build();
        } else {
            // Fallback to expand the secondary container.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_EXPAND)
                .build();
        }
    });
}

Na urządzeniach składanych możesz podzielić ekran pionowo, jeśli urządzenie jest w orientacji poziomej, wyświetlić pojedynczą aktywność, jeśli urządzenie jest w orientacji pionowej, i podzielić ekran poziomo, jeśli urządzenie jest ustawione na stole:

Kotlin

if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator { params ->
        val tag = params.splitRuleTag
        val parentWindowMetrics = params.parentWindowMetrics
        val parentConfiguration = params.parentConfiguration
        val foldingFeatures =
            params.parentWindowLayoutInfo.displayFeatures.filterIsInstance<FoldingFeature>()
        val feature = if (foldingFeatures.size == 1) foldingFeatures[0] else null
        val builder = SplitAttributes.Builder()
        builder.setSplitType(SPLIT_TYPE_HINGE)
        return@setSplitAttributesCalculator if (feature?.isSeparating == true) {
            // Horizontal split for tabletop posture.
            builder
                .setSplitType(SPLIT_TYPE_HINGE)
                .setLayoutDirection(
                    if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL) {
                        SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
                    } else {
                        SplitAttributes.LayoutDirection.LOCALE
                    }
                )
                .build()
        } else if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build()
        } else {
            // No split for tall displays.
            builder
                .setSplitType(SPLIT_TYPE_EXPAND)
                .build()
        }
    }
}

Java

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
        String tag = params.getSplitRuleTag();
        WindowMetrics parentWindowMetrics = params.getParentWindowMetrics();
        Configuration parentConfiguration = params.getParentConfiguration();
        List<FoldingFeature> foldingFeatures =
            params.getParentWindowLayoutInfo().getDisplayFeatures().stream().filter(
                    item -> item instanceof FoldingFeature)
                .map(item -> (FoldingFeature) item)
                .collect(Collectors.toList());
        FoldingFeature feature = foldingFeatures.size() == 1 ? foldingFeatures.get(0) : null;
        SplitAttributes.Builder builder = new SplitAttributes.Builder();
        builder.setSplitType(SplitType.SPLIT_TYPE_HINGE);
        if (feature != null && feature.isSeparating()) {
            // Horizontal slit for tabletop posture.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_HINGE)
                .setLayoutDirection(
                    feature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL
                        ? SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
                        : SplitAttributes.LayoutDirection.LOCALE)
                .build();
        }
        else if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build();
        } else {
            // No split for tall displays.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_EXPAND)
                .build();
        }
    });
}

Obiekty zastępcze

Aktywności zastępcze to puste aktywności dodatkowe, które zajmują obszar podziału aktywności. Ostatecznie mają one zostać zastąpione inną aktywnością zawierającą treści. Na przykład aktywność zastępcza może zajmować drugą stronę podziału aktywności w układzie lista-szczegóły, dopóki nie zostanie wybrany element z listy. W tym momencie aktywność zawierająca szczegółowe informacje o wybranym elemencie listy zastąpi aktywność zastępczą.

Domyślnie system wyświetla symbole zastępcze tylko wtedy, gdy jest wystarczająco dużo miejsca na podział aktywności. Miejsca docelowe są automatycznie zamykane, gdy rozmiar wyświetlacza zmieni się na szerokość lub wysokość zbyt małą, aby wyświetlić podział. Gdy jest to możliwe, system ponownie uruchamia element zastępczy w zresetowanym stanie.

Rysunek 10. Składane urządzenie jest składane i rozkładane. Aktywność Placeholder zostaje zakończona i utworzona ponownie po zmianie rozmiaru wyświetlacza.

Atrybut stickyPlaceholder metody SplitPlaceholderRule lub setSticky() interfejsu SplitPlaceholder.Builder może jednak zastąpić domyślne działanie. Jeśli atrybut lub metoda określa wartość true, system wyświetla element zastępczy jako aktywność na samej górze okna zadania, gdy rozmiar ekranu zostanie zmniejszony z 2-panelowego do 1-panelowego (przykład znajdziesz w sekcji Konfiguracja podziału).

Rysunek 11. Składane urządzenie jest składane i rozkładane. Działanie Placeholder jest trwałe.

Zmiany rozmiaru okna

Gdy zmiany konfiguracji urządzenia zmniejszą szerokość okna zadania tak, że nie będzie ono wystarczająco duże, aby pomieścić układ wielopanelowy (np. gdy duże urządzenie składane z ekranem o rozmiarze tabletu zostanie złożone do rozmiaru telefonu lub gdy rozmiar okna aplikacji zostanie zmieniony w trybie wielu okien), aktywności inne niż zastępcze w panelu dodatkowym okna zadania zostaną ułożone jedna na drugiej nad aktywnościami w panelu głównym.

Zastępcze aktywności są wyświetlane tylko wtedy, gdy jest wystarczająca szerokość ekranu, aby podzielić go na 2 części. Na mniejszych ekranach element zastępczy jest automatycznie zamykany. Gdy obszar wyświetlania znów będzie wystarczająco duży, element zastępczy zostanie utworzony ponownie. (Patrz sekcja Symbole zastępcze).

Nakładanie aktywności jest możliwe, ponieważ WindowManager określa kolejność aktywności w panelu dodatkowym powyżej aktywności w panelu głównym.

Wiele aktywności w panelu dodatkowym

Aktywność B uruchamia aktywność C w miejscu bez dodatkowych flag intencji:

Podział aktywności zawierający aktywności A, B i C, przy czym C jest ułożona na B.

W rezultacie kolejność działań w tym samym zadaniu będzie następująca:

Dodatkowy stos działań zawierający działanie C ułożone na B.
          Stos pomocniczy jest ułożony na stosie głównym działań
          zawierającym działanie A.

W mniejszym oknie zadania aplikacja zmniejsza się do pojedynczej aktywności z aktywnością C na górze stosu:

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

Cofanie się w mniejszym oknie powoduje przełączanie się między aktywnościami ułożonymi jedna na drugiej.

Jeśli okno zadania zostanie przywrócone do większego rozmiaru, który może pomieścić wiele paneli, aktywności będą ponownie wyświetlane obok siebie.

Skumulowane podziały

Aktywność B uruchamia aktywność C z boku i przesuwa podział na bok:

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

W rezultacie kolejność działań w tym samym zadaniu będzie następująca:

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

W mniejszym oknie zadania aplikacja zmniejsza się do pojedynczej aktywności z aktywnością C na górze:

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

Stała orientacja pionowa

Ustawienie manifestu android:screenOrientation umożliwia aplikacjom ograniczenie aktywności do orientacji pionowej lub poziomej. Aby zwiększyć wygodę użytkowników urządzeń z dużym ekranem, takich jak tablety i urządzenia składane, producenci urządzeń (OEM) mogą ignorować prośby o orientację ekranu i wyświetlać aplikację w formacie letterbox w orientacji pionowej na wyświetlaczach poziomych lub w orientacji poziomej na wyświetlaczach pionowych.

Rysunek 12. Aktywności w formacie letterbox: stała orientacja pionowa na urządzeniu w orientacji poziomej (po lewej), stała orientacja pozioma na urządzeniu w orientacji pionowej (po prawej).

Podobnie, gdy włączone jest osadzanie aktywności, producenci OEM mogą dostosowywać urządzenia do wyświetlania aktywności w orientacji pionowej w formacie letterbox w orientacji poziomej na dużych ekranach (szerokość ≥ 600 dp). Gdy działanie o stałej orientacji pionowej uruchamia drugie działanie, urządzenie może wyświetlać oba działania obok siebie na ekranie dwupanelowym.

Rysunek 13. Aktywność A w 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 osadzanie aktywności (patrz sekcja Konfiguracja podziału). Urządzenia dostosowane przez producentów OEM mogą wtedy określać, czy wyświetlać aktywności w stałej orientacji pionowej w formacie letterbox.

Konfiguracja podziału

Reguły podziału konfigurują podziały aktywności. Reguły podziału określa się w pliku konfiguracji XML lub przez wywoływanie interfejsu API WindowManager Jetpacka.

W obu przypadkach aplikacja musi mieć dostęp do biblioteki WindowManager i informować system, że zaimplementowała osadzanie 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 osadzania aktywności.

  2. Poinformuj system, że Twoja aplikacja implementuje osadzanie aktywności.

    Dodaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED do elementu <application> w 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 przypadku WindowManager w wersji 1.1.0-alpha06 i nowszych podziały osadzania aktywności są wyłączone, chyba że właściwość zostanie dodana do manifestu i ustawiona na wartość „true”.

    Producenci urządzeń używają tego ustawienia również do włączania niestandardowych funkcji w aplikacjach obsługujących osadzanie aktywności. Na przykład urządzenia mogą wyświetlać aktywność w trybie pionowym na ekranach w trybie poziomym, aby dostosować ją do przejścia na układ dwupanelowy, gdy rozpocznie się druga aktywność (patrz Stała orientacja pionowa).

Konfiguracja XML

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

  1. Utwórz plik zasobu XML, który:

    • Określa działania, które mają wspólny podział
    • Konfigurowanie opcji podziału
    • Tworzy element 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 Jetpack Startup Initializer udostępnia plik XML RuleController podczas uruchamiania aplikacji, dzięki czemu reguły obowiązują od momentu rozpoczęcia działania dowolnych aktywności.

    Aby utworzyć inicjator, wykonaj te czynności:

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

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

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

      Inicjator udostępnia reguły podziału programowi 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 RuleControllerSplitInitializer:

    <!-- 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. W rezultacie reguły podziału obowiązują od momentu uruchomienia głównej aktywności aplikacji.

WindowManager API

Osadzanie aktywności możesz zaimplementować programowo za pomocą kilku wywołań interfejsu API. Wywołuj te funkcje w metodzie onCreate() podklasy Application, aby mieć pewność, że reguły będą obowiązywać przed rozpoczęciem jakichkolwiek działań.

Aby utworzyć podział aktywności za pomocą kodu:

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

    1. Utwórz SplitPairFilter, który identyfikuje aktywności, które mają wspólny podział:

      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 dla podziału:

      Kotlin

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

      Java

      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, jak dostępny obszar wyświetlania jest przydzielany do poszczególnych kontenerów aktywności. Typ podziału proporcji określa proporcję dostępnego obszaru wyświetlania przydzielonego do kontenera głównego. Kontener dodatkowy zajmuje pozostałą część dostępnego obszaru wyświetlania.
      • setLayoutDirection(): określa sposób rozmieszczenia kontenerów aktywności względem siebie, przy czym pierwszy jest kontenerem głównym.
    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 podziału, które określają, kiedy zastosować regułę, identyfikując aktywności, które mają wspólny podział.
      • setDefaultSplitAttributes(): stosuje atrybuty układu do reguły.
      • 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), jaką musi mieć mniejszy z 2 wymiarów wyświetlacza, aby włączyć podział niezależnie od orientacji urządzenia.
      • setMaxAspectRatioInPortrait(): określa maksymalny współczynnik proporcji wyświetlania (wysokość:szerokość) w orientacji pionowej, dla którego wyświetlane są podziały aktywności. Jeśli współczynnik proporcji wyświetlacza w orientacji pionowej 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 powoduje, że aktywności zajmują całe okno zadania w orientacji pionowej na większości tabletów. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape(). Wartością domyślną w przypadku orientacji poziomej jest ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary(): określa, jak zakończenie wszystkich działań w kontenerze dodatkowym wpływa na działania w kontenerze głównym. NEVER oznacza, że system nie powinien kończyć głównych działań, gdy wszystkie działania w kontenerze pomocniczym zostaną zakończone (patrz Zakończ działania).
      • setFinishSecondaryWithPrimary(): określa, jak zakoń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, gdy wszystkie działania w kontenerze głównym zostaną zakończone (patrz Zakończ działania).
      • setClearTop(): określa, czy wszystkie aktywności w kontenerze dodatkowym są kończone, gdy w kontenerze zostanie uruchomiona nowa aktywność. Wartość false oznacza, że nowe aktywności są układane na aktywnościach, które już znajdują się w kontenerze dodatkowym.
    5. Pobierz instancję singletona WindowManager RuleController i dodaj regułę:

      Kotlin

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

      Java

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

    6. Utwórz element zastępczy dla kontenera dodatkowego, gdy treść jest niedostępna:

    7. Utwórz ActivityFilter, który identyfikuje aktywność, z którą symbol zastępczy dzieli okno zadania:

      Kotlin

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

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );

    8. Dodaj filtr do zestawu filtrów:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);

    9. 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(this, 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 aktywności zastępczej.
      • 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ł.
      • setMinSmallestWidthDp(): ustawia minimalną wartość (w dp), jaką musi mieć mniejszy z 2 wymiarów wyświetlacza, aby umożliwić podział niezależnie od orientacji urządzenia.
      • setMaxAspectRatioInPortrait(): ustawia maksymalny współczynnik proporcji wyświetlania (wysokość:szerokość) w orientacji pionowej, dla którego wyświetlane są podziały aktywności. Uwaga: wartość domyślna to 1,4, co powoduje, że aktywności wypełniają okno zadania w orientacji pionowej na większości tabletów. Zobacz też SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape(). Wartością domyślną w przypadku orientacji poziomej jest ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder(): Określa, jak zakończenie aktywności w obiekcie zastępczym wpływa na aktywności w kontenerze głównym. ALWAYS oznacza, że system powinien zawsze kończyć działania w kontenerze podstawowym, gdy kończy się działanie w kontenerze zastępczym (patrz Kończenie działań).
      • setSticky(): określa, czy aktywność zastępcza ma się pojawiać na górze stosu aktywności na małych wyświetlaczach po tym, jak po raz pierwszy pojawiła się w podziale o odpowiedniej minimalnej szerokości.
    10. Dodaj regułę do RuleController WindowManager:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);

  2. Określ czynności, które nigdy nie powinny być częścią podziału:

    1. Utwórz element ActivityFilter, który identyfikuje aktywność, która powinna zawsze 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. Dodaj 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, które określają, kiedy należy 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 RuleController WindowManager:

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

Umieszczanie w różnych aplikacjach

Na Androidzie 13 (API na poziomie 33) i nowszym aplikacje mogą osadzać aktywności z innych aplikacji. Osadzanie aktywności w różnych aplikacjach lub w różnych identyfikatorach użytkownika umożliwia wizualną integrację aktywności z wielu aplikacji na Androida. System wyświetla aktywność aplikacji hosta i aktywność zagnieżdżoną z innej aplikacji obok siebie lub jedna pod drugą, tak jak w przypadku zagnieżdżania aktywności w jednej aplikacji.

Na przykład aplikacja Ustawienia może osadzić aktywność selektora tapet z aplikacji WallpaperPicker:

Rysunek 14. Aplikacja Ustawienia (menu po lewej stronie) z selektorem tapet jako aktywnością osadzoną (po prawej stronie).

Model zaufania

Procesy hosta, które osadzają aktywności z innych aplikacji, mogą zmieniać sposób prezentacji osadzonych aktywności, w tym ich rozmiar, położenie, przycinanie i przezroczystość. Złośliwe hosty mogą wykorzystywać tę funkcję do wprowadzania użytkowników w błąd i przeprowadzania ataków typu clickjacking lub innych ataków polegających na modyfikowaniu interfejsu.

Aby zapobiec niewłaściwemu wykorzystaniu osadzania aktywności w różnych aplikacjach, Android wymaga, aby aplikacje wyrażały zgodę na osadzanie swoich aktywności. Aplikacje mogą oznaczać hosty jako zaufane lub niezaufane.

Zaufane hosty

Aby umożliwić innym aplikacjom osadzanie i pełne kontrolowanie prezentacji działań z Twojej aplikacji, w atrybucie android:knownActivityEmbeddingCerts elementu <activity> lub <application> w pliku manifestu aplikacji określ certyfikat SHA-256 aplikacji hosta.

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, tablicę 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 ten:

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

Właściciele aplikacji mogą uzyskać skrót certyfikatu SHA, uruchamiając zadanie Gradle signingReport. Skrót certyfikatu to odcisk cyfrowy SHA-256 bez dwukropków oddzielających. Więcej informacji znajdziesz w sekcjach Uruchamianie raportu podpisywaniaUwierzytelnianie klienta.

Niezaufane hosty

Aby zezwolić dowolnej aplikacji na osadzanie działań Twojej aplikacji i kontrolowanie ich prezentacji, w elementach <activity> lub <application> w pliku manifestu aplikacji określ atrybut android:allowUntrustedActivityEmbedding, na przykład:

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

Domyślna wartość atrybutu to false, co uniemożliwia osadzanie aktywności w innych aplikacjach.

Uwierzytelnianie niestandardowe

Aby ograniczyć ryzyko związane z osadzaniem 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 gospodarz uwierzytelni się po osadzeniu Twojej aktywności, możesz wyświetlić rzeczywistą treść. Jeśli nie, 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 osadza Twoją aktywność, np.:

Kotlin

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

Java

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

Ograniczenie minimalnego rozmiaru

System Android stosuje minimalną wysokość i szerokość określoną w elemencie <layout> pliku manifestu aplikacji do osadzonych aktywności. Jeśli aplikacja nie określi minimalnej wysokości i szerokości, zostaną zastosowane domyślne wartości systemowe (sw220dp).

Jeśli host spróbuje zmienić rozmiar osadzonego kontenera na mniejszy niż minimalny, osadzony kontener rozszerzy się, aby zajmować całą przestrzeń zadania.

<activity-alias>

Aby osadzanie zaufanych lub niezaufanych działań działało z elementem <activity-alias>, do działania docelowego należy zastosować android:knownActivityEmbeddingCerts lub android:allowUntrustedActivityEmbedding, a nie alias. Zasady weryfikujące bezpieczeństwo na serwerze systemowym są oparte na flagach ustawionych na celu, a nie na aliasie.

Aplikacja hostująca

Aplikacje hostujące implementują osadzanie aktywności w różnych aplikacjach w taki sam sposób jak osadzanie aktywności w jednej aplikacji. Obiekty SplitPairRuleSplitPairFilter lub ActivityRuleActivityFilter określają osadzone aktywności i podziały okna zadań. Reguły podziału są definiowane statycznie w XML lub w czasie działania za pomocą wywołań interfejsu Jetpack WindowManager API.

Jeśli aplikacja hostująca spróbuje osadzić aktywność, która nie została włączona do osadzania w innych aplikacjach, aktywność zajmie cały obszar zadania. W związku z tym aplikacje hosta muszą wiedzieć, czy docelowe aktywności zezwalają na osadzanie w innych aplikacjach.

Jeśli osadzona aktywność rozpocznie nową aktywność w tym samym zadaniu, a nowa aktywność nie będzie korzystać z osadzania w innych aplikacjach, zajmie ona całe granice zadania, zamiast nakładać się na aktywność w osadzonym kontenerze.

Aplikacja hostująca może osadzać własne działania bez ograniczeń, o ile są one uruchamiane w ramach tego samego zadania.

Przykłady podziału

Dzielenie ekranu z poziomu pełnego okna

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

Nie wymaga refaktoryzacji. Konfigurację podziału możesz zdefiniować statycznie lub w czasie działania, a następnie wywołać funkcję Context#startActivity() bez dodatkowych parametrów.

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

Domyślnie podzielone

Jeśli strona docelowa aplikacji jest przeznaczona do podzielenia na 2 kontenery na dużych ekranach, najlepsze wrażenia użytkownika są wtedy, gdy oba działania są tworzone i wyświetlane jednocześnie. Treści mogą jednak nie być dostępne w przypadku drugiego kontenera podzielonego ekranu, dopóki użytkownik nie wejdzie w interakcję z aktywnością w kontenerze głównym (np. nie wybierze elementu z menu nawigacyjnego). Aktywność zastępcza może wypełnić lukę, dopóki w drugim kontenerze podziału nie będzie można wyświetlić treści (patrz sekcja Obiekty zastępcze).

Rysunek 16. Podział utworzony przez otwarcie 2 aktywności jednocześnie. Jedna aktywność jest symbolem zastępczym.

Aby utworzyć podział z symbolem zastępczym, utwórz symbol zastępczy i powiąż go z aktywnością podstawową:

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

Gdy aplikacja otrzyma intencję, docelowa aktywność może być wyświetlana jako druga część podzielonej aktywności, np. prośba o wyświetlenie ekranu szczegółów z informacjami o elemencie z listy. Na małych wyświetlaczach szczegóły są widoczne w pełnym oknie zadań, a na większych urządzeniach – obok listy.

Rysunek 17. Szczegóły precyzyjnego linku wyświetlane osobno na małym ekranie, ale razem z listą na dużym ekranie.

Żądanie uruchomienia powinno być kierowane do głównej aktywności, a aktywność z docelowymi szczegółami powinna być uruchamiana w trybie podzielonego ekranu. System automatycznie wybiera odpowiedni sposób prezentacji – w układzie pionowym lub obok siebie – na podstawie dostępnej szerokości wyświetlacza.

Kotlin

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

Java

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

Miejsce docelowe linku precyzyjnego może być jedyną aktywnością, która powinna być dostępna dla użytkownika w stosie nawigacji wstecznej. Możesz chcieć uniknąć zamknięcia aktywności szczegółów i pozostawienia tylko aktywności głównej:

Duży wyświetlacz z aktywnością listy i aktywnością szczegółów obok siebie.
          Nawigacja wstecz nie może zamknąć aktywności szczegółów i pozostawić aktywności listy na ekranie.

Mały wyświetlacz z wyświetlaną tylko szczegółową aktywnością. Nie można wrócić do poprzedniego ekranu, aby zamknąć ekran szczegółów i wyświetlić ekran listy.

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

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

Zapoznaj się z sekcją Atrybuty konfiguracji.

Wiele działań w podzielonych kontenerach

Łączenie wielu aktywności w podzielonym kontenerze umożliwia użytkownikom dostęp do szczegółowych treści. Na przykład w przypadku podziału na listę i szczegóły użytkownik może przejść do sekcji ze szczegółami, ale zachować główną aktywność:

Rysunek 18. Aktywność została otwarta w miejscu w drugim panelu okna zadania.

Kotlin

class DetailActivity : AppCompatActivity() {
    fun onOpenSubdetail() {
        startActivity(Intent(this, SubdetailActivity::class.java))
    }
}

Java

public class DetailActivity  extends AppCompatActivity {
    void onOpenSubdetail() {
        startActivity(new Intent(this, SubdetailActivity.class));
    }
}

Aktywność podrzędna jest umieszczana nad aktywnością szczegółową, zasłaniając ją:

Użytkownik może następnie wrócić do poprzedniego poziomu szczegółowości, cofając się w stosie:

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

Domyślnie działania są układane jeden na drugim, gdy są uruchamiane z działania w tym samym kontenerze dodatkowym. Aktywności uruchamiane z głównego kontenera w aktywnym podziale trafiają też do kontenera dodatkowego na górze stosu aktywności.

Działania w nowym zadaniu

Gdy działania w oknie podzielonego zadania uruchamiają działania w nowym zadaniu, nowe zadanie jest oddzielone od zadania, które obejmuje podział, i wyświetla się w pełnym oknie. Na ekranie Ostatnie widać 2 zadania: zadanie w podzielonym widoku i nowe zadanie.

Rysunek 20. Uruchom aktywność C w nowym zadaniu z aktywności B.

Zastępowanie aktywności

Aktywności można zastępować w stosie kontenera dodatkowego, 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 rozpocząć nową aktywność w kontenerze dodatkowym i usunąć aktywność lub aktywności, które były w nim wcześniej.

Rysunek 21. Aktywność związana z nawigacją najwyższego poziomu w panelu głównym zastępuje aktywność związaną z miejscem docelowym w panelu dodatkowym.

Jeśli aplikacja nie zakończy działania w kontenerze dodatkowym po zmianie wyboru nawigacji, nawigacja wsteczna może być myląca, gdy podział jest zwinięty (gdy urządzenie jest złożone). Jeśli na przykład w panelu głównym masz menu, a w panelu dodatkowym ekrany A i B, to po złożeniu telefonu ekran B będzie nad ekranem A, a ekran A nad menu. Gdy użytkownik wróci z B, zamiast menu pojawi się A.

W takich przypadkach ekran A musi zostać usunięty z listy wstecznej.

Domyślne działanie podczas uruchamiania z boku w nowym kontenerze na istniejącym podziale polega na umieszczeniu nowych kontenerów pomocniczych na górze i zachowaniu starych w stosie wstecznym. Możesz skonfigurować podziały tak, aby wyczyścić poprzednie kontenery pomocnicze za pomocą 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

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

Java

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

Możesz też użyć tego samego działania dodatkowego i z działania głównego (menu) wysyłać nowe intencje, które będą rozwiązywane w tej samej instancji, ale będą wywoływać aktualizację stanu lub interfejsu w kontenerze dodatkowym.

Wiele podziałów

Aplikacje mogą zapewniać wielopoziomową nawigację, uruchamiając dodatkowe aktywności z boku.

Gdy działanie w kontenerze dodatkowym uruchamia nowe działanie z boku, nad istniejącym podziałem tworzony jest nowy podział.

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

Stos wsteczny zawiera wszystkie wcześniej otwarte aktywności, dzięki czemu użytkownicy mogą po zakończeniu aktywności C przejść do podziału A/B.

Działania A, B i C w stosie. 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 podziałów A/B i B/C i uruchom aktywność C w normalny sposób z aktywności B:

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

Kotlin

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

Java

public class B extends AppCompatActivity{
    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 otwierający okno z ustawieniami konta.

Rysunek 23. Różne aktywności z funkcjonalnie identycznymi elementami interfejsu.

Jeśli 2 aktywności, które mają wspólny element interfejsu, są podzielone, wyświetlanie tego elementu w obu aktywnościach jest zbędne i może być mylące.

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

Aby wiedzieć, kiedy działania są w podziale, sprawdź przepływ SplitController.splitInfoList lub zarejestruj detektor za pomocą SplitControllerCallbackAdapter, aby monitorować zmiany stanu podziału. Następnie dostosuj interfejs:

Kotlin

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    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) {
    super.onCreate(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 uruchamia się je w stanie STARTED, aby oszczędzać zasoby (więcej informacji znajdziesz w artykule Używanie korutyn Kotlin z komponentami uwzględniającymi cykl życia).

Wywołania zwrotne mogą być wykonywane w dowolnym stanie cyklu życia, w tym gdy aktywność jest zatrzymana. Słuchacze powinni być zwykle zarejestrowani w onStart() i niezarejestrowani w onStop().

Okno modalne na pełnym ekranie

Niektóre aktywności blokują możliwość interakcji z aplikacją, dopóki nie zostanie wykonane określone działanie, np. ekran logowania, ekran potwierdzenia zasad lub komunikat o błędzie. Nie należy wyświetlać działań modalnych w podziale.

Aby aktywność zawsze wypełniała okno zadania, możesz użyć konfiguracji expand:

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

Kończenie aktywności

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

Rysunek 25. Gest przesuwania kończący aktywność B.
Rysunek 26. Gest przesuwania kończący aktywność A.

Jeśli urządzenie jest skonfigurowane tak, aby używać przycisku Wstecz zamiast nawigacji gestami, dane wejściowe są wysyłane do aktywnego działania – działania, które zostało dotknięte lub uruchomione jako ostatnie.

Wpływ ukończenia wszystkich aktywności 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 działania po drugiej stronie podziału. Atrybuty to:

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

Możliwe wartości atrybutów to:

  • always – zawsze kończ działania w powiązanym kontenerze.
  • never – nigdy nie kończ aktywności w powiązanym kontenerze.
  • adjacent – dokończ działania w powiązanym kontenerze, gdy oba kontenery są wyświetlane obok siebie, ale nie wtedy, gdy są ułożone jeden na drugim.

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 podziału zostaną zakończone, pozostały kontener zajmuje całe okno:

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

Podział zawierający działania A i B. A zostanie zamknięte, a B zajmie całe okno.

Podział zawierający działania A i B. B kończy się, a A zajmuje całe okno.

Wspólne wykonywanie zadań

Automatycznie koń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 działania A i B. B jest zakończone, co powoduje również zakończenie A, pozostawiając puste okno zadania.

Podział zawierający działania A i B. A kończy się, pozostawiając B w oknie zadania.

Automatycznie koń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 działania A i B. Zadanie A zostało ukończone, co spowodowało też ukończenie zadania B. Okno zadań jest teraz puste.

Podział zawierający działania A i B. B kończy zadanie, a A zostaje sam w oknie zadania.

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

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

Podział zawierający działania A i B. Zadanie A zostało ukończone, co spowodowało też ukończenie zadania B. Okno zadań jest teraz puste.

Podział zawierający działania A i B. B jest zakończone, co powoduje również zakończenie A, pozostawiając puste okno zadania.

Kończenie wielu aktywności w kontenerach

Jeśli w podzielonym kontenerze jest kilka aktywności, zakończenie aktywności na dole stosu nie powoduje automatycznego zakończenia aktywności na górze.

Jeśli na przykład w kontenerze dodatkowym znajdują się 2 aktywności, C nad B:

Drugi stos działań, w którym działanie C jest ułożone na działaniu B, jest ułożony na pierwszym stosie działań, w którym znajduje się działanie A.

Konfiguracja podziału jest określona przez konfigurację działań A i B:

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

zakończenie aktywności u góry zachowuje podział.

Podział z aktywnością A w kontenerze głównym oraz aktywnościami B i C w kontenerze dodatkowym, przy czym C jest ułożona na B. C kończy aktywność, a A i B – nie.

Zakończenie działania u dołu (u podstaw) kontenera dodatkowego nie powoduje usunięcia działań znajdujących się nad nim, a tym samym zachowuje podział.

Podział z aktywnością A w kontenerze głównym oraz aktywnościami B i C w kontenerze dodatkowym, przy czym C jest ułożona na B. B kończy, a A i C pozostają w podziale aktywności.

Wykonywane są też wszelkie dodatkowe reguły dotyczące wspólnego kończenia aktywności, np. kończenia aktywności dodatkowej z aktywnością główną:

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

Podział z aktywnością A w kontenerze głównym oraz aktywnościami B i C w kontenerze dodatkowym, przy czym C jest ułożona na B. A kończy się, a także kończy B i C.

A gdy podział jest skonfigurowany tak, aby zakończyć podstawowe i dodatkowe razem:

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

Podział z aktywnością A w kontenerze głównym oraz aktywnościami B i C w kontenerze dodatkowym, przy czym C jest ułożona na B. C kończy aktywność, a A i B – nie.

Podział z aktywnością A w kontenerze głównym oraz aktywnościami B i C w kontenerze dodatkowym, przy czym C jest ułożona na B. B kończy, a A i C pozostają w podziale aktywności.

Podział z aktywnością A w kontenerze głównym oraz aktywnościami B i C w kontenerze dodatkowym, przy czym C jest ułożona na B. A kończy się, a wraz z nim B i C.

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

Nie można zmieniać właściwości aktywnego i widocznego podziału. Zmiana reguł podziału wpływa na dodatkowe uruchomienia 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 działanie lub działania poboczne w podziale i ponownie uruchom je z nową konfiguracją.

Dynamiczne właściwości podziału

Android 15 (poziom interfejsu API 35) i nowsze wersje obsługiwane przez Jetpack WindowManager 1.4 i nowsze oferują dynamiczne funkcje, które umożliwiają konfigurowanie podziałów osadzania aktywności, w tym:

  • Rozwijanie okienka: interaktywny, przesuwalny separator umożliwia użytkownikom zmianę rozmiaru okienek w prezentacji podzielonej na części.
  • Przypinanie stosu aktywności: użytkownicy mogą przypinać treści w jednym kontenerze i odseparowywać nawigację w tym kontenerze od nawigacji w innym kontenerze.
  • Przyciemnienie okna dialogowego na pełnym ekranie: podczas wyświetlania okna dialogowego aplikacje mogą określić, czy przyciemnić całe okno zadania, czy tylko kontener, w którym zostało otwarte okno dialogowe.

Rozwijanie panelu

Rozszerzanie panelu umożliwia użytkownikom dostosowanie ilości miejsca na ekranie przydzielonego do dwóch aktywności w układzie dwupanelowym.

Aby dostosować wygląd separatora okien i ustawić zakres, w którym można go przesuwać:

  1. Tworzenie instancji DividerAttributes

  2. Dostosuj atrybuty separatora:

    • color:kolor separatora panelu, który można przeciągać.

    • widthDp: szerokość separatora panelu, który można przeciągać. Ustaw wartość WIDTH_SYSTEM_DEFAULT, aby system określał szerokość separatora.

    • Zakres przeciągania: minimalny odsetek ekranu, jaki może zajmować każdy z paneli. Może wynosić od 0,33 do 0,66. Ustaw wartość DRAG_RANGE_SYSTEM_DEFAULT, aby system określał zakres przeciągania.

    Kotlin

    val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
        .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
        .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
    
    if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
        splitAttributesBuilder.setDividerAttributes(
            DividerAttributes.DraggableDividerAttributes.Builder()
                .setColor(getColor(R.color.divider_color))
                .setWidthDp(4)
                .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
                .build()
        )
    }
    val splitAttributes: SplitAttributes = splitAttributesBuilder.build()

    Java

    SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
        .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
        .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);
    
    if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
        splitAttributesBuilder.setDividerAttributes(
          new DividerAttributes.DraggableDividerAttributes.Builder()
            .setColor(ContextCompat.getColor(this, R.color.divider_color))
            .setWidthDp(4)
            .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
            .build()
        );
    }
    SplitAttributes _splitAttributes = splitAttributesBuilder.build();

Przypinanie stosu aktywności

Przypinanie stosu aktywności umożliwia użytkownikom przypinanie jednego z podzielonych okien, dzięki czemu aktywność pozostaje bez zmian, gdy użytkownicy poruszają się w drugim oknie. Przypinanie stosu aktywności zapewnia lepszą wielozadaniowość.

Aby włączyć przypinanie stosu aktywności w aplikacji:

  1. Dodaj przycisk do pliku układu aktywności, którą chcesz przypiąć, np. do aktywności szczegółowej układu lista-szczegóły:

    <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/detailActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/white"
     tools:context=".DetailActivity">
    
    <TextView
       android:id="@+id/textViewItemDetail"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="36sp"
       android:textColor="@color/obsidian"
       app:layout_constraintBottom_toTopOf="@id/pinButton"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
    
    <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/pinButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/pin_this_activity"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. W metodzie onCreate() aktywności ustaw odbiornik kliknięć na przycisku:

    Kotlin

    val pinButton: Button = findViewById(R.id.pinButton)
    pinButton.setOnClickListener {
        val splitAttributes: SplitAttributes = SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build()
    
        val pinSplitRule = SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build()
    
        SplitController.getInstance(applicationContext)
            .pinTopActivityStack(taskId, pinSplitRule)
    }

    Java

    Button pinButton = findViewById(R.id.pinButton);
    pinButton.setOnClickListener( (view) -> {
        SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
    
        SplitPinRule pinSplitRule = new SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build();
    
        SplitController.getInstance(getApplicationContext())
            .pinTopActivityStack(getTaskId(), pinSplitRule);
    });

Okno pełnoekranowe – przyciemnienie

Aktywności zwykle przyciemniają wyświetlacze, aby zwrócić uwagę na okno. W przypadku osadzania aktywności oba panele wyświetlacza dwupanelowego powinny być przyciemnione, a nie tylko panel zawierający aktywność, która otworzyła okno dialogowe. Zapewni to spójny interfejs.

W przypadku WindowManager w wersji 1.4 i nowszych całe okno aplikacji jest domyślnie przyciemniane po otwarciu okna dialogowego (patrz EmbeddingConfiguration.DimAreaBehavior.ON_TASK).

Aby przyciemnić tylko kontener aktywności, która otworzyła okno, użyj elementu EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.

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

Utwórz nową konfigurację, która wyświetla działanie boczne w pełnym oknie, a następnie ponownie uruchom działanie z intencją, która prowadzi do tego samego wystąpienia.

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

Osadzanie aktywności jest obsługiwane na Androidzie 12L (poziom interfejsu API 32) i nowszym, ale jest też dostępne na niektórych urządzeniach z wcześniejszymi wersjami platformy. Aby sprawdzić dostępność funkcji w czasie działania programu, 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ł ekranu nie jest obsługiwany, aktywności są uruchamiane na stosie aktywności (zgodnie z modelem osadzania aktywności).

Zapobieganie zastąpieniu przez system

Producenci urządzeń z Androidem (producenci oryginalnego sprzętu, czyli OEM) mogą zaimplementować osadzanie aktywności jako funkcję systemu urządzenia. System określa reguły podziału dla aplikacji z wieloma aktywnościami, zastępując zachowanie okien aplikacji. Zastąpienie przez system wymusza na aplikacjach z wieloma aktywnościami tryb osadzania aktywności zdefiniowany przez system.

Osadzanie aktywności systemowej może poprawić prezentację aplikacji dzięki układom wielopanelowym, takim jak lista-szczegóły, bez wprowadzania zmian w aplikacji. Osadzanie aktywności systemowej może jednak powodować nieprawidłowe układy aplikacji, błędy lub konflikty z osadzaniem aktywności zaimplementowanym przez aplikację.

Aplikacja może zapobiegać osadzaniu aktywności systemowej lub na to zezwalać, ustawiając wartość PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE w pliku manifestu aplikacji, np.:

<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 WindowProperties. Ustaw wartość false, jeśli aplikacja implementuje osadzanie aktywności lub jeśli chcesz w inny sposób uniemożliwić systemowi stosowanie w niej reguł osadzania aktywności. Ustaw wartość true, aby zezwolić systemowi na stosowanie w aplikacji zdefiniowanego przez niego osadzania aktywności.

Ograniczenia i ostrzeżenia

  • Tylko aplikacja hostująca zadanie, która jest identyfikowana jako właściciel głównej aktywności w zadaniu, może organizować i osadzać inne aktywności w zadaniu. Jeśli działania obsługujące osadzanie i podział są uruchamiane w zadaniu należącym do innej aplikacji, osadzanie i podział nie będą działać w przypadku tych działań.
  • Działania można organizować tylko w ramach jednego zadania. Uruchomienie aktywności w nowym zadaniu zawsze powoduje otwarcie jej w nowym, rozwiniętym oknie poza istniejącymi podziałami.
  • Dzielić można tylko działania w ramach tego samego procesu. Wywołanie zwrotne SplitInfo zgłasza tylko działania należące do tego samego procesu, ponieważ nie ma możliwości uzyskania informacji o działaniach w innych procesach.
  • Każda reguła dotycząca pary lub pojedynczej aktywności ma zastosowanie tylko do uruchomień aktywności, które nastąpiły po zarejestrowaniu reguły. Obecnie nie ma możliwości aktualizowania istniejących podziałów ani ich właściwości wizualnych.
  • Konfiguracja filtra pary podziału musi być zgodna z intencjami używanymi podczas pełnego uruchamiania aktywności. Dopasowanie następuje w momencie, gdy w procesie aplikacji rozpoczyna się nowa aktywność, więc może nie znać nazw komponentów, które są rozwiązywane później w procesie systemowym podczas korzystania z niejawnych intencji. Jeśli nazwa komponentu nie jest znana w momencie uruchomienia, można zamiast niej 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 podziałów i z nich po ich utworzeniu. Podziały są tworzone tylko przez bibliotekę WindowManager, gdy uruchamiane są nowe aktywności z pasującymi regułami, a usuwane, gdy kończy się ostatnia aktywność w kontenerze podziału.
  • Działania można ponownie uruchomić, gdy zmieni się konfiguracja, więc gdy podział zostanie utworzony lub usunięty, a granice działania ulegną zmianie, działanie może zostać całkowicie zniszczone i utworzone na nowo. W związku z tym deweloperzy aplikacji powinni zachować ostrożność w przypadku takich działań jak uruchamianie nowych aktywności z wywołań zwrotnych cyklu życia.
  • Urządzenia muszą zawierać interfejs rozszerzeń okien, aby obsługiwać osadzanie aktywności. Interfejs jest dostępny na prawie wszystkich urządzeniach z dużym ekranem z Androidem 12L (API na poziomie 32) lub nowszym. Niektóre urządzenia z dużym ekranem, które nie obsługują wielu aktywności, nie mają jednak interfejsu rozszerzeń okien. Jeśli urządzenie z dużym ekranem nie obsługuje trybu wielu okien, może nie obsługiwać osadzania aktywności.

Dodatkowe materiały