Przechwytywanie aktywności optymalizuje działanie aplikacji na urządzeniach z dużym ekranem, dzieląc okno zadania aplikacji między 2 aktywności lub 2 wystąpienia tej samej aktywności.

Jeśli Twoja aplikacja składa się z kilku aktywności, jej osadzenie umożliwia lepsze wrażenia użytkownika na tabletach, urządzeniach składanych i urządzeniach z ChromeOS.
Wstawianie aktywności nie wymaga refaktoryzacji kodu. Sposób wyświetlania aktywności przez aplikację (obok siebie lub w kolorze) możesz określić, tworząc plik konfiguracji XML lub wykonując wywołania interfejsu Jetpack WindowManager.
Obsługa małych ekranów jest automatyczna. Gdy aplikacja jest 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 prezentację na podstawie utworzonej przez Ciebie konfiguracji – nie jest wymagana żadna logika rozgałęzienia.
Wstawianie aktywności dostosowuje się do zmian orientacji urządzenia i działa płynnie na składanych urządzeniach, układając i rozkładają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.
Okno dzielenia zadań
Umieszczenie aktywności dzieli okno zadania aplikacji na 2 kontenery: podstawowy i dodatkowy. Kontenery zawierają działania uruchomione z głównego działania lub z innych działań znajdujących się już w kontenerach.
Aktywności są umieszczane w kontenerze dodatkowym w miarę ich uruchamiania, a na małych ekranach kontener dodatkowy jest umieszczany na wierzchu kontenera głównego. Dzięki temu grupowanie aktywności i powrót do poprzedniego ekranu są zgodne z kolejnością aktywności już wbudowanych w aplikację.
Umieszczenie w ramach 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 jedna nad drugą:


Aktywność, która zajmuje całe okno zadania, może utworzyć podział, uruchamiając nową aktywność równolegle:

Aktywności, które są już w ramach podziału i dzielą okno zadania, mogą uruchamiać inne aktywności na te sposoby:
Po bokach innej aktywności:
Rysunek 4. Aktywność A uruchamia aktywność C po prawej stronie aktywności B. Na bok, aby przesunąć podział na bok, ukrywając poprzednią główną aktywność:
Rysunek 5. Aktywność B rozpoczyna aktywność C z boku i przesuwa podział w bok. Uruchomić aktywność na pierwszym miejscu, czyli na tym samym stosie aktywności:
Rysunek 6. Aktywność B uruchamia aktywność C bez dodatkowych flag intencji. Uruchom okno pełne aktywności w tym samym zadaniu:
Rysunek 7. Aktywność A lub aktywność B uruchamia aktywność C, która wypełnia okno zadania.
Nawigacja wstecz
Różne typy aplikacji mogą mieć różne reguły nawigacji wstecz w stanie okna podzielonego zadania w zależności od zależności między aktywnościami lub sposobu wywołania przez użytkowników zdarzenia wstecz. Oto kilka przykładów:
- W powiązaniu: jeśli aktywności są powiązane i jedna nie powinna być wyświetlana bez drugiej, możesz skonfigurować przekierowanie wstecz, aby zakończyć obie.
- Samodzielne działanie: jeśli czynności są całkowicie niezależne, cofnięcie się z jednej czynności nie wpływa na stan innej czynności w oknie zadania.
Gdy korzystasz z nawigacji przyciskami, zdarzenie wstecz jest wysyłane do ostatniej aktywnej czynności.
W przypadku nawigacji przy użyciu gestów:
Android 14 (poziom interfejsu API 34) i starsze – zdarzenie wstecz jest wysyłane do aktywności, w której wystąpił gest. Gdy użytkownik przesunie palcem po lewej stronie ekranu, zdarzenie wstecz zostanie wysłane do aktywności w lewym panelu podzielonego okna. Gdy użytkownicy przesuwają palcem po prawej stronie ekranu, zdarzenie Wstecz jest wysyłane do aktywności w panelu po prawej stronie.
Android 15 (poziom 35 interfejsu API) lub nowszy
Gdy masz do czynienia z kilkoma aktywnościami z tej samej aplikacji, gest kończy aktywność na górze niezależnie od kierunku przesunięcia palcem, co zapewnia bardziej spójne działanie.
W scenariuszach obejmujących 2 aktywności z różnych aplikacji (nakładka) zdarzenie wstecz jest kierowane do ostatniej aktywnej aktywności, zgodnie z zachowaniem nawigacji przy użyciu przycisku.
Układ wielopanelowy
Jetpack WindowManager umożliwia tworzenie aktywności z przewijanym układem na urządzeniach z dużym ekranem z Androidem 12L (poziom interfejsu API 32) lub nowszym oraz na niektórych urządzeniach z wersjami platformy starszymi niż ta. Istniejące aplikacje, które są oparte na wielu działaniach, a nie na fragmentach lub układach opartych na widoku, takich jak SlidingPaneLayout
, mogą zapewnić lepsze wrażenia użytkownika na dużym ekranie bez refaktoryzacji kodu źródłowego.
Typowym przykładem 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ść czeka, aż oba zajęcia zostaną narysowane, a potem wyświetla je razem. Z punktu widzenia użytkownika te 2 czynności są wykonywane jako 1.

Atrybuty podziału
Możesz określić, jak proporcjonalnie rozdzielić okno zadania między podzielonymi kontenerami oraz jak ułożyć kontenery względem siebie.
W przypadku reguł zdefiniowanych w pliku konfiguracji XML ustaw te atrybuty:
splitRatio
: określa proporcje kontenera. Wartość jest liczbą zmiennoprzecinkową z otwartego przedziału (0,0, 1,0).splitLayoutDirection
: określa, jak rozbite kontenery są rozmieszczone względem siebie. Dostępne wartości:ltr
: od lewej do prawejrtl
: od prawej do lewejlocale
: wartośćltr
lubrtl
jest określana na podstawie ustawień lokalizacji
Przykłady znajdziesz w sekcji Konfiguracja XML.
W przypadku reguł utworzonych za pomocą interfejsów WindowManager API utwórz obiekt SplitAttributes
za pomocą obiektu SplitAttributes.Builder
i wywołaj te metody konstruktora:
setSplitType()
: określa proporcje kontenerów podzielonych. Przykłady prawidłowych argumentów, w tym metodySplitAttributes.SplitType.ratio()
, znajdziesz w dokumentacjiSplitAttributes.SplitType
.setLayoutDirection()
: określa układ kontenerów. Możliwe wartości znajdziesz w sekcjiSplitAttributes.LayoutDirection
.
Przykłady znajdziesz w sekcji WindowManager API.

Orientacja podzielona
Wymiary i format ekranu określają pozycjonowanie aktywności w ramach podziału na wdrożenia aktywności. Na dużych ekranach w orientacji poziomej aktywności są wyświetlane obok siebie, a na wysokich ekranach w orientacji pionowej lub na składanych urządzeniach w pozycji poziomej – jeden nad drugim.
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 w zależności od różnych stanów urządzenia, na przykład:
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ć jedną aktywność, jeśli urządzenie jest w orientacji pionowej, oraz podzielić ekran poziomo, jeśli urządzenie jest w pozycji poziomej:
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 należy je zastąpić inną aktywnością zawierającą treści. Na przykład w ramach układu listy z szczegółami aktywność zastępcza może zajmować część drugorzędną aktywności podzielonej na listę i szczegóły do momentu wybrania elementu z listy. Wtedy zastępcza ją aktywność zawierająca szczegółowe informacje o wybranym elemencie listy.
Domyślnie system wyświetla zastępniki tylko wtedy, gdy jest wystarczająco dużo miejsca na podział aktywności. Zasoby zastępcze automatycznie się kończą, gdy rozmiar wyświetlacza zmienia się na taki, którego szerokość lub wysokość jest zbyt mała, aby wyświetlić podział. Jeśli jest na to miejsce, system ponownie uruchamia element zastępczy z ponownym zainicjalizowanym stanem.

Jednak atrybut stickyPlaceholder
metody SplitPlaceholderRule
lub setSticky()
interfejsu SplitPlaceholder.Builder
może zastąpić domyślne działanie. Jeśli atrybut lub metoda określa wartość true
, system wyświetla placeholder jako główną aktywność w oknie zadania, gdy rozmiar wyświetlacza zostanie zmniejszony z 2 do 1 półki (patrz Konfiguracja podziału).

Zmiany rozmiaru okna
Gdy zmiany konfiguracji urządzenia zmniejszają szerokość okna zadania, tak że nie mieści się ono w ramach układu z kilkoma panelami (np. gdy składany duży ekran urządzenia zmienia rozmiar z rozmiaru tabletu na rozmiar telefonu lub gdy zmienia się rozmiar okna aplikacji w trybie wielookiennym), czynności, które nie są elementami zastępczymi, w panelu pomocniczym okna zadania są ułożone na czynnościach w panelu głównym.
Aktywności zastępcze są wyświetlane tylko wtedy, gdy jest wystarczająca szerokość wyświetlania na podział. Na mniejszych ekranach komórka zastępcza jest automatycznie usuwana. Gdy obszar wyświetlania znów stanie się wystarczająco duży, placeholder zostanie utworzony ponownie. (patrz sekcja Zmienniki).
Nakładanie się aktywności jest możliwe, ponieważ WindowManager sortuje aktywności w panelu pomocniczym nad aktywnościami w panelu głównym.
Wiele działań w panelu dodatkowym
Aktywność B uruchamia aktywność C bez dodatkowych flag intencji:
w efekcie w ramach tego samego zadania występuje następująca kolejność działań:
W mniejszym oknie zadania aplikacja zwija się do pojedynczej aktywności z C na szczycie stosu:
W mniejszym oknie możesz się przełączać między aktywnościami ułożonymi w wielu warstwach.
Jeśli konfiguracja okna zadania zostanie przywrócona do większego rozmiaru, który może pomieścić kilka paneli, aktywności będą ponownie wyświetlane obok siebie.
Warstwowe podziały
Aktywność B rozpoczyna się z aktywności C z bocznej strony i przesuwa podział:
W efekcie w ramach tego samego zadania czynności są wykonywane w takiej kolejności:
W mniejszym oknie zadania aplikacja zwija się do pojedynczej czynności z literą C u góry:
Orientacja pionowa
Ustawienie pliku manifestu android:screenOrientation umożliwia aplikacjom ograniczenie aktywności do orientacji poziomej lub pionowej. Aby zwiększyć wygodę użytkowników na urządzeniach z dużym ekranem, takich jak tablety i urządzenia składane, producenci urządzeń (OEM) mogą ignorować prośby dotyczące orientacji ekranu i ustawić aplikację w orientacji pionowej na ekranach poziomych lub w orientacji poziomej na ekranach pionowych.

Podobnie, gdy włączone jest umieszczanie w ramce, producenci urządzeń mogą dostosować urządzenia do wyświetlania aktywności w ramce letterbox w orientacji pionowej na dużych ekranach (szerokość ≥ 600 dp). Gdy działanie w układzie pionowym uruchamia drugie działanie, urządzenie może wyświetlać oba działania obok siebie na ekranie podzielonym na 2 części.

Zawsze dodawaj właściwość android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
do pliku manifestu aplikacji, aby informować urządzenia o tym, że aplikacja obsługuje umieszczanie aktywności (patrz sekcja Konfiguracja podziału). Urządzenia spersonalizowane przez producenta mogą następnie określić, czy mają wyświetlać aktywność w formacie letterbox.
Konfiguracja podziału
Reguły podziału konfigurują podział aktywności. Reguły podziału definiujesz w pliku konfiguracyjnym XML lub za pomocą wywołań interfejsu Jetpack WindowManager API.
W obu przypadkach aplikacja musi mieć dostęp do biblioteki WindowManager i musi poinformować system, że zaimplementowała wbudowaną aktywność.
Wykonaj te czynności:
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 udostępnia wszystkie komponenty wymagane do umieszczania aktywności.
poinformować system, że Twoja aplikacja ma funkcję wklejania aktywności;
Dodaj właściwość
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
do elementu <application> w pliku manifestu aplikacji i ustaw jego 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 wersji WindowManager 1.1.0-alpha06 i nowszych funkcje podziału wstawiania aktywności są wyłączone, chyba że doda się do pliku manifestu odpowiednią właściwość i ustawi się ją na „true”.
Producenci urządzeń używają tego ustawienia, aby włączyć funkcje niestandardowe w aplikacjach obsługujących umieszczanie aktywności. Na przykład urządzenia mogą wyświetlać w układzie poziomym aktywność w układzie pionowym, aby po rozpoczęciu drugiej aktywności przejść do układu z 2 panelami (patrz Układ pionowy).
Konfiguracja XML
Aby utworzyć implementację umieszczania aktywności na podstawie pliku XML, wykonaj te czynności:
Utwórz plik zasobu XML, który:
- Określa działania, które mają wspólny podział
- Konfigurowanie opcji podziału
- Tworzy obiekt zastępczy dla dodatkowego kontenera w przypadku braku treści.
- Określa działania, które nigdy nie powinny być uwzględniane w podziale
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>
Utwórz funkcję inicjalizacyjną.
Komponent WindowManager
RuleController
analizuje plik konfiguracji XML i udostępnia reguły systemowi. Biblioteka Jetpacka StartupInitializer
udostępnia plik XML aplikacjiRuleController
podczas uruchamiania aplikacji, dzięki czemu reguły są aktywne, gdy rozpoczyna się jakakolwiek aktywność.Aby utworzyć funkcję inicjalizacyjną:
Dodaj najnowszą zależność biblioteki Jetpack Startup do pliku na poziomie modułu
build.gradle
, na przykład:implementation 'androidx.startup:startup-runtime:1.1.1'
Utwórz klasę, która implementuje interfejs
Initializer
.Inicjalizator udostępnia reguły podziału programowi
RuleController
, przekazując identyfikator pliku konfiguracji XML (main_split_config.xml
) metodzieRuleController.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(); } }
Utwórz dostawcę treści dla definicji reguł.
Dodaj
androidx.startup.InitializationProvider
do pliku manifestu aplikacji jako<provider>
. Dołącz odwołanie do implementacji inicjalizatoraRuleController
:SplitInitializer
<!-- AndroidManifest.xml --> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <!-- Make SplitInitializer discoverable by InitializationProvider. --> <meta-data android:name="${applicationId}.SplitInitializer" android:value="androidx.startup" /> </provider>
InitializationProvider
wykrywa i inicjujeSplitInitializer
przed wywołaniem metodyonCreate()
aplikacji. W rezultacie reguły podziału są aktywne, gdy rozpoczyna się główna aktywność aplikacji.
Interfejs WindowManager API
Umieszczenie aktywności można zaimplementować programowo za pomocą kilku wywołań interfejsu API. Wykonuj wywołania w metodzie onCreate()
podklasy Application
, aby upewnić się, że reguły są aktywne przed uruchomieniem aktywności.
Application
Aby utworzyć podział aktywności za pomocą kodu:
Utwórz regułę podziału:
Utwórz
SplitPairFilter
, który identyfikuje aktywności, które mają ten sam 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 );
Dodaj filtr do zestawu filtrów:
Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
Utwórz atrybuty układu dla podziału:
Kotlin
val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build()
Java
final SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
SplitAttributes.Builder
tworzy obiekt zawierający atrybuty układu:setSplitType()
: określa sposób przydzielania dostępnej powierzchni wyświetlania poszczególnym kontenerom aktywności. Typ podziału na podstawie proporcji określa proporcję dostępnej powierzchni wyświetlania przypisanej do głównego kontenera. Pozostała część dostępnej powierzchni wyświetlania jest przypisana do kontenera dodatkowego.setLayoutDirection()
: określa sposób rozmieszczenia kontenerów aktywności względem siebie (najpierw kontener główny).
Kompilacja
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 pary podziałów, które określają, kiedy stosować regułę, przez identyfikowanie działań, które mają wspólny podział.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()
: określa 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()
: określa maksymalny współczynnik proporcji wyświetlania (wysokość:szerokość) w orientacji pionowej, w której wyświetlane są podziały aktywności. Jeśli współczynnik proporcji w układem pionowym przekracza maksymalny współczynnik proporcji, podziały są wyłączone niezależnie od szerokości wyświetlacza. Uwaga: domyślna wartość to 1,4, co powoduje, że na większości tabletów aktywności zajmują całe okno zadania w orientacji pionowej. Zobacz teżSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
isetMaxAspectRatioInLandscape()
. Wartością domyślną dla orientacji poziomej jestALWAYS_ALLOW
.setFinishPrimaryWithSecondary()
: określa sposób, w jaki zakończenie wszystkich aktywności w kontenerze pomocniczym wpływa na aktywności w kontenerze głównym.NEVER
oznacza, że system nie powinien kończyć działań głównych, gdy wszystkie działania w kontenerze pomocniczym zostaną zakończone (patrz Zakończ działania).setFinishSecondaryWithPrimary()
: określa, jak zakończenie wszystkich aktywności w kontenerze głównym wpływa na aktywności w kontenerze dodatkowym.ALWAYS
wskazuje, że system powinien zawsze kończyć czynności w kontenerze pomocniczym, gdy wszystkie czynności w kontenerze głównym zostaną zakończone (patrz Kończenie czynności).setClearTop()
: określa, czy wszystkie aktywności w kontenerze pomocniczym są zakończone, gdy w kontenerze uruchomiona jest nowa aktywność. Wartośćfalse
określa, że nowe aktywności są dodawane do tych, które są już w kontenerze drugorzędnym.
Pobierz pojedynczą instancję klasy WindowManager
RuleController
i dodaj regułę:Kotlin
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
Java
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
Gdy treści nie ma, utwórz zastępczy element dla kontenera dodatkowego:
Utwórz
ActivityFilter
, który identyfikuje aktywność, z którą placeholder dzieli okno zadania:Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
Dodaj filtr do zestawu filtrów:
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
Utwórz
SplitPlaceholderRule
:Kotlin
val splitPlaceholderRule = SplitPlaceholderRule.Builder( placeholderActivityFilterSet, Intent(context, PlaceholderActivity::class.java) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build()
Java
SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder( placeholderActivityFilterSet, new Intent(context, PlaceholderActivity.class) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build();
SplitPlaceholderRule.Builder
tworzy i konfiguruje regułę:placeholderActivityFilterSet
: zawiera filtry aktywności, które określają, kiedy należy zastosować regułę, przez identyfikowanie aktywności, z którymi powiązana jest aktywność zastępcza.Intent
: określa uruchomienie aktywności z miejscem zastępczym.setDefaultSplitAttributes()
: atrybuty układu są stosowane do reguły.setMinWidthDp()
: Ustawia minimalną szerokość wyświetlacza (w pikselach niezależnych od gęstości, dp) umożliwiającą podział.setMinSmallestWidthDp()
: zmienia 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, w której wyświetlane są podziały aktywności. Uwaga: wartość domyślna to 1,4, co powoduje, że na większości tabletów aktywności wypełniają okno zadania w orientacji poziomej. Zobacz teżSPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
isetMaxAspectRatioInLandscape()
. Wartością domyślną dla orientacji poziomej jestALWAYS_ALLOW
.setFinishPrimaryWithPlaceholder()
: określa, jak zakończenie działania obiektu zastępczego wpływa na działania w kontenerze głównym. ZAWSZE wskazuje, że system zawsze powinien zakończyć czynności w kontenerze głównym po zakończeniu działania placeholdera (patrz Zakończ czynności).setSticky()
: określa, czy aktywność zastępcza ma być widoczna na szczycie grupy aktywności na małych ekranach, gdy zastępcza aktywność zostanie po raz pierwszy wyświetlona w ramach grupy z wystarczającą minimalną szerokością.
Dodaj regułę do WindowManagera
RuleController
:Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
Określ działania, które nigdy nie powinny być uwzględniane w podziale:
Utwórz
ActivityFilter
, który identyfikuje aktywność, która powinna zawsze zajmować całą powierzchnię wyświetlania zadania:Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
Dodaj filtr do zestawu filtrów:
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
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łę, przez identyfikowanie działań, które chcesz wykluczyć z podziału.setAlwaysExpand()
: określa, czy aktywność ma wypełniać całe okno zadania.
Dodaj regułę do WindowManagera
RuleController
:Kotlin
ruleController.addRule(activityRule)
Java
ruleController.addRule(activityRule);
Umieszczenie w innej aplikacji
Na Androidzie 13 (poziom API 33) lub nowszym aplikacje mogą umieszczać w sobie czynności z innych aplikacji. Umieszczenie aktywności w innej aplikacji (lub z użyciem identyfikatora UID) umożliwia wizualną integrację aktywności z wielu aplikacji na Androida. System wyświetla aktywność aplikacji hosta i osadzonej aktywności z innej aplikacji obok siebie lub u góry i dołu, tak jak w przypadku osadzenia aktywności w jednej aplikacji.
Na przykład aplikacja Ustawienia może zawierać aktywność selektora tapety z aplikacji WallpaperPicker:

Model zaufania
Procesy hosta, które zawierają wbudowane aktywności z innych aplikacji, mogą zmienić sposób wyświetlania tych aktywności, w tym ich rozmiar, położenie, przycięcie i przezroczystość. Złośliwi hostowie mogą używać tej funkcji, aby wprowadzać użytkowników w błąd i tworzyć ataki typu clickjacking lub inne ataki polegające na przekierowywaniu użytkowników do fałszywych stron.
Aby zapobiec niewłaściwemu umieszczaniu elementów aktywności w innych aplikacjach, Android wymaga, aby aplikacje zezwalały na umieszczanie elementów aktywności. Aplikacje mogą oznaczać hosty jako zaufane lub niezaufajne.
Zaufane hosty
Aby umożliwić innym aplikacjom umieszczanie i pełną kontrolę nad prezentacją działań z Twojej aplikacji, podaj certyfikat SHA-256 aplikacji hosta w atrybucie android:knownActivityEmbeddingCerts
w elementach <activity>
lub <application>
pliku manifestu aplikacji.
Ustaw wartość android:knownActivityEmbeddingCerts
jako ciąg znaków:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
... />
lub, aby określić wiele certyfikatów, tablicę ciągów znaków:
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
... />
który odwołuje się do zasobu w ten sposób:
<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
. Digest certyfikatu to odcisk cyfrowy SHA-256 bez dwukropków oddzielających. Więcej informacji znajdziesz w artykułach Tworzenie raportu z podpisywania i Uwierzytelnianie klienta.
Niezaufane hosty
Aby umożliwić każdej aplikacji umieszczanie działań z Twojej aplikacji i sterowanie ich wyświetlaniem, określ atrybut android:allowUntrustedActivityEmbedding
w elementach <activity>
lub <application>
w pliku manifestu aplikacji, na przykład:
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
Domyślna wartość atrybutu to „fałsz”, co uniemożliwia umieszczanie w innych aplikacjach.
Uwierzytelnianie niestandardowe
Aby ograniczyć ryzyko nieautoryzowanego umieszczania treś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 uwierzytelnienia. Jeśli gospodarz uwierzytelnił się po umieszczeniu Twojej aktywności, możesz wyświetlić rzeczywiste treści. Jeśli nie, możesz poinformować użytkownika, że działanie nie zostało dozwolone, i zablokować treści.
Aby sprawdzić, czy gospodarz umieszcza Twoją aktywność, użyj metody ActivityEmbeddingController#isActivityEmbedded()
z biblioteki Jetpack WindowManager, na przykład:
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity); }
Minimalne ograniczenie rozmiaru
System Androida stosuje minimalną wysokość i szerokość określone w pliku manifestu aplikacji <layout>
do zaimplementowanych działań. Jeśli aplikacja nie określa minimalnej wysokości i szerokości, obowiązują wartości domyślne systemu (sw220dp
).
Jeśli host spróbuje zmienić rozmiar osadzonego kontenera na mniejszy niż minimalny, osadzony kontener rozszerzy się, aby wypełnić cały obszar zadania.
<activity-alias>
Aby umieszczanie w ramach działań zaufanych lub niezaufanych działało z elementem <activity-alias>
, do działania docelowego należy zastosować element android:knownActivityEmbeddingCerts
lub android:allowUntrustedActivityEmbedding
, a nie alias. Zasada, która weryfikuje bezpieczeństwo na serwerze systemowym, jest oparta na flagach ustawionych na obiekcie docelowym, a nie na aliasie.
Aplikacja hosta
Aplikacje hosta implementują umieszczanie aktywności w innych aplikacjach w taki sam sposób, w jaki implementują umieszczanie aktywności w jednej aplikacji. Obiekty SplitPairRule
i SplitPairFilter
lub ActivityRule
i ActivityFilter
określają wbudowane aktywności i podziały okna zadania. Reguły podziału są definiowane statycznie w pliku XML lub w czasie wykonywania za pomocą wywołań interfejsu Jetpack WindowManager API.
Jeśli aplikacja hosta próbuje osadzić aktywność, która nie została włączona do umieszczania w innych aplikacjach, aktywność zajmuje cały zakres zadania. W związku z tym aplikacje hosta muszą wiedzieć, czy docelowe aktywności umożliwiają umieszczanie elementów z innych aplikacji.
Jeśli wbudowana aktywność uruchamia nową aktywność w tym samym zadaniu, a nowa aktywność nie została włączona w ramach wbudowanego kontenera, zajmuje ona cały obszar zadania, a nie nakłada się na aktywność wbudowaną w kontenerze.
Aplikacja hosta może umieszczać w sobie własne działania bez ograniczeń, o ile tylko działania te są uruchamiane w ramach tego samego zadania.
Przykłady podziału
Dzielenie z pełnego okna

Nie trzeba przeprowadzać refaktoryzacji. Konfigurację podziału możesz zdefiniować statycznie lub w czasie wykonywania, a potem wywołać funkcję Context#startActivity()
bez żadnych dodatkowych parametrów.
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Domyślne dzielenie
Gdy strona docelowa aplikacji jest podzielona na 2 kontenery na dużych ekranach, użytkownik ma najlepsze wrażenia, gdy obie czynności są tworzone i prezentowane jednocześnie. Treści mogą jednak nie być dostępne dla drugiego kontenera w ramach podziału, dopóki użytkownik nie wejdzie w interakcję z działalnością w kontenerze głównym (np. nie wybierze elementu z menu nawigacyjnego). Aktywność obiektu zastępczego może wypełnić lukę do czasu, aż treści będą mogły być wyświetlane w drugim kontenerze podziału (patrz sekcja Obiekty zastępcze).

Aby utworzyć podział z użyciem zastępczego elementu, utwórz zastępczy element i połącz go z główną aktywnością:
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
Podział precyzyjnych linków
Gdy aplikacja otrzyma intencję, docelową aktywność można wyświetlić jako część wtórną podzielonej aktywności, na przykład prośbę o wyświetlenie ekranu z informacjami o elemencie z listy. Na małych ekranach szczegóły są wyświetlane w pełnym oknie zadania, a na większych urządzeniach – obok listy.

Żądanie uruchomienia powinno być kierowane do głównej aktywności, a docelowa aktywność z szczegółami powinna być uruchamiana w ramach podziału. System automatycznie wybiera odpowiednią prezentację – obok siebie lub w wielokrotnym wierszu – na podstawie dostępnej szerokości wyświetlania.
Kotlin
override fun onCreate(savedInstanceState Bundle?) { . . . RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { . . . RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
Miejsce docelowe linku głębokiego może być jedyną aktywnością, która powinna być dostępna dla użytkownika na ostatnim poziomie nawigacji wstecz. Możesz też uniknąć zamykania aktywności z informacjami i zostawić tylko główną aktywność:
Zamiast tego możesz ukończyć obie czynności jednocześnie, używając atrybutu finishPrimaryWithSecondary
:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
Zobacz sekcję Atrybuty konfiguracji.
Wiele działań w kontenerach podzielonych
Umieszczanie wielu aktywności w podzielonym kontenerze umożliwia użytkownikom dostęp do treści. Na przykład w przypadku podziału na listę i szczegóły użytkownik może przejść do sekcji podrzędnej, ale zachować główną aktywność:

Kotlin
class DetailActivity { . . . fun onOpenSubDetail() { startActivity(Intent(this, SubDetailActivity::class.java)) } }
Java
public class DetailActivity { . . . void onOpenSubDetail() { startActivity(new Intent(this, SubDetailActivity.class)); } }
Aktywność podrzędna jest umieszczana nad aktywnością szczegółową, co powoduje jej ukrycie:
Użytkownik może wrócić do poprzedniego poziomu szczegółów, przechodząc wstecz przez poziomy:

Domyślnym zachowaniem podczas uruchamiania aktywności z aktywności w tym samym kontenerze jest ich ułożenie jedna na drugiej. Aktywności rozpoczęte w głównym kontenerze w ramach aktywnego podziału trafiają też do kontenera pomocniczego i znajdują się na szczycie stosu aktywności.
Aktywności w nowym zadaniu
Gdy czynności w oknie podzielonego zadania uruchamiają czynności w nowym zadaniu, nowe zadanie jest oddzielone od zadania, które zawiera podział, i wyświetlane w pełnym oknie. Na ekranie Ostatnie są widoczne 2 zadania: zadanie w ramach podziału i nowe zadanie.

Wymiana aktywności
Aktywności można zastępować w dodatkowym zbiorze kontenerów, np. gdy główna aktywność jest używana do nawigacji na najwyższym poziomie, a dodatkowa aktywność jest wybranym celem. Każdy element w menu najwyższego poziomu powinien uruchamiać nową aktywność w kontenerze drugorzędnym i usuwać aktywność lub aktywności, które były tam wcześniej.

Jeśli aplikacja nie zakończy aktywności w kontenerze drugorzędnym po zmianie wyboru nawigacji, cofanie może być mylące, 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, gdy użytkownik złoży telefon, ekran B znajdzie się nad ekranem A, a ekran A nad menu. Gdy użytkownik wraca z poziomu B, zamiast menu pojawia się poziom A.
W takich przypadkach ekran A musi zostać usunięty ze stosu.
Domyślne zachowanie podczas uruchamiania nowej instancji kontenera na karcie obok istniejącego podziału polega na umieszczeniu nowych kontenerów pomocniczych na górze i zachowaniu starych kontenerów w grupie kontenerów na dole. Możesz skonfigurować podziały tak, aby czyś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
class MenuActivity { . . . fun onMenuItemSelected(selectedMenuItem: Int) { startActivity(Intent(this, classForItem(selectedMenuItem))) } }
Java
public class MenuActivity { . . . void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
Możesz też użyć tej samej aktywności w kontenerze pomocniczym, a z głównej aktywności (menu) wysyłać nowe intencje, które odwołują się do tej samej instancji, ale wywołują stan lub aktualizację interfejsu w kontenerze pomocniczym.
Wiele podziałów
Aplikacje mogą zapewniać nawigację na wielu poziomach, uruchamiając dodatkowe czynności po bokach.
Gdy aktywność w kontenerze drugorzędnym uruchamia nową aktywność po stronie, nowy podział jest tworzony na górze dotychczasowego podziału.

Stapel zawiera wszystkie wcześniej otwarte aktywności, dzięki czemu użytkownicy mogą przejść do testu A/B po zakończeniu korzystania z C.
Aby utworzyć nowy podział, uruchom nową aktywność obok istniejącego kontenera pomocniczego. Zadeklaruj konfiguracje zarówno dla podziału A/B, jak i B/C, a potem uruchom aktywność C normalnie z poziomu B:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
<SplitPairFilter
window:primaryActivityName=".B"
window:secondaryActivityName=".C"/>
</SplitPairRule>
Kotlin
class B { . . . fun onOpenC() { startActivity(Intent(this, C::class.java)) } }
Java
public class B { . . . void onOpenC() { startActivity(new Intent(this, C.class)); } }
Reakcja na zmiany stanu podziału
Różne czynności w aplikacji mogą mieć elementy UI, które pełnią tę samą funkcję, np. element sterujący otwierający okno z ustawieniami konta.

Jeśli 2 aktywności, które mają wspólny element interfejsu użytkownika, są w ramach podziału, wyświetlanie tego elementu w obu aktywnościach jest zbędne i może wprowadzać użytkowników w błąd.

Aby dowiedzieć się, kiedy działania są w ramach podziału, sprawdź przepływ SplitController.splitInfoList
lub zarejestruj listenera z SplitControllerCallbackAdapter
, aby otrzymywać powiadomienia o zmianach w stanie 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 { 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); }); }
Coroutines można uruchamiać w dowolnym stanie cyklu życia, ale zwykle są one uruchamiane w stanie STARTED
, aby oszczędzać zasoby (więcej informacji znajdziesz w artykule Używanie coroutines w Kotlinie z uwzględnieniem cyklu życia).
Callbacki mogą być wywoływane w dowolnym stanie cyklu życia, w tym po zatrzymaniu aktywności. Słuchacze powinni być zwykle zarejestrowani w onStart()
i niezarejestrowani w onStop()
.
Okno modalne na pełnym ekranie
Niektóre czynności blokują użytkownikom interakcję z aplikacją, dopóki nie wykonają określonego działania, np. nie klikną przycisku logowania, nie zaakceptują zasad lub nie usuną komunikatu o błędzie. Działania modalne nie powinny pojawiać się w podgrupie.
Aktywność może być zawsze wypełniana w oknie zadania za pomocą konfiguracji rozszerzania:
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullWidthActivity"/>
</ActivityRule>
Zakończ działania
Użytkownicy mogą kończyć aktywności po obu stronach podziału, przesuwając palcem od krawędzi ekranu:


Jeśli urządzenie jest skonfigurowane tak, aby używać przycisku Wstecz zamiast gestów, dane wejściowe są wysyłane do aktywnej aktywności – aktywności, która została ostatnio dotknięta lub uruchomiona.
Wpływ ukończenia wszystkich działań w kontenerze na kontener przeciwny zależy od konfiguracji podziału.
Atrybuty konfiguracji
Możesz określić atrybuty reguły pary podziałów, aby skonfigurować sposób, w jaki zakończenie wszystkich działań po jednej stronie podziału wpływa na działania po drugiej stronie podziału. Atrybuty:
window:finishPrimaryWithSecondary
– sposób, w jaki zakończenie wszystkich działań w kontenerze dodatkowym wpływa na działania w kontenerze głównymwindow:finishSecondaryWithPrimary
– sposób, w jaki zakończenie wszystkich działań w kontenerze głównym wpływa na działania w kontenerze dodatkowym
Możliwe wartości atrybutów:
always
– zawsze kończ aktywności w powiązanym kontenerze.never
– nigdy nie kończ działań w powiązanym kontenerze.adjacent
– kończyć aktywności w powiązanym kontenerze, gdy dwa kontenery są wyświetlane obok siebie, ale nie wtedy, gdy są ułożone jeden na drugim;
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 aktywności w jednym kontenerze w ramach podziału zostaną zakończone, pozostały kontener zajmie całe okno:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Dokończenie wspólnych działań
automatycznie kończyć działania w kontenerze głównym po zakończeniu wszystkich działań w kontenerze pomocniczym:
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
automatycznie kończyć działania w kontenerze pomocniczym po zakończeniu wszystkich działań w kontenerze głównym:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Zakończ wszystkie zadania, gdy zakończą się wszystkie zadania w kontenerze głównym lub pomocniczym:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Zakończ wiele działań w kontenerach
Jeśli w podzielonym kontenerze ułożone są obok siebie liczne aktywności, zakończenie aktywności na dole nie powoduje automatycznego zakończenia aktywności u góry.
Jeśli np. w kontenerze drugorzędnym znajdują się 2 aktywności, a C jest nad B:
a konfiguracja podziału jest określana przez konfigurację działań A i B:
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Zakończenie głównej aktywności zachowuje podział.
Zakończenie dolnej (podstawowej) aktywności w kontenerze dodatkowym nie powoduje usunięcia aktywności znajdujących się nad nią, a tym samym zachowuje podział.
Są też wykonywane wszelkie dodatkowe reguły dotyczące kończenia aktywności, takie jak kończenie aktywności drugorzędnych wraz z aktywnością główną:
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Gdy konfiguracja polega na zakończeniu strumienia głównego i dodatkowego razem:
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
Zmienianie właściwości rozsunięcia w czasie wykonywania
Właściwości aktywnego i widocznego podziału nie można zmienić. Zmiana reguł podziału wpływa na dodatkowe uruchomienia aktywności i nowe kontenery, ale nie dotyczy istniejących i aktywnych podziałów.
Aby zmienić właściwości aktywnych podziałów, zakończ aktywność poboczną lub kilka takich aktywności w ramach danego podziału i uruchom je ponownie z nową konfiguracją.
Właściwości podziału dynamicznego
Android 15 (poziom interfejsu API 35) i nowsze obsługiwane przez Jetpacka WindowManager 1.4 i nowsze oferują funkcje dynamiczne, które umożliwiają konfigurowanie rozłamywania w ramach aktywności, w tym:
- Rozszerzanie paneli: interaktywny, przeciągany separator umożliwia użytkownikom zmianę rozmiaru paneli w ramach podzielonej prezentacji.
- Przypinanie elementów w grupie aktywności: użytkownicy mogą przypinać treści w jednym kontenerze i oddzielać nawigację w tym kontenerze od nawigacji w innym kontenerze.
- Zaciemnianie okna dialogowego na pełnym ekranie: podczas wyświetlania okna dialogowego aplikacje mogą określić, czy zaciemnić całe okno zadania, czy tylko kontener, który otworzył okno dialogowe.
Rozwinięcie panelu
Rozszerzenie panelu umożliwia użytkownikom dostosowanie ilości miejsca na ekranie przypisanej do dwóch czynności w układzie z dwoma panelami.
Aby dostosować wygląd przegrody między oknami i ustawić zakres przeciągania, wykonaj te czynności:
Utwórz instancję
DividerAttributes
Dostosowywanie atrybutów separatora:
color
: kolor przeciąganego separatora panelu.widthDp
: szerokość przeciąganego separatora panelu. Ustaw naWIDTH_SYSTEM_DEFAULT
, aby system określił szerokość separatora.Zakres przeciągania: minimalny odsetek ekranu, który może zajmować dana kolumna. Może wynosić od 0,33 do 0,66. Ustaw na
DRAG_RANGE_SYSTEM_DEFAULT
, aby system określił 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(context, 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(context, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ); } SplitAttributes splitAttributes = splitAttributesBuilder.build();
Przypinanie elementów stosu aktywności
Przypinanie grupy aktywności umożliwia użytkownikom przypinanie jednego z okna podzielonego, dzięki czemu aktywność pozostaje bez zmian, gdy użytkownicy poruszają się po drugim oknie. Przypinanie kart zapewnia większą wygodę podczas wielozadaniowości.
Aby umożliwić przypinanie elementów w steku aktywności w aplikacji:
Dodaj do pliku układu przycisk aktywności, którą chcesz przypiąć, na przykład szczegółową aktywność w układzie 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>
W metodzie
onCreate()
aktywności ustaw odbiornik onclick przycisku:Kotlin
pinButton = 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); });
Przyciemnianie okna pełnoekranowego
Aktywności zwykle przyciemniają wyświetlacz, aby zwrócić uwagę na dialog. Aby zapewnić spójność interfejsu użytkownika, w przypadku umieszczania aktywności oba panele wyświetlacza z podwójnym panelem powinny być przyciemnione, a nie tylko panel zawierający aktywność, która otworzyła okno dialogowe.
W ramach WindowManager 1.4 i wyższych po otwarciu okna dialogowego domyślnie przyciemnia się całe okno aplikacji (patrz EmbeddingConfiguration.DimAreaBehavior.ON_TASK
).
Aby przyciemnić tylko kontener aktywności, która otworzyła okno, użyj EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK
.
Przenoszenie aktywności z podzielonego okna do pełnego okna
Utwórz nową konfigurację, która wyświetla pełne okno aktywności pobocznej, a następnie uruchom ponownie działanie z zamiarem, który przekierowuje do tej samej instancji.
Sprawdzanie obsługi podziału w czasie wykonywania
Osadzanie aktywności jest obsługiwane w Androidzie 12L (poziom interfejsu API 32) i nowszych wersjach, ale jest też dostępne na niektórych urządzeniach z uprzednimi wersjami platformy. Aby sprawdzić dostępność funkcji w czasie wykonywania kodu, 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 rozdzielniki nie są obsługiwane, aktywności są uruchamiane na szczycie stosu aktywności (zgodnie z modelem umieszczania bez aktywności).
Zapobieganie zastępowaniu ustawień systemowych
Producenci urządzeń z Androidem (producenci oryginalnego sprzętu, czyli OEM) mogą implementować funkcję wklejania aktywności jako funkcję systemu urządzenia. System określa reguły podziału dla aplikacji wielozadaniowych, zastępując zachowanie okna aplikacji. Zastąpienie systemowe powoduje, że aplikacje z wieloma aktywnościami są zmuszane do korzystania z trybu wklejania aktywności zdefiniowanego przez system.
Przejęcie aktywności przez system może poprawić prezentację aplikacji dzięki układom wielopanelowym, takim jak lista-szczegóły, bez wprowadzania żadnych zmian w aplikacji. Jednak przejęcie aktywności przez system może też powodować nieprawidłowe układy aplikacji, błędy lub konflikty z wdrożonym przez aplikację przejęciem aktywności.
Aplikacja może zapobiegać umieszczaniu aktywności systemu lub zezwalać na to, aby miało to miejsce. Aby to zrobić, ustaw PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE
w pliku manifestu aplikacji, na przykład:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
android:value="true|false" />
</application>
</manifest>
Nazwa usługi jest zdefiniowana w obiekcie Jetpack WindowManager WindowProperties
. Ustaw wartość na false
, jeśli Twoja aplikacja implementuje osadzenie aktywności, lub jeśli chcesz w inny sposób uniemożliwić systemowi stosowanie do niej reguł dotyczących osadzania aktywności. Ustaw wartość na true
, aby umożliwić systemowi stosowanie do aplikacji osadzania aktywności zdefiniowanego przez system.
Ograniczenia, ograniczenia i ostrzeżenia
- Tylko aplikacja hosta zadania, 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 zadania obsługujące umieszczanie i pododdziały są wykonywane w ramach zadania należącego do innej aplikacji, umieszczanie i pododdziały nie będą działać w przypadku tych zadań.
- Działania można organizować tylko w ramach jednego zadania. Uruchomienie aktywności w nowym zadaniu zawsze powoduje jej wyświetlenie w nowym oknie poza istniejącymi podziałami.
- Tylko działania w tym samym procesie można grupować i umieszczać w podziale. W wyniku wywołania zwrotnego
SplitInfo
są raportowane tylko działania należące do tego samego procesu, ponieważ nie ma możliwości uzyskania informacji o działaniach w różnych procesach. - Każda para lub pojedyncza reguła aktywności ma zastosowanie tylko do uruchamiania aktywności, które nastąpiło po zarejestrowaniu reguły. Obecnie nie można aktualizować dotychczasowych podziałów ani ich właściwości wizualnych.
- Konfiguracja filtra pary rozdzielonej musi odpowiadać intencjom używanym podczas uruchamiania aktywności w pełni. Dopasowanie następuje w momencie, gdy nowa aktywność jest uruchamiana z procesu aplikacji, więc może nie znać nazw komponentów, które są rozwiązywane później w ramach procesu systemowego podczas korzystania z intencji domyślnych. Jeśli nazwa komponentu nie jest znana w momencie uruchomienia, zamiast niej można użyć znaku zastępczego („*/*”), a filtrowanie można wykonać na podstawie działania w intencji.
- Obecnie nie można przenosić działań między kontenerami ani przenosić ich do grup lub z nich po utworzeniu. Podziały są tworzone przez bibliotekę WindowManager tylko wtedy, gdy uruchamiane są nowe aktywności z pasującymi regułami. Podziały są usuwane, gdy kończy się ostatnia aktywność w kontenerze podzielonym.
- Aktywności można ponownie uruchomić, gdy zmieni się konfiguracja. Gdy więc utworzysz lub usuniesz podział i zmienisz granice aktywności, ta ostatnia może zostać całkowicie zniszczona, a następnie utworzona na nowo. Dlatego deweloperzy aplikacji powinni zachować ostrożność w takich kwestiach jak uruchamianie nowych działań z poziomu wywołań cyklu życia.
- Aby obsługiwać umieszczanie aktywności, urządzenia muszą mieć interfejs rozszerzeń okna. Interfejs ten jest dostępny na prawie wszystkich urządzeniach z dużym ekranem z Androidem 12L (poziom API 32) lub nowszym. Niektóre urządzenia z dużym ekranem, które nie mogą uruchamiać wielu działań, nie mają interfejsu rozszerzeń okien. Jeśli urządzenie z dużym ekranem nie obsługuje trybu wielookienkowego, może nie obsługiwać umieszczania aktywności.
Dodatkowe materiały
- Codelabs:
- Ścieżka szkoleniowa – osadzanie aktywności
- Przykładowa aplikacja: osadzanie aktywności