Einbetten von Aktivitäten

Die Aktivitätseinbettung optimiert Apps auf Geräten mit großen Bildschirmen, indem das Aufgabenfenster einer Anwendung auf zwei Aktivitäten oder zwei Instanzen derselben Aktivität aufgeteilt wird.

Abbildung 1. App „Einstellungen“ mit Aktivitäten nebeneinander.

Wenn deine App aus mehreren Aktivitäten besteht, kannst du mithilfe der Einbettung von Aktivitäten die Nutzerfreundlichkeit auf Tablets, faltbaren Geräten und ChromeOS-Geräten verbessern.

Das Einbetten von Aktivitäten erfordert keine Code-Refaktorierung. Durch Erstellen einer XML-Konfigurationsdatei oder mithilfe von Jetpack WindowManager-API-Aufrufen legen Sie fest, wie die Aktivitäten in Ihrer App nebeneinander oder gestapelt angezeigt werden.

Die Unterstützung kleiner Bildschirme wird automatisch gepflegt. Wenn sich deine App auf einem Gerät mit kleinem Bildschirm befindet, werden Aktivitäten übereinander gestapelt. Auf großen Bildschirmen werden Aktivitäten nebeneinander angezeigt. Das System bestimmt die Präsentation anhand der von Ihnen erstellten Konfiguration. Es ist keine Verzweigungslogik erforderlich.

Das Einbetten von Aktivitäten berücksichtigt Änderungen der Geräteausrichtung und funktioniert nahtlos auf faltbaren Geräten. Aktivitäten werden gestapelt oder entstapelt, wenn das Gerät auf- und zugeklappt wird.

Das Einbetten von Aktivitäten wird auf den meisten Geräten mit großen Bildschirmen mit Android 12L (API-Level 32) und höher unterstützt.

Aufgabenfenster teilen

Die Aktivitätseinbettung teilt das Aufgabenfenster der Anwendung in zwei Container auf: primäre und sekundäre Container. Die Container enthalten Aktivitäten, die aus der Hauptaktivität oder anderen bereits in den Containern enthaltenen Aktivitäten gestartet wurden.

Aktivitäten werden beim Start im sekundären Container gestapelt und der sekundäre Container auf kleinen Bildschirmen über dem primären Container gestapelt. Das Stacking von Aktivitäten und die Rückwärtsnavigation entsprechen also der Reihenfolge der Aktivitäten, die bereits in Ihre App integriert sind.

Die Einbettung von Aktivitäten ermöglicht es Ihnen, Aktivitäten auf verschiedene Weise darzustellen. Ihre Anwendung kann das Aufgabenfenster aufteilen, indem sie zwei Aktivitäten gleichzeitig nebeneinander startet:

Abbildung 2. Zwei Aktivitäten nebeneinander.

Oder eine Aktivität, die das gesamte Aufgabenfenster einnimmt, kann eine Aufteilung erstellen, indem eine neue Aktivität neben folgenden Elementen gestartet wird:

Abbildung 3: Aktivität A startet Aktivität B seitlich.

Aktivitäten, die sich bereits in einer Aufteilung befinden und ein Aufgabenfenster teilen, können andere Aktivitäten auf folgende Arten starten:

  • Neben einer anderen Aktivität:

    Abbildung 4: Aktivität A beginnt Aktivität C neben Aktivität B.
  • Zur Seite und verlegen Sie die Teilung seitwärts, um die vorherige primäre Aktivität zu verbergen:

    Abbildung 5: Aktivität B startet Aktivität C zur Seite und verschiebt den Teil zur Seite.
  • Starten Sie eine Aktivität oben, d. h. innerhalb desselben Aktivitäten-Stacks:

    Abbildung 6. Aktivität B startet Aktivität C ohne zusätzliche Intent-Flags.
  • Starten Sie ein vollständiges Fenster mit einer Aktivität in derselben Aufgabe:

    Abbildung 7. Aktivität A oder Aktivität B startet Aktivität C, die das Aufgabenfenster ausfüllt.

Rückwärtsnavigation

Verschiedene Arten von Anwendungen können unterschiedliche Regeln für die Rückwärtsnavigation in einem Aufgabenfensterstatus haben, abhängig von den Abhängigkeiten zwischen Aktivitäten oder davon, wie Nutzer das Zurück-Ereignis auslösen. Beispiel:

  • Zusammenführung: Wenn Aktivitäten zusammenhängen und eine nicht ohne die andere angezeigt werden soll, kann die Rückwärtsnavigation so konfiguriert werden, dass beides abgeschlossen wird.
  • Allein: Wenn Aktivitäten völlig unabhängig sind, wirkt sich die Rückwärtsnavigation bei einer Aktivität nicht auf den Status einer anderen Aktivität im Aufgabenfenster aus.

Bei Verwendung der Schaltflächennavigation wird das Ereignis „Zurück“ an die letzte fokussierte Aktivität gesendet. Bei einer bewegungsbasierten Navigation wird das Zurück-Ereignis an die Aktivität gesendet, in der die Geste aufgetreten ist.

Mehrfensterlayout

Mit Jetpack WindowManager können Sie ein Mehrfensterlayout für die Einbettung von Aktivitäten auf Geräten mit großen Bildschirmen mit Android 12L (API-Level 32) oder höher und auf einigen Geräten mit älteren Plattformversionen erstellen. Vorhandene Apps, die auf mehreren Aktivitäten und nicht auf Fragmenten basieren, oder ansichtsbasierte Layouts wie SlidingPaneLayout können für eine bessere Nutzererfahrung auf großen Bildschirmen sorgen, ohne dass der Quellcode refaktoriert werden muss.

Ein gängiges Beispiel ist die Aufteilung von Listen und Details. Um eine qualitativ hochwertige Präsentation zu gewährleisten, startet das System die Listenaktivität und die Anwendung startet sofort die Detailaktivität. Das Übergangssystem wartet, bis beide Aktivitäten gezeichnet sind, und zeigt sie dann zusammen an. Für den Nutzer werden die beiden Aktivitäten als eine gestartet.

Abbildung 8. Zwei Aktivitäten, die gleichzeitig in einem Mehrfensterlayout gestartet wurden.

Aufteilungsattribute

Sie können angeben, wie das Aufgabenfenster zwischen den aufgeteilten Containern und das Layout der Container im Verhältnis zueinander angeordnet werden soll.

Legen Sie für Regeln, die in einer XML-Konfigurationsdatei definiert sind, die folgenden Attribute fest:

  • splitRatio: Legt die Containerproportionen fest. Der Wert ist eine Gleitkommazahl im offenen Intervall (0,0 bis 1,0).
  • splitLayoutDirection: Gibt an, wie die aufgeteilten Container relativ zueinander angeordnet sind. Mögliche Werte:
    • ltr: von links nach rechts
    • rtl: Linksläufig
    • locale: ltr oder rtl wird durch die Spracheinstellung bestimmt

Beispiele finden Sie unten im Abschnitt XML-Konfiguration.

Erstellen Sie für Regeln, die mit den WindowManager APIs erstellt wurden, ein SplitAttributes-Objekt mit SplitAttributes.Builder und rufen Sie die folgenden Builder-Methoden auf:

Beispiele finden Sie unten unter WindowManager API.

Abbildung 9. Zwei Aktivitätsaufteilungen wurden von links nach rechts angeordnet, aber mit unterschiedlichen Aufteilungsverhältnissen.

Platzhalter

Platzhalteraktivitäten sind leere sekundäre Aktivitäten, die einen Bereich einer Aktivitätsaufteilung belegen. Sie sollen letztendlich durch eine andere Aktivität mit Inhalten ersetzt werden. Beispielsweise könnte eine Platzhalteraktivität die sekundäre Seite einer Aktivitätsaufteilung in einem Listen-Detail-Layout einnehmen, bis ein Element aus der Liste ausgewählt wird. Dann wird der Platzhalter durch eine Aktivität mit den Detailinformationen zum ausgewählten Listenelement ersetzt.

Standardmäßig zeigt das System Platzhalter nur dann an, wenn genügend Platz für eine Aktivitätsaufteilung vorhanden ist. Platzhalter werden automatisch fertiggestellt, wenn sich die Anzeigegröße auf eine Breite oder Höhe ändert, die für eine Aufteilung zu klein ist. Wenn der Platz ausreicht, startet das System den Platzhalter mit einem neu initialisierten Status neu.

Abbildung 10. Faltbares Gerät auf- und zuklappen. Die Platzhalteraktivität wird beendet und bei Änderungen der Anzeigegröße neu erstellt.

Das Attribut stickyPlaceholder einer SplitPlaceholderRule- oder setSticky()-Methode von SplitPlaceholder.Builder kann jedoch das Standardverhalten überschreiben. Wenn das Attribut oder die Methode den Wert true angibt, zeigt das System den Platzhalter als oberste Aktivität im Aufgabenfenster an, wenn die Anzeige von einer Zweifensteransicht zur Einzelansicht verkleinert wird (ein Beispiel finden Sie unter Unterteilte Konfiguration).

Abbildung 11. Faltbares Gerät auf- und zuklappen. Die Platzhalteraktivität ist fixiert.

Änderungen der Fenstergröße

Wenn durch Änderungen an der Gerätekonfiguration die Breite des Aufgabenfensters so reduziert wird, dass sie für ein Mehrfensterlayout nicht groß genug ist (z. B. wenn ein faltbares Gerät mit einem großen Bildschirm von der Tablet- auf die Smartphone-Größe geklappt wird oder die Größe des App-Fensters im Mehrfenstermodus angepasst wird), werden die Nicht-Platzhalteraktivitäten im sekundären Bereich des Aufgabenfensters über den Aktivitäten im primären Bereich gestapelt.

Platzhalteraktivitäten werden nur angezeigt, wenn die Anzeigebreite für eine Aufteilung ausreicht. Auf kleineren Bildschirmen wird der Platzhalter automatisch geschlossen. Wenn der Anzeigebereich wieder groß genug wird, wird der Platzhalter neu erstellt. Weitere Informationen finden Sie oben unter Platzhalter.

Das Stapeln von Aktivitäten ist möglich, weil WindowManager die Aktivitäten im sekundären Bereich über den Aktivitäten im primären Bereich anordnet.

Mehrere Aktivitäten im sekundären Bereich

Aktivität B startet Aktivität C ohne zusätzliche Intent-Flags:

Aktivitätsaufteilung mit Aktivitäten A, B und C, wobei C über B gestapelt ist.

Dies führt zur folgenden Z-Reihenfolge der Aktivitäten in derselben Aufgabe:

Sekundärer Aktivitätsstapel mit Aktivität C über Seite B.
          Der Sekundär-Stack wird über den einfachen Aktivitäts-Stack gestapelt, der Aktivität A enthält.

In einem kleineren Aufgabenfenster wird die Anwendung also auf eine einzelne Aktivität mit C oben im Stapel verkleinert:

Kleines Fenster, in dem nur Aktivität C angezeigt wird

Wenn Sie im kleineren Fenster zurücknavigieren, können Sie durch die übereinander gestapelten Aktivitäten wechseln.

Wenn die Konfiguration des Aufgabenfensters in einer größeren Größe wiederhergestellt wird, die mehrere Bereiche aufnehmen kann, werden die Aktivitäten wieder nebeneinander angezeigt.

Gestapelte Aufteilungen

Aktivität B startet Aktivität C zur Seite und verschiebt den Teil zur Seite:

Aufgabenfenster mit den Aktivitäten A und B und dann den Aktivitäten B und C.

Das Ergebnis ist die folgende Z-Reihenfolge der Aktivitäten in derselben Aufgabe:

Die Aktivitäten A, B und C in einem Stapel. Die Aktivitäten sind in der folgenden Reihenfolge von oben nach unten gestapelt: C, B, A.

In einem kleineren Aufgabenfenster wird die Anwendung auf eine einzelne Aktivität mit C oben verkleinert:

Kleines Fenster, in dem nur Aktivität C angezeigt wird

Festes Hochformat

Mit der Manifesteinstellung android:screenOrientation können Apps Aktivitäten auf das Hoch- oder Querformat beschränken. Um die Nutzerfreundlichkeit auf Geräten mit großen Bildschirmen wie Tablets und faltbaren Geräten zu verbessern, können Gerätehersteller (OEMs) Anfragen zur Bildschirmausrichtung ignorieren und die App im Hochformat im Querformat oder im Querformat auf Displays im Hochformat anzeigen lassen.

Abbildung 12. Letterbox-Aktivitäten: fixiertes Hochformat auf Gerät im Querformat (links), festes Querformat auf Gerät im Hochformat (rechts).

Wenn die Einbettung von Aktivitäten aktiviert ist, können OEMs Geräte so anpassen, dass Aktivitäten im Letterbox-Format im Querformat auf großen Bildschirmen (Breite ≥ 600 dp) angezeigt werden. Wenn eine feste Hochformataktivität eine zweite Aktivität startet, kann das Gerät die beiden Aktivitäten nebeneinander in einem Zweifenster anzeigen.

Abbildung 13. Aktivität A mit festem Hochformat startet Aktivität B seitlich.

Füge deiner App-Manifestdatei immer das Attribut android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED hinzu, um Geräte darüber zu informieren, dass deine App die Einbettung von Aktivitäten unterstützt (siehe Split-Konfiguration unten). OEM-benutzerdefinierte Geräte können dann bestimmen, ob Aktivitäten im Letterbox-Format (Fixed Portrait) verwendet werden sollen.

Konfiguration der Aufteilung

Mit Aufteilungsregeln werden Aktivitätsaufteilungen konfiguriert. Sie definieren Aufteilungsregeln in einer XML-Konfigurationsdatei oder durch Jetpack-WindowManager-API-Aufrufe.

In beiden Fällen muss Ihre App auf die WindowManager-Bibliothek zugreifen und das System darüber informieren, dass die App die Aktivitätseinbettung implementiert hat.

Unternimm Folgendes:

  1. Fügen Sie der Datei build.gradle Ihrer App auf Modulebene die neueste WindowManager-Bibliotheksabhängigkeit hinzu. Beispiel:

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

    Die WindowManager-Bibliothek bietet alle Komponenten, die für das Einbetten von Aktivitäten erforderlich sind.

  2. Teilen Sie dem System mit, dass in Ihrer App die Aktivitätseinbettung implementiert ist.

    Fügen Sie dem <application>-Element der Manifestdatei der App das Attribut android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED hinzu und setzen Sie den Wert auf „true“. Beispiel:

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

    Ab WindowManager-Version 1.1.0-alpha06 sind Aufteilungen mit Aktivitätseinbettungen deaktiviert, es sei denn, die Eigenschaft wird dem Manifest hinzugefügt und auf „true“ gesetzt.

    Außerdem können Gerätehersteller benutzerdefinierte Funktionen für Apps aktivieren, die das Einbetten von Aktivitäten unterstützen. Auf Geräten im Querformat kann z. B. eine Aktivität im Hochformat nur mit dem Letterbox-Bild dargestellt werden, um die Aktivität für den Übergang zu einem Layout mit zwei Bereichen anzupassen, wenn eine zweite Aktivität beginnt (siehe Feste Hochformatausrichtung).

XML-Konfiguration

Führen Sie die folgenden Schritte aus, um eine XML-basierte Implementierung der Aktivitätseinbettung zu erstellen:

  1. Erstellen Sie eine XML-Ressourcendatei, die folgende Aufgaben erfüllt:

    • Definiert Aktivitäten, die eine Aufteilung teilen
    • Konfiguriert die Aufteilungsoptionen
    • Erstellt einen Platzhalter für den sekundären Container der Aufteilung, wenn kein Inhalt verfügbar ist
    • Gibt Aktivitäten an, die niemals Teil einer Aufteilung sein sollen

    Beispiel:

    <!-- 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. Erstellen Sie einen Initialisierer.

    Die WindowManager-Komponente RuleController parst die XML-Konfigurationsdatei und stellt die Regeln dem System zur Verfügung. Eine Jetpack-Startbibliothek Initializer stellt RuleController beim Start der App die XML-Datei zur Verfügung, damit die Regeln wirksam werden, wenn Aktivitäten beginnen.

    So erstellen Sie einen Initialisierer:

    1. Fügen Sie der Datei build.gradle auf Modulebene die neueste Abhängigkeit der Jetpack Startup-Bibliothek hinzu. Beispiel:

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

    2. Erstellen Sie eine Klasse, die die Initializer-Schnittstelle implementiert.

      Der Initialisierer macht die Aufteilungsregeln für RuleController verfügbar, indem er die ID der XML-Konfigurationsdatei (main_split_config.xml) an die Methode RuleController.parseRules() übergibt.

      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. Erstellen Sie einen Contentanbieter für die Regeldefinitionen.

    Füge deiner App-Manifestdatei androidx.startup.InitializationProvider als <provider> hinzu. Füge eine Referenz zur Implementierung deines RuleController-Initialisierers SplitInitializer hinzu:

    <!-- 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 erkennt und initialisiert SplitInitializer, bevor die onCreate()-Methode der App aufgerufen wird. Die Aufteilungsregeln gelten daher, wenn die Hauptaktivität der App beginnt.

WindowManager-API

Mit ein paar API-Aufrufen können Sie die Einbettung von Aktivitäten programmatisch implementieren. Führen Sie die Aufrufe in der Methode onCreate() einer Unterklasse von Application aus, um sicherzustellen, dass die Regeln vor dem Start von Aktivitäten wirksam sind.

So erstellen Sie programmatisch eine Aktivitätsaufteilung:

  1. Erstellen Sie eine Aufteilungsregel:

    1. Erstellen Sie einen SplitPairFilter mit den Aktivitäten, die die Aufteilung teilen:

      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. So fügen Sie den Filter einer Filtergruppe hinzu:

      Kotlin

      val filterSet = setOf(splitPairFilter)
      

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
      
    3. Erstellen Sie Layoutattribute für die Aufteilung:

      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 erstellt ein Objekt mit Layoutattributen:

      • setSplitType: Definiert, wie der verfügbare Anzeigebereich den einzelnen Aktivitätscontainern zugeordnet wird. Der Verhältnisaufteilungstyp gibt den Anteil des verfügbaren Anzeigebereichs an, der dem primären Container zugewiesen ist. Der sekundäre Container belegt den Rest des verfügbaren Anzeigebereichs.
      • setLayoutDirection: Gibt an, wie die Aktivitätscontainer im Verhältnis zueinander angeordnet werden (Primärcontainer zuerst).
    4. Erstellen Sie ein 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 erstellt und konfiguriert die Regel:

      • filterSet: Enthält Filter für aufgeteilte Paare, die festlegen, wann die Regel angewendet werden soll. Dazu werden Aktivitäten ermittelt, die eine Aufteilung verwenden.
      • setDefaultSplitAttributes: Wendet Layoutattribute auf die Regel an.
      • setMinWidthDp: Hiermit wird die minimale Anzeigebreite (in dichteunabhängigen Pixeln, dp) festgelegt, die eine Aufteilung ermöglicht.
      • setMinSmallestWidthDp: Legt den Mindestwert (in dp) fest, den der kleinere der beiden Displayabmessungen haben muss, um unabhängig von der Geräteausrichtung eine Aufteilung zu ermöglichen.
      • setMaxAspectRatioInPortrait: Hiermit wird das maximale Seitenverhältnis (Höhe:Breite) im Hochformat festgelegt, für das Aktivitätsaufteilungen angezeigt werden. Wenn das Seitenverhältnis eines Displays im Hochformat das maximale Seitenverhältnis überschreitet, wird die Teilung unabhängig von der Breite des Bildschirms deaktiviert. Hinweis:Der Standardwert ist 1,4.Das bedeutet, dass Aktivitäten auf den meisten Tablets das gesamte Aufgabenfenster im Hochformat belegen. Weitere Informationen finden Sie unter SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT und setMaxAspectRatioInLandscape. Der Standardwert für das Querformat ist ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary: Hiermit wird festgelegt, wie sich das Abschließen aller Aktivitäten im sekundären Container auf die Aktivitäten im primären Container auswirkt. NEVER gibt an, dass das System die primären Aktivitäten nicht beenden soll, wenn alle Aktivitäten im sekundären Container abgeschlossen wurden (siehe Aktivitäten beenden).
      • setFinishSecondaryWithPrimary: Hiermit wird festgelegt, wie sich das Abschließen aller Aktivitäten im primären Container auf die Aktivitäten im sekundären Container auswirkt. ALWAYS gibt an, dass das System die Aktivitäten immer im sekundären Container abschließen soll, wenn alle Aktivitäten im primären Container abgeschlossen wurden (siehe Aktivitäten beenden).
      • setClearTop: Gibt an, ob alle Aktivitäten im sekundären Container beendet sind, wenn eine neue Aktivität im Container gestartet wird. Mit „False“ wird angegeben, dass neue Aktivitäten über die Aktivitäten gestapelt werden, die sich bereits im sekundären Container befinden.
    5. Rufen Sie die Singleton-Instanz von WindowManager RuleController ab und fügen Sie die Regel hinzu:

      Kotlin

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

      Java

      RuleController ruleController = RuleController.getInstance(this);
      ruleController.addRule(splitPairRule);
      
  2. Erstellen Sie einen Platzhalter für den sekundären Container, wenn keine Inhalte verfügbar sind:

    1. Erstellen Sie einen ActivityFilter, der die Aktivität identifiziert, mit der der Platzhalter eine Aufteilung des Aufgabenfensters teilt:

      Kotlin

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

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
      
    2. So fügen Sie den Filter einer Filtergruppe hinzu:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
      

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
      
    3. Erstellen Sie ein 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 erstellt und konfiguriert die Regel:

      • placeholderActivityFilterSet: Enthält Aktivitätsfilter, die festlegen, wann die Regel angewendet werden soll. Dazu werden Aktivitäten ermittelt, mit denen die Platzhalteraktivität verknüpft ist.
      • Intent: Gibt den Start der Platzhalteraktivität an.
      • setDefaultSplitAttributes: Wendet Layoutattribute auf die Regel an.
      • setMinWidthDp: Hiermit wird die minimale Anzeigebreite (in dichteunabhängigen Pixeln, dp) festgelegt, die eine Aufteilung ermöglicht.
      • setMinSmallestWidthDp: Legt den Mindestwert (in dp) fest, den der kleinere der beiden Anzeigeabmessungen haben muss, um unabhängig von der Geräteausrichtung eine Aufteilung zu ermöglichen.
      • setMaxAspectRatioInPortrait: Hiermit wird das maximale Seitenverhältnis (Höhe:Breite) im Hochformat festgelegt, für das Aktivitätsaufteilungen angezeigt werden. Hinweis:Der Standardwert ist 1,4.Das führt auf den meisten Tablets dazu, dass das Aufgabenfenster im Hochformat ausgefüllt wird. Weitere Informationen finden Sie unter SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT und setMaxAspectRatioInLandscape. Der Standardwert für das Querformat ist ALWAYS_ALLOW.
      • setFinishPrimaryWithPlaceholder: Hiermit wird festgelegt, wie sich das Abschließen der Platzhalteraktivität auf die Aktivitäten im primären Container auswirkt. ALWAYS gibt an, dass das System die Aktivitäten immer im primären Container beenden soll, wenn der Platzhalter beendet ist (siehe Aktivitäten beenden).
      • setSticky: Damit wird festgelegt, ob die Platzhalteraktivität auf kleinen Displays über dem Aktivitätsstapel erscheint, sobald der Platzhalter zuerst in einem Split mit einer ausreichenden Mindestbreite angezeigt wurde.
    4. Füge die Regel zum WindowManager RuleController hinzu:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)
      

      Java

      ruleController.addRule(splitPlaceholderRule);
      
  3. Geben Sie Aktivitäten an, die niemals Teil einer Aufteilung sein sollen:

    1. Erstellen Sie eine ActivityFilter, die eine Aktivität kennzeichnet, die immer den gesamten Aufgabenbereich einnehmen sollte:

      Kotlin

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

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
      
    2. So fügen Sie den Filter einer Filtergruppe hinzu:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)
      

      Java

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

      Kotlin

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

      Java

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

      ActivityRule.Builder erstellt und konfiguriert die Regel:

      • expandedActivityFilterSet: Enthält Aktivitätsfilter, die festlegen, wann die Regel angewendet werden soll. Dazu identifizieren Sie Aktivitäten, die Sie aus Aufteilungen ausschließen möchten.
      • setAlwaysExpand: Gibt an, ob die Aktivität das gesamte Aufgabenfenster ausfüllen soll.
    4. Füge die Regel zum WindowManager RuleController hinzu:

      Kotlin

      ruleController.addRule(activityRule)
      

      Java

      ruleController.addRule(activityRule);
      

Anwendungsübergreifende Einbettung

Unter Android 13 (API-Level 33) und höher können Apps Aktivitäten aus anderen Apps einbetten. Die anwendungsübergreifende (UID-) Einbettung von Aktivitäten ermöglicht die visuelle Integration von Aktivitäten aus verschiedenen Android-Apps. Das System zeigt eine Aktivität der Host-App und eine eingebettete Aktivität aus einer anderen App auf dem Bildschirm nebeneinander oder oben und unten an, genau wie beim Einbetten von Einzel-App-Aktivitäten.

In die Einstellungen-App könnte beispielsweise die Hintergrundauswahlaktivität aus der HintergründePicker-App eingebettet werden:

Abbildung 14. App „Einstellungen“ (Menü auf der linken Seite) mit Hintergrundauswahl als eingebettete Aktivität (rechts).

Vertrauensmodell

Hostprozesse, die Aktivitäten aus anderen Apps einbetten, können die Darstellung der eingebetteten Aktivitäten neu definieren, einschließlich Größe, Position, Zuschneiden und Transparenz. Schädliche Hosts können diese Funktion nutzen, um Nutzer in die Irre zu führen und Clickjacking oder andere UI-Redressing-Angriffe zu verursachen.

Damit der Missbrauch eingebetteter Aktivitäten von App-übergreifenden Aktivitäten verhindert wird, müssen Apps die Einbettung ihrer Aktivitäten erlauben. Apps können Hosts als vertrauenswürdig oder nicht vertrauenswürdig kennzeichnen.

Vertrauenswürdige Hosts

Damit andere Apps die Darstellung von Aktivitäten aus Ihrer App einbetten und uneingeschränkt steuern können, geben Sie das SHA-256-Zertifikat der Host-App im Attribut android:knownActivityEmbeddingCerts der Elemente <activity> oder <application> der Manifestdatei Ihrer App an.

Legen Sie den Wert von android:knownActivityEmbeddingCerts entweder als String fest:

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

oder ein String-Array, um mehrere Zertifikate anzugeben:

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

der auf eine Ressource wie die folgende verweist:

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

App-Inhaber können einen SHA-Zertifikat-Digest abrufen, indem sie die Gradle-Task signingReport ausführen. Der Zertifikat-Digest ist der SHA-256-Fingerabdruck ohne die Doppelpunkte. Weitere Informationen finden Sie unter Signaturbericht ausführen und Client authentifizieren.

Nicht vertrauenswürdige Hosts

Wenn Sie einer App erlauben möchten, die Aktivitäten Ihrer App einzubetten und ihre Darstellung zu steuern, geben Sie im App-Manifest das Attribut android:allowUntrustedActivityEmbedding in den Elementen <activity> oder <application> an. Beispiel:

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

Der Standardwert des Attributs ist „false“, wodurch das Einbetten von App-Aktivitäten verhindert wird.

Benutzerdefinierte Authentifizierung

Erstellen Sie einen benutzerdefinierten Authentifizierungsmechanismus, der die Hostidentität überprüft, um das Risiko der Einbettung nicht vertrauenswürdiger Aktivitäten zu verringern. Wenn Sie die Hostzertifikate kennen, verwenden Sie zur Authentifizierung die androidx.security.app.authenticator-Bibliothek. Wenn sich der Host nach dem Einbetten deiner Aktivität authentifiziert, kannst du den tatsächlichen Inhalt anzeigen. Falls nicht, kannst du den Nutzer darüber informieren, dass die Aktion nicht erlaubt wurde, und den Inhalt blockieren.

Verwenden Sie die Methode ActivityEmbeddingController#isActivityEmbedded() aus der Jetpack WindowManager-Bibliothek, um zu prüfen, ob ein Host Ihre Aktivität einbettet. Beispiel:

Kotlin

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

Java

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

Mindestgröße

Das Android-System wendet die im App-Manifestelement <layout> angegebene Mindesthöhe und -breite auf eingebettete Aktivitäten an. Wenn eine App keine Mindesthöhe und -breite vorgibt, werden die Systemstandardwerte angewendet (sw220dp).

Wenn der Host versucht, die Größe des eingebetteten Containers auf eine kleinere Größe als das Minimum zu ändern, wird der eingebettete Container so erweitert, dass er die gesamten Aufgabengrenzen einnimmt.

<Aktivitätsalias>

Damit das Einbetten vertrauenswürdiger oder nicht vertrauenswürdiger Aktivitäten mit dem Element <activity-alias> funktioniert, muss android:knownActivityEmbeddingCerts oder android:allowUntrustedActivityEmbedding auf die Zielaktivität und nicht auf den Alias angewendet werden. Die Richtlinie zur Überprüfung der Sicherheit auf dem Systemserver basiert auf den Flags, die im Ziel festgelegt sind, und nicht auf dem Alias.

Hostanwendung

Host-Apps implementieren die Einbettung App-übergreifender Aktivitäten auf die gleiche Weise wie die Einbettung von Einzel-App-Aktivitäten. SplitPairRule- und SplitPairFilter- oder ActivityRule- und ActivityFilter-Objekte geben eingebettete Aktivitäten und Aufteilungen von Aufgabenfenstern an. Aufteilungsregeln werden statisch in XML oder zur Laufzeit mithilfe von Jetpack WindowManager API-Aufrufen definiert.

Wenn eine Host-App versucht, eine Aktivität einzubetten, die App-übergreifende Einbettung nicht aktiviert hat, belegt die Aktivität die gesamten Aufgabengrenzen. Daher müssen Host-Anwendungen wissen, ob Zielaktivitäten eine appübergreifende Einbettung zulassen.

Wenn eine eingebettete Aktivität eine neue Aktivität in derselben Aufgabe startet und für die neue Aktivität die App-übergreifende Einbettung nicht aktiviert wurde, nimmt die Aktivität die gesamten Aufgabengrenzen ein und überlagert nicht die Aktivität im eingebetteten Container.

Eine Host-Anwendung kann ihre eigenen Aktivitäten ohne Einschränkung einbetten, solange die Aktivitäten in derselben Aufgabe gestartet werden.

Beispiele für Aufteilungen

Vom gesamten Fenster teilen

Abbildung 15. Aktivität A startet Aktivität B seitlich.

Keine Refaktorierung erforderlich. Sie können die Konfiguration für die Aufteilung statisch oder zur Laufzeit definieren und dann Context#startActivity() ohne zusätzliche Parameter aufrufen.

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

Standardmäßig aufteilen

Wenn die Landingpage einer App so konzipiert ist, dass sie auf großen Bildschirmen in zwei Container aufgeteilt wird, ist die Nutzererfahrung am besten, wenn beide Aktivitäten gleichzeitig erstellt und präsentiert werden. Inhalte sind jedoch möglicherweise erst dann für den sekundären Container des Splits verfügbar, wenn der Nutzer mit der Aktivität im primären Container interagiert (z. B. wenn er ein Element aus einem Navigationsmenü auswählt). Platzhalteraktivitäten können die Lücke füllen, bis Inhalte im sekundären Container des Splits angezeigt werden können (siehe Platzhalter oben).

Abbildung 16. Die Aufteilung wurde durch gleichzeitiges Öffnen von zwei Aktivitäten erstellt. Eine Aktivität ist ein Platzhalter.

Um eine Aufteilung mit einem Platzhalter zu erstellen, erstellen Sie einen Platzhalter und verknüpfen ihn mit der primären Aktivität:

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

Wenn eine App einen Intent empfängt, kann die Zielaktivität als sekundärer Teil einer Aktivitätsaufteilung angezeigt werden, z. B. eine Anfrage zum Anzeigen eines Detailbildschirms mit Informationen zu einem Element aus einer Liste. Auf kleinen Displays werden die Details im vollständigen Aufgabenfenster angezeigt, auf größeren Geräten neben der Liste.

Abbildung 17. Deeplink-Detailaktivität wird allein auf einem kleinen Bildschirm, aber zusammen mit einer Listenaktivität auf einem großen Bildschirm angezeigt.

Die Startanfrage sollte an die Hauptaktivität weitergeleitet und die Zieldetailaktivität in einer Aufteilung gestartet werden. Das System wählt automatisch die richtige Präsentation – gestapelt oder nebeneinander – basierend auf der verfügbaren Anzeigebreite aus.

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

Das Deeplink-Ziel ist möglicherweise die einzige Aktivität, die dem Nutzer im Zurück-Navigationsstack verfügbar sein sollte. Sie sollten außerdem vermeiden, die Detailaktivität zu schließen und nur die Hauptaktivität zu lassen:

Großes Display mit Listen- und Detailaktivitäten nebeneinander.
          Bei der Rückwärtsnavigation können die Details der Aktivität nicht geschlossen und die Listenaktivität nicht auf dem Bildschirm angezeigt werden.

Kleines Display nur mit Detailaktivität. Bei der Rückwärtsnavigation kann die Detailaktivität nicht geschlossen und die Listenaktivität angezeigt werden.

Stattdessen können Sie mit dem Attribut finishPrimaryWithSecondary beide Aktivitäten gleichzeitig abschließen:

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

Siehe Konfigurationsattribute unten.

Mehrere Aktivitäten in geteilten Containern

Wenn mehrere Aktivitäten in einem geteilten Container gestapelt werden, können Nutzer auf tiefgehende Inhalte zugreifen. Bei einer Aufteilung von Listen und Details muss der Nutzer beispielsweise zu einem Abschnitt mit Unterdetails wechseln, aber die primäre Aktivität beibehalten:

Abbildung 18. Die Aktivität wird im sekundären Bereich des Aufgabenfensters geöffnet.

Kotlin

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

Java

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

Die Unterdetailaktivität wird über der Detailaktivität platziert und blendet sie aus:

Der Nutzer kann dann zur vorherigen Detailebene zurückkehren, indem er durch den Stack zurücknavigiert:

Abbildung 19. Aktivität wurde vom Anfang des Stapels entfernt.

Aktivitäten werden standardmäßig übereinander gestapelt, wenn Aktivitäten aus einer Aktivität im selben sekundären Container gestartet werden. Aktivitäten, die aus dem primären Container innerhalb eines aktiven Splits gestartet werden, landen auch im sekundären Container oben im Aktivitätspaket.

Aktivitäten in einer neuen Aufgabe

Wenn Aktivitäten in einem aufgeteilten Aufgabenfenster Aktivitäten in einer neuen Aufgabe starten, ist die neue Aufgabe von der Aufgabe getrennt, die die Aufteilung enthält, und wird im Vollbildmodus angezeigt. Der Bildschirm „Zuletzt verwendet“ zeigt zwei Aufgaben: die Aufgabe in der geteilten Aufgabe und die neue Aufgabe.

Abbildung 20. Beginnen Sie Aktivität C in einer neuen Aufgabe aus Aktivität B.

Aktivitäten ersetzen

Aktivitäten können im sekundären Containerstack ersetzt werden, z. B. wenn die primäre Aktivität für die Navigation auf oberster Ebene verwendet wird und die sekundäre Aktivität ein ausgewähltes Ziel ist. Mit jeder Auswahl aus der Navigation der obersten Ebene sollte eine neue Aktivität im sekundären Container gestartet und die zuvor dort vorhandenen Aktivitäten entfernt werden.

Abbildung 21. Die Navigationsaktivität der obersten Ebene im primären Bereich ersetzt die Zielaktivitäten im sekundären Bereich.

Wenn die App die Aktivität nicht im sekundären Container beendet, wenn sich die Navigationsauswahl ändert, kann die Rückwärtsnavigation verwirrend sein, wenn die Aufteilung minimiert wird (bei zusammengeklapptem Gerät). Beispiel: Sie haben ein Menü im primären Bereich und die Bildschirme A und B im sekundären Bereich gestapelt. Wenn der Nutzer das Smartphone aufklappt, befindet sich B über A und A über dem Menü. Wenn der Nutzer von B zurückkehrt, erscheint anstelle des Menüs A.

Bildschirm A muss in solchen Fällen aus dem Back Stack entfernt werden.

Beim Starten auf der Seite eines neuen Containers gegenüber einem vorhandenen Split werden standardmäßig die neuen sekundären Container auf den ersten Platz gelegt und die alten im Back-Stack beibehalten. Sie können die Splits so konfigurieren, dass die vorherigen sekundären Container mit clearTop gelöscht und neue Aktivitäten normal gestartet werden.

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

Alternativ können Sie dieselbe sekundäre Aktivität verwenden und aus der primären (Menü-)Aktivität neue Intents senden, die zur selben Instanz aufgelöst werden, aber ein Status- oder UI-Update im sekundären Container auslösen.

Mehrere Aufteilungen

Apps können eine mehrstufige Tiefennavigation bieten, indem sie zusätzliche Aktivitäten an der Seite starten.

Wenn eine Aktivität in einem sekundären Container eine neue Aktivität an der Seite startet, wird über dem vorhandenen Split ein neuer Split erstellt.

Abbildung 22. Aktivität B startet Aktivität C nebenan.

Der Back-Stack enthält alle Aktivitäten, die zuvor geöffnet wurden. Nutzer können also zum A/B-Split wechseln, nachdem sie C beendet haben.

Die Aktivitäten A, B und C in einem Stapel. Die Aktivitäten sind in der folgenden Reihenfolge von oben nach unten gestapelt: C, B, A.

Wenn Sie eine neue Aufteilung erstellen möchten, starten Sie die neue Aktivität neben dem vorhandenen sekundären Container. Deklarieren Sie die Konfigurationen für die A/B- und B/C-Splits und starten Sie Aktivität C normalerweise von 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));
    }
}

Auf Statusänderungen reagieren

Verschiedene Aktivitäten in einer App können UI-Elemente haben, die dieselbe Funktion erfüllen, z. B. ein Steuerelement, das ein Fenster mit Kontoeinstellungen öffnet.

Abbildung 23. Verschiedene Aktivitäten mit funktional identischen UI-Elementen

Wenn sich zwei Aktivitäten, die ein gemeinsames UI-Element haben, in einem Split befinden, ist es redundant und möglicherweise verwirrend, wenn das Element in beiden Aktivitäten angezeigt wird.

Abbildung 24. Doppelte UI-Elemente in der Aktivitätsaufteilung.

Wenn Sie wissen möchten, wann sich Aktivitäten in einem Split befinden, prüfen Sie den SplitController.splitInfoList-Ablauf oder registrieren Sie einen Listener mit SplitControllerCallbackAdapter auf Änderungen am Aufteilungsstatus. Passen Sie dann die Benutzeroberfläche entsprechend an:

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

Koroutinen können in jedem Lebenszyklusstatus gestartet werden, werden aber in der Regel mit dem Status STARTED gestartet, um Ressourcen zu sparen. Weitere Informationen finden Sie unter Kotlin-Koroutinen mit lebenszyklusbezogenen Komponenten verwenden.

Callbacks können in jedem Lebenszyklusstatus erfolgen, auch beim Beenden einer Aktivität. Listener sollten normalerweise in onStart() registriert und nicht in onStop() registriert sein.

Modales Vollbildfenster

Einige Aktivitäten blockieren Nutzer an der Interaktion mit der Anwendung, bis eine bestimmte Aktion ausgeführt wird. Dies kann beispielsweise eine Aktivität auf dem Anmeldebildschirm, ein Bildschirm zur Richtlinienbestätigung oder eine Fehlermeldung sein. Modale Aktivitäten sollten verhindert werden, dass sie in einem Split auftreten.

Eine Aktivität kann mithilfe der erweiterten Konfiguration erzwungen werden, dass das Aufgabenfenster immer gefüllt wird:

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

Aktivitäten beenden

Nutzer können Aktivitäten auf beiden Seiten des Splits beenden, indem sie vom Rand des Displays nach oben wischen:

Abbildung 25. Wischgeste zum Beenden von Aktivität B.
Abbildung 26. Wischgeste zum Beenden von Aktivität A.

Wenn das Gerät so eingerichtet ist, dass die Schaltfläche „Zurück“ anstelle der Bedienung über Gesten verwendet wird, wird die Eingabe an die fokussierte Aktivität gesendet, also an die Aktivität, die zuletzt berührt oder gestartet wurde.

Wie sich das Beenden aller Aktivitäten in einem Container auf den gegenüberliegenden Container auswirkt, hängt von der Aufteilungskonfiguration ab.

Konfigurationsattribute

Sie können Regelattribute für Aufteilungspaare angeben, um zu konfigurieren, wie sich das Beenden aller Aktivitäten auf einer Seite der Aufteilung auf die Aktivitäten auf der anderen Seite der Aufteilung auswirkt. Die Attribute sind:

  • window:finishPrimaryWithSecondary: Wie wirkt sich das Beenden aller Aktivitäten im sekundären Container auf die Aktivitäten im primären Container aus?
  • window:finishSecondaryWithPrimary: Wie wirkt sich das Beenden aller Aktivitäten im primären Container auf die Aktivitäten im sekundären Container aus?

Mögliche Werte der Attribute:

  • always: Die Aktivitäten werden immer im zugehörigen Container beendet.
  • never: Die Aktivitäten im zugehörigen Container werden nie beendet.
  • adjacent: Die Aktivitäten im zugehörigen Container werden beendet, wenn die beiden Container nebeneinander angezeigt werden, aber nicht, wenn die beiden Container gestapelt sind.

Beispiel:

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

Standardkonfiguration

Wenn alle Aktivitäten in einem Container eines Splits abgeschlossen sind, belegt der verbleibende Container das gesamte Fenster:

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

Aufteilung mit den Aktivitäten A und B. A ist fertig, sodass B das gesamte Fenster einnimmt.

Aufteilung mit den Aktivitäten A und B. B ist fertig, sodass A das gesamte Fenster einnimmt.

Aktivitäten gemeinsam beenden

Die Aktivitäten im primären Container werden automatisch beendet, wenn alle Aktivitäten im sekundären Container abgeschlossen sind:

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

Aufteilung mit den Aktivitäten A und B. B beendet ist, wodurch auch A beendet wird, sodass das Aufgabenfenster leer bleibt.

Aufteilung mit den Aktivitäten A und B. A ist abgeschlossen und B bleibt im Aufgabenfenster unverändert.

Schließen Sie die Aktivitäten im sekundären Container automatisch ab, wenn alle Aktivitäten im primären Container abgeschlossen sind:

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

Aufteilung mit den Aktivitäten A und B. A wird beendet, was auch B beendet, wobei das Aufgabenfenster leer bleibt.

Aufteilung mit den Aktivitäten A und B. B abgeschlossen ist, sodass A im Aufgabenfenster bleibt.

Schließen Sie die Aktivitäten zusammen ab, wenn alle Aktivitäten im primären oder sekundären Container abgeschlossen sind:

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

Aufteilung mit den Aktivitäten A und B. A wird beendet, was auch B beendet, wobei das Aufgabenfenster leer bleibt.

Aufteilung mit den Aktivitäten A und B. B beendet ist, wodurch auch A beendet wird, sodass das Aufgabenfenster leer bleibt.

Mehrere Aktivitäten in Containern abschließen

Wenn mehrere Aktivitäten in einem geteilten Container gestapelt sind, werden durch das Beenden einer Aktivität am unteren Rand des Stapels die Aktivitäten nicht automatisch oben beendet.

Beispiel: Wenn sich zwei Aktivitäten im sekundären Container befinden, steht C über B:

Der sekundäre Aktivitätsstack, der Aktivität C über B gestapelt hat, wird über den vorherigen Aktivitätsstapel mit Aktivität A gestapelt.

Die Konfiguration der Aufteilung wird durch die Konfiguration der Aktivitäten A und B definiert:

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

Beim Abschließen der obersten Aktivität wird die Aufteilung beibehalten.

Mit Aktivität A im primären Container und Aktivitäten B und C im sekundären Container, C über B gestapelt. C beendet, wobei A und B in der Aktivitätsaufteilung verbleiben.

Wenn die untere Aktivität (Stammaktivität) des sekundären Containers beendet wird, werden die darüberliegenden Aktivitäten nicht entfernt. Daher wird auch die Aufteilung beibehalten.

Mit Aktivität A im primären Container und Aktivitäten B und C im sekundären Container, C über B gestapelt. B beendet, wobei A und C in der Aktivitätsaufteilung verbleiben.

Alle zusätzlichen Regeln für das gemeinsame Abschließen von Aktivitäten, z. B. das Abschließen der sekundären Aktivität mit der primären Aktivität, werden ebenfalls ausgeführt:

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

Teilt mit Aktivität A im primären Container und Aktivitäten B und C im sekundären Container, C übereinander gestapelt auf B. Ende A, B und C ebenfalls.

Und wenn die Aufteilung so konfiguriert ist, dass die primäre und die sekundäre Instanz zusammen abgeschlossen werden:

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

Mit Aktivität A im primären Container und Aktivitäten B und C im sekundären Container, C über B gestapelt. C beendet, wobei A und B in der Aktivitätsaufteilung verbleiben.

Mit Aktivität A im primären Container und Aktivitäten B und C im sekundären Container, C über B gestapelt. B beendet, wobei A und C in der Aktivitätsaufteilung verbleiben.

Mit Aktivität A im primären Container und Aktivitäten B und C im sekundären Container, C über B gestapelt. Ende A, Ende B und C.

Aufteilungsattribute während der Laufzeit ändern

Die Eigenschaften einer derzeit aktiven und sichtbaren Aufteilung können nicht geändert werden. Das Ändern der Aufteilungsregeln wirkt sich auf zusätzliche Aktivitätsstarts und neue Container aus, jedoch nicht auf bestehende und aktive Splits.

Wenn Sie die Eigenschaften aktiver Aufteilungen ändern möchten, schließen Sie die Nebenaktivität oder -aktivitäten in dem Split ab und starten Sie die Seite mit einer neuen Konfiguration noch einmal.

Aktivität aus einem Split-to-Full-Fenster extrahieren

Erstellen Sie eine neue Konfiguration, die das vollständige Fenster der Nebenaktivität anzeigt, und starten Sie die Aktivität dann mit einem Intent neu, der in dieselbe Instanz aufgelöst wird.

Unterstützung von Split während der Laufzeit prüfen

Das Einbetten von Aktivitäten wird ab Android 12L (API-Level 32) unterstützt, ist aber auch auf einigen Geräten mit früheren Plattformversionen verfügbar. Mit dem Attribut SplitController.splitSupportStatus oder der Methode SplitController.getSplitSupportStatus() können Sie während der Laufzeit prüfen, ob die Funktion verfügbar ist:

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

Wenn Aufteilungen nicht unterstützt werden, werden Aktivitäten zusätzlich zum Aktivitäten-Stack gestartet (nach dem Einbettungsmodell ohne Aktivität).

Systemüberschreibung verhindern

Die Hersteller von Android-Geräten (Original Equipment Manufacturers oder OEMs) können die Aktivitätseinbettung als Funktion des Gerätesystems implementieren. Das System legt Aufteilungsregeln für Anwendungen mit mehreren Aktivitäten fest und überschreibt das Windowing-Verhalten der Anwendungen. Die Systemüberschreibung erzwingt für Apps mit mehreren Aktivitäten einen systemdefinierten Modus zum Einbetten von Aktivitäten.

Durch das Einbetten von Systemaktivitäten kann die App-Präsentation durch Mehrfensterlayouts wie list-detail verbessert werden, ohne dass Änderungen an der App erforderlich sind. Die Einbettung von Systemaktivitäten kann jedoch auch zu falschen App-Layouts, Fehlern oder Konflikten mit der von der App implementierten Aktivitätseinbettung führen.

In Ihrer Anwendung kann das Einbetten von Systemaktivitäten verhindert oder zugelassen werden. Dazu legen Sie in der Manifestdatei der Anwendung ein Attribut fest. Beispiel:

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

Der Name der Eigenschaft wird im Jetpack WindowManager-Objekt WindowProperties definiert. Legen Sie den Wert auf false fest, wenn in Ihrer App Aktivitätseinbettungen implementiert sind oder wenn Sie verhindern möchten, dass das System seine Aktivitätseinbettungsregeln auf Ihre App anwendet. Setzen Sie den Wert auf true, damit das System systemdefinierte Aktivitätseinbettungen auf Ihre App anwenden kann.

Einschränkungen und Vorbehalte

  • Nur die Host-App der Aufgabe, die als Inhaber der Stammaktivität in der Aufgabe identifiziert wurde, kann andere Aktivitäten organisieren und in die Aufgabe einbetten. Wenn Aktivitäten, die Einbettung und Aufteilung unterstützen, in einer Aufgabe ausgeführt werden, die zu einer anderen Anwendung gehört, funktionieren Einbettungen und Aufteilungen für diese Aktivitäten nicht.
  • Aktivitäten können nur innerhalb einer Aufgabe organisiert werden. Wenn Sie eine Aktivität in einer neuen Aufgabe starten, wird sie immer in einem neuen maximierten Fenster außerhalb vorhandener Aufteilungen platziert.
  • Nur Aktivitäten im selben Prozess können organisiert und aufgeteilt werden. Der SplitInfo-Callback meldet nur Aktivitäten, die zum selben Prozess gehören, da keine Informationen zu Aktivitäten in verschiedenen Prozessen vorliegen.
  • Jede Paar- oder einzelne Aktivitätsregel gilt nur für Aktivitätsstarts, die nach der Registrierung der Regel erfolgen. Es gibt derzeit keine Möglichkeit, vorhandene Splits oder ihre visuellen Eigenschaften zu aktualisieren.
  • Die Filterkonfiguration für aufgeteilte Paare muss mit den Intents übereinstimmen, die beim vollständigen Start von Aktivitäten verwendet werden. Der Abgleich findet an dem Punkt statt, an dem im Anwendungsprozess eine neue Aktivität gestartet wird. Daher sind bei Verwendung von impliziten Intents möglicherweise Komponentennamen nicht bekannt, die später im Systemprozess aufgelöst werden. Wenn der Name einer Komponente zum Zeitpunkt der Einführung nicht bekannt ist, kann stattdessen ein Platzhalter („*/*“) verwendet werden. Außerdem kann nach Intent-Aktion gefiltert werden.
  • Es gibt derzeit keine Möglichkeit, Aktivitäten zwischen Containern oder in und aus Splits zu verschieben, nachdem sie erstellt wurden. Splits werden von der WindowManager-Bibliothek nur erstellt, wenn neue Aktivitäten mit übereinstimmenden Regeln gestartet werden. Splits werden gelöscht, wenn die letzte Aktivität in einem aufgeteilten Container beendet ist.
  • Aktivitäten können neu gestartet werden, wenn sich die Konfiguration ändert. Wenn also ein Split erstellt oder entfernt wird und sich die Aktivitätsgrenzen ändern, kann die Aktivität vollständig gelöscht und eine neue Instanz erstellt werden. Daher sollten App-Entwickler vorsichtig sein, wenn es darum geht, neue Aktivitäten aus Lebenszyklus-Callbacks zu starten.
  • Geräte müssen die Oberfläche für Fenstererweiterungen enthalten, damit Aktivitäten eingebettet werden können. Fast alle Geräte mit großem Bildschirm mit Android 12L (API-Level 32) oder höher haben die Benutzeroberfläche. Auf einigen Geräten mit großen Bildschirmen, die nicht mehrere Aktivitäten ausführen können, wird die Oberfläche für Fenstererweiterungen jedoch nicht angezeigt. Wenn ein Gerät mit großem Bildschirm den Mehrfenstermodus nicht unterstützt, wird das Einbetten von Aktivitäten möglicherweise nicht unterstützt.

Zusätzliche Ressourcen