アクティビティの埋め込み

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

アクティビティの埋め込みは、大画面デバイス上のアプリのタスク ウィンドウを 2 つのアクティビティ、または同じアクティビティの 2 つのインスタンスに分割することでアプリを最適化する機能です。

図 1. 設定アプリのアクティビティを並べて表示。

大画面をサポートするために従来のコードベースを更新する作業には、多大な労力と時間がかかります。また、フラグメントを使用してアクティビティ ベースのアプリをマルチペイン レイアウトに変換するには、大規模なリファクタリングが必要です。

アクティビティの埋め込みでは、アプリのリファクタリングはほとんど必要ありません。XML 構成ファイルを作成するか、Jetpack WindowManager API を呼び出すことで、アプリでアクティビティを並べて表示するか重ねて表示するかを決められます。

小画面のサポートは自動的に維持されます。アプリが小画面のデバイスにある場合、アクティビティは互いに重ねられます。大画面の場合、アクティビティは並べて表示されます。作成した構成に基づいてシステムで表示方法が判断されます。分岐ロジックは必要ありません。

アクティビティの埋め込みでは、デバイスの向きを変えられます。折りたたみ式デバイスでもシームレスに動作し、デバイスの折りたたみと展開に合わせて、アクティビティの積み重ねと積み重ねの解除が行われます。

最新の Android 開発では、フラグメント、ナビゲーション コンポーネント、SlidingPaneLayout のような多用途レイアウト マネージャーを備えた、単一アクティビティ アーキテクチャが採用されています。

しかし、複数のアクティビティで構成されているアプリの場合、アクティビティの埋め込みを使用することで、タブレット、折りたたみ式デバイス、ChromeOS デバイスでのユーザー エクスペリエンスを簡単に向上させることができます。

分割タスク ウィンドウ

アクティビティの埋め込みは、アプリのタスク ウィンドウをプライマリとセカンダリという 2 つのコンテナに分割します。コンテナは、メイン アクティビティから起動されたアクティビティ、またはコンテナ内にすでに存在する他のアクティビティから起動されたアクティビティを保持します。

アクティビティは、起動時にセカンダリ コンテナ内で重ねられ、小画面ではセカンダリ コンテナがプライマリ コンテナの上に重ねられるため、アクティビティの積み重ねと「戻る」ナビゲーションは、アプリにすでに組み込まれているアクティビティの順序と一致します。

アクティビティの埋め込みを使用すると、さまざまな方法でアクティビティを表示できます。アプリは、2 つのアクティビティを同時に並べて起動することで、タスク ウィンドウを分割できます。

図 2. 2 つのアクティビティを並べて表示。

また、タスク ウィンドウ全体を占有するアクティビティは、新しいアクティビティを並べて起動することで、タスク ウィンドウを分割できます。

図 3. アクティビティ A がアクティビティ B を横に起動。

すでに分割されてタスク ウィンドウを共有しているアクティビティは、次の方法で他のアクティビティを起動できます。

  • 横のアクティビティの上に重ねて起動:

    図 4. アクティビティ A が、横のアクティビティ B の上にアクティビティ C を重ねて起動。
  • 横に起動し、分割を横にシフトして以前のプライマリ アクティビティを非表示にする:

    図 5. アクティビティ B が、横にアクティビティ C を起動して分割を横にシフト。
  • アクティビティをその場所の一番上(つまり同じアクティビティ スタック)に起動:

    図 6. アクティビティ B が、追加のインテント フラグなしでアクティビティ C を起動。
  • アクティビティをそのタスクのフルウィンドウで起動:

    図 7. アクティビティ A またはアクティビティ B が、タスク ウィンドウいっぱいにアクティビティ C を起動。

「戻る」ナビゲーション

アクティビティ間の依存関係やユーザーが「戻る」イベントをトリガーする方法に応じて、分割タスク ウィンドウ状態での「戻る」ナビゲーションのルールはアプリの種類によって異なる場合があります。次に例を示します。

  • 一緒に: アクティビティ同士に関連性があり、一方のアクティビティを他方のアクティビティなしで表示すべきでない場合、両方を終了させるように「戻る」ナビゲーションを設定できます。
  • 単独で: タスク ウィンドウ内のアクティビティ同士が完全に独立している場合、一方のアクティビティの「戻る」ナビゲーションは、他方のアクティビティの状態に影響を与えません。

ボタン ナビゲーションを使用する場合、「戻る」イベントは最後にフォーカスされたアクティビティに送信されます。ジェスチャー ベースのナビゲーションの場合、「戻る」イベントはジェスチャーが行われたアクティビティに送信されます。

マルチペイン レイアウト

Jetpack WindowManager 1.0 Beta03 を使用すると、12L(API レベル 32)を搭載した大画面デバイスと、以前のバージョンのプラットフォームを搭載した一部のデバイスで、アクティビティを使ったマルチペイン レイアウトを構築できます。フラグメントやビューベースのレイアウト(SlidingPaneLayout など)ではなく、複数のアクティビティを利用した既存のアプリは、大幅なリファクタリングをせずに、大画面のユーザー エクスペリエンスを高められます。

一般的な例としては、リストと詳細の分割が挙げられます。質の高い表示を確保するために、システムはリスト アクティビティを起動し、その後アプリは直ちに詳細アクティビティを起動します。遷移システムは、両方のアクティビティが描画されるまで待機してから、一緒に表示します。ユーザーにとっては、2 つのアクティビティが 1 つのものとして起動します。

図 8. マルチペイン レイアウトで同時に起動した 2 つのアクティビティ。

分割比

アプリは、分割の設定の ratio 属性によって、タスク ウィンドウの比率を指定できます(後述の分割の設定をご覧ください)。

図 9. 分割比が異なる 2 つのアクティビティ分割。

プレースホルダ

プレースホルダ アクティビティは、アクティビティ分割の領域を占有する空のセカンダリ アクティビティです。最終的には、コンテンツを含む別のアクティビティに置き換えられます。たとえばプレースホルダ アクティビティは、リストのアイテムが選択されるまで、リストと詳細のレイアウトでアクティビティ分割のセカンダリ側を占有します。選択された時点で、プレースホルダは選択されたリストアイテムの詳細情報を含むアクティビティに置き換えられます。

プレースホルダは、分割に十分なスペースがある場合にのみ表示されます。ディスプレイ サイズがアクティビティ分割を表示できないほど小さな幅に変更されると自動的に終了しますが、スペースが許せば自動的に(再初期化した状態で)再起動します。

図 10. 折りたたみ式デバイスの折りたたみと展開。プレースホルダ アクティビティは終了し、ディスプレイ サイズが変更されると再作成されます。

ウィンドウ サイズの変更

デバイス構成の変更によってタスク ウィンドウの幅が小さくなり、マルチペイン レイアウトに十分な大きさでなくなった場合(大画面の折りたたみ式デバイスがタブレット サイズからスマートフォン サイズに折りたたまれた場合や、マルチウィンドウ モードでアプリ ウィンドウがサイズ変更された場合など)、タスク ウィンドウのセカンダリ ペインにあるプレースホルダ以外のアクティビティが、プライマリ ペインのアクティビティの上に重ねられます。

プレースホルダのアクティビティは、分割するために十分な表示幅がある場合にのみ表示されます。小画面では、プレースホルダは自動的に閉じられます。表示領域が再び十分な大きさになると、プレースホルダが再作成されます(前述のプレースホルダをご覧ください)。

アクティビティを重ねることができるのは、WindowManager がセカンダリ ペインのアクティビティをプライマリ ペインのアクティビティの上に Z オーダーで表示するためです。

セカンダリ ペインの複数のアクティビティ

アクティビティ B が追加のインテント フラグなしで、アクティビティ C を所定の位置で起動します。

アクティビティ C がアクティビティ B の上に重ねられた、アクティビティ A、B、C を含むアクティビティ分割。

その結果、同じタスクのアクティビティの Z オーダーは次のようになります。

アクティビティ B の上に重ねられたアクティビティ C を含むセカンダリ アクティビティ スタック。セカンダリ スタックが、アクティビティ A を含むプライマリ アクティビティ スタックの上に重ねられています。

タスク ウィンドウが小さくなるとアプリは 1 つのアクティビティに縮小され、アクティビティ C がスタックの一番上に重ねられます。

アクティビティ C のみを表示する小さいウィンドウ。

小さいウィンドウで「戻る」操作を行うと、重ねられた各アクティビティ間で移動します。

タスク ウィンドウ構成を、複数のペインに対応できる大きなサイズに復元すると、アクティビティは再び並んで表示されます。

分割の積み重ね

アクティビティ B が、横にアクティビティ C を起動して分割を横にシフトします。

アクティビティ A と B が表示され、次にアクティビティ B と C が表示されるタスク ウィンドウ。

その結果、同じタスクのアクティビティの Z オーダーは次のようになります。

1 つのスタック内にあるアクティビティ A、B、C。アクティビティは上から C、B、A の順に重ねられています。

アプリは、タスク ウィンドウが小さくなると、アクティビティ C が一番上にある 1 つのアクティビティに縮小されます。

アクティビティ C のみを表示する小さいウィンドウ。

分割の設定

コンテナと分割は、分割ルールに基づいて WindowManager ライブラリで作成できます。分割ルールの設定手順は以下のとおりです。

  1. build.gradle ファイルに WindowManager ライブラリの依存関係を追加します。

    implementation("androidx.window:window:1.0.0-beta03")

  2. 次の処理を行うリソース ファイルを作成します。

    • フィルタを使用して分割するアクティビティを定義する
    • 分割を共有するすべてのアクティビティの分割オプションを設定する
    • 分割に入れないアクティビティを指定する

    例:

    <!-- The split configuration for activities. -->
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Automatically split the following activity pairs. -->
        <SplitPairRule
            window:splitRatio="0.3"
            window:splitMinWidth="600dp"
            window:finishPrimaryWithSecondary="true"
            window:finishSecondaryWithPrimary="true">
            <SplitPairFilter
                window:primaryActivityName=".SplitActivityList"
                window:secondaryActivityName=".SplitActivityDetail"/>
            <SplitPairFilter
                window:primaryActivityName="*"
                window:secondaryActivityName="*/*"
                window:secondaryActivityAction="android.intent.action.VIEW"/>
        </SplitPairRule>
    
        <!-- Automatically launch a placeholder for the list activity. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".SplitActivityListPlaceholder"
            window:splitRatio="0.3"
            window:splitMinWidth="600dp">
            <ActivityFilter
                window:activityName=".SplitActivityList"/>
        </SplitPlaceholderRule>
    
    </resources>
    
  3. ルールの定義をライブラリに伝えます。

    この例では、アプリの他のコンポーネントが読み込まれてアクティビティが起動する前に、Jetpack Startup ライブラリを使用して初期化を行います。この Startup の機能を有効にするには、アプリのビルドファイルにライブラリ依存関係を追加します。

    implementation("androidx.startup:startup-runtime:1.1.0")

    アプリ マニフェストに次のエントリを追加します。

    <!-- AndroidManifest.xml -->
    
    <provider android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- This entry makes ExampleWindowInitializer discoverable. -->
        <meta-data  android:name="androidx.window.sample.embedding.ExampleWindowInitializer"
            android:value="androidx.startup" />
    </provider>
    
  4. 最後に、Initializer クラスの実装を追加します。

    ルールを設定するには、SplitController.initialize() に定義(main_split_config)を含む XML リソース ファイルの ID を指定します。

    Kotlin

    class ExampleWindowInitializer : Initializer<SplitController> {
       override fun create(context: Context): SplitController {
           SplitController.initialize(context, R.xml.main_split_config)
           return SplitController.getInstance(context)
       }
    
       override fun dependencies(): List<Class<out Initializer<*>>> {
           return emptyList()
       }
    }
    

    Java

    class ExampleWindowInitializer extends Initializer<SplitController> {
       @Override
       SplitController create(Context context) {
           SplitController.initialize(context, R.xml.main_split_config);
           return SplitController.getInstance(context);
       }
    
       @Override
       List<Class<? extends Initializer<?>>> dependencies() {
           return emptyList();
       }
    }
    

分割の例

フルウィンドウからの分割

図 11. アクティビティ A がアクティビティ B を横に起動。

リファクタリングは必要ありません。分割の設定を静的に、またはランタイムに定義し、追加のパラメータなしで Context#startActivity() を呼び出すことができます。

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

デフォルトでの分割

アプリのランディング ページが、大画面で 2 つのコンテナに分割されるように設計されている場合、両方のアクティビティを同時に作成して表示すると最良のユーザー エクスペリエンスが得られます。ただし、ユーザーがプライマリ コンテナでアクティビティを操作する(ナビゲーション メニューからアイテムを選択するなど)まで、分割のセカンダリ コンテナでコンテンツを利用できない場合があります。プレースホルダ アクティビティは、分割のセカンダリ コンテナにコンテンツを表示できるようになるまで、空白を埋めることができます(前述のプレースホルダをご覧ください)。

図 12. 2 つのアクティビティを同時に開くことで作成された分割。一方のアクティビティはプレースホルダです。

プレースホルダを伴う分割を作成するには、プレースホルダを作成し、プライマリ アクティビティに関連付けます。

<SplitPlaceholderRule
    window:placeholderIntentName=".Placeholder">
    <ActivityFilter
        window:activityName=".Main"/>
</SplitPlaceholderRule>

アプリがインテントを受け取ると、ターゲット アクティビティをアクティビティ分割のセカンダリ部分として表示できます(リストのアイテムに関する情報を詳細画面に表示するリクエストなど)。詳細は、小さいディスプレイではタスク ウィンドウ全体に表示され、大きいデバイスではリストの横に表示されます。

図 13. ディープリンクの詳細アクティビティは、小画面では単独で表示され、大画面ではリスト アクティビティとともに表示される。

起動リクエストはメイン アクティビティにルーティングされ、ターゲットの詳細アクティビティは分割で起動する必要があります。SplitController は、利用可能な表示幅に基づいて、適切な表示方法(積み重ねまたは横並び)を自動的に選択します。

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    …
    splitController.registerRule(SplitPairRule(newFilters))
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    …
    splitController.registerRule(new SplitPairRule(newFilters));
    startActivity(new Intent(this, DetailActivity.class));
}

「戻る」ナビゲーション スタックでユーザーが利用できるアクティビティが、ディープリンクのデスティネーションのみの場合があります。その場合も含めて、詳細アクティビティが閉じてメイン アクティビティだけが残る、ということがないようにしてください。

リスト アクティビティと詳細アクティビティを並べて表示した大きなディスプレイ。
          「戻る」ナビゲーションで、詳細アクティビティを閉じてリスト アクティビティを画面に残すことはできません。

詳細アクティビティのみの小さいディスプレイ。「戻る」ナビゲーションで、詳細アクティビティを閉じてリスト アクティビティを表示することはできません。

代わりに、finishPrimaryWithSecondary 属性を使用することで両方のアクティビティを同時に終了できます。

<SplitPairRule
    window:finishPrimaryWithSecondary="true">
    <SplitPairFilter
        window:primaryActivityName=".List"
        window:secondaryActivityName=".Detail"/>
</SplitPairRule>

分割コンテナ内の複数のアクティビティ

分割コンテナに複数のアクティビティを重ねると、ユーザーは深いコンテンツにアクセスできます。たとえばリストと詳細の分割で、ユーザーがサブ詳細セクションに移動する必要がある場合でも、プライマリ アクティビティはそのまま表示し続けます。

図 14. タスク ウィンドウのセカンダリ ペインで所定の位置に開かれたアクティビティ。

Kotlin

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

Java

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

サブ詳細アクティビティが、詳細アクティビティを隠すように、上に配置されます。

すると、ユーザーはスタック内をさかのぼって移動することで前の詳細レベルに戻ることができます。

図 15. スタック最上位からのアクティビティの削除。

同じセカンダリ コンテナ内のアクティビティからアクティビティを起動すると、アクティビティは互いに重ねられます。これがデフォルトの動作です。アクティブな分割の中でプライマリ コンテナから起動されたアクティビティも、アクティビティ スタックの一番上にあるセカンダリ コンテナに配置されます。

新しいタスクのアクティビティ

分割タスク ウィンドウ内のアクティビティが新しいタスクのアクティビティを起動すると、新しいタスクは、分割を含むタスクとは別に、ウィンドウ全体に表示されます。履歴画面には 2 つのタスク(分割の中のタスクと新しいタスク)が表示されます。

図 16. アクティビティ B から新しいタスクでアクティビティ C を起動。

アクティビティの置き換え

セカンダリ コンテナ スタック内でアクティビティを置き換えることができます(プライマリ アクティビティをトップレベル ナビゲーションに使用し、セカンダリ アクティビティが選択したデスティネーションである場合など)。トップレベル ナビゲーションから選択されるたびに、セカンダリ コンテナで新しいアクティビティが起動し、それまでそこにあったアクティビティは削除されます。

図 17. プライマリ ペインのトップレベル ナビゲーション アクティビティが、セカンダリ ペインのデスティネーション アクティビティに置き換わる。

ナビゲーションの選択が変更されたときにアプリがセカンダリ コンテナのアクティビティを終了しない場合、分割が閉じられた(デバイスが折りたたまれた)ときに「戻る」ナビゲーションがわかりにくくなる可能性があります。たとえば、プライマリ ペインにメニューがあり、セカンダリ ペインに画面 A と B が重ねられている場合、ユーザーがスマートフォンを折りたたむと、B が A の上になり、A がメニューの上になります。ユーザーが B から戻ると、メニューではなく A が表示されます。

このような場合は、画面 A をバックスタックから削除する必要があります。

既存の分割の上に新しいコンテナで横に起動する場合のデフォルト動作では、新しいセカンダリ コンテナが上に配置され、古いコンテナがバックスタックに保持されます。clearTop で以前のセカンダリ コンテナを消去して新しいアクティビティを通常どおり起動するように、分割を設定できます。

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

あるいは、同じセカンダリ アクティビティを使用し、プライマリ(メニュー)アクティビティから、同じインスタンスに解決されてセカンダリ コンテナで状態または UI の更新をトリガーする新しいインテントを送信します。

複数の分割

アプリは、追加のアクティビティを横に起動することで、多層の深いナビゲーションを提供できます。

セカンダリ コンテナのアクティビティが新しいアクティビティを横に起動すると、既存の分割の上に新しい分割が作成されます。

図 18. アクティビティ B がアクティビティ C を横に起動。

バックスタックには以前開かれたアクティビティがすべて含まれているため、ユーザーは C を終了した後に A / B 分割に移動できます。

スタック内にあるアクティビティ A、B、C。アクティビティは上から C、B、A の順に重ねられています。

新しい分割を作成するには、既存のセカンダリ コンテナから新しいアクティビティを横に起動します。A / B 分割と B / C 分割の両方の設定を宣言し、アクティビティ C を通常どおり 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));
    }
}

分割状態の変更に対応する

アプリ内の異なるアクティビティに、同じ機能を果たす UI 要素が存在することがあります(アカウント設定を含むウィンドウを開くコントロールなど)。

図 19. 機能が同じ UI 要素を持つ異なるアクティビティ。

分割の中に共通の UI 要素を持つ 2 つのアクティビティがある場合、その要素を両方のアクティビティに表示することは冗長であり、混乱を招くおそれがあります。

図 20. アクティビティの分割で UI 要素が重複。

アクティビティが分割の中にあることを確認するには、分割状態の変更について SplitController でリスナーを登録します。次に、それに応じて UI を調整します。

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    splitController
        .addSplitListener(this, mainThreadExecutor, SplitInfoChangeCallback())
}

inner class SplitInfoChangeCallback : Consumer<List<SplitInfo>> {
    override fun accept(splitInfoList: List<SplitInfo>) {
        findViewById<View>(R.id.infoButton).visibility =
            if (!splitInfoList.isEmpty()) View.GONE else View.VISIBLE
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    splitController
        .addSplitListener(this, mainThreadExecutor, SplitInfoChangeCallback());
}

class SplitInfoChangeCallback extends Consumer<List<SplitInfo>> {
    public void accept(List<SplitInfo> splitInfoList) {
        findViewById<View>(R.id.infoButton).visibility =
            !splitInfoList.isEmpty()) ? View.GONE : View.VISIBLE;
    }
}

コールバックは、アクティビティが停止している場合を含め、どのようなライフサイクル状態でも発生する可能性があります。リスナーは通常、onStart() で登録し、onStop() で登録を解除します。

フルウィンドウ モーダル

ログイン画面、ポリシーの確認画面、エラー メッセージなど、アクティビティの中には、指定されたアクションが行われるまでユーザーがアプリを操作できないようにするものもあります。モーダル アクティビティは、分割に表示されないようにする必要があります。

Expand 設定を使用することで、アクティビティが常にタスク ウィンドウ全体に表示されるよう強制できます。

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

アクティビティを終了する

ディスプレイの端からスワイプすることで、分割のどちらかの側のアクティビティを終了できます。

図 21. スワイプ操作でアクティビティ B を終了。
図 22. スワイプ操作でアクティビティ A を終了。

デバイスがジェスチャー ナビゲーションではなく [戻る] ボタンを使用するように設定されている場合、入力はフォーカスされているアクティビティ(最後にタップまたは起動されたアクティビティ)に送信されます。

分割のアクティビティを 1 つ終了した場合の結果は、分割の設定によって異なります。

デフォルト設定

分割のアクティビティを 1 つ終了すると、残りのアクティビティがウィンドウ全体を占有します。

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

アクティビティ A とアクティビティ B を含む分割。A が終了すると B がウィンドウ全体を占有します。

アクティビティ A とアクティビティ B を含む分割。B が終了すると A がウィンドウ全体を占有します。

アクティビティをまとめて終了する

セカンダリ アクティビティが終了すると、プライマリ アクティビティが自動的に終了します。

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

アクティビティ A とアクティビティ B を含む分割。B が終了すると A も終了し、タスク ウィンドウは空になります。

アクティビティ A とアクティビティ B を含む分割。A が終了すると B だけがタスク ウィンドウに残ります。

プライマリ アクティビティが終了すると、セカンダリ アクティビティが自動的に終了します。

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

アクティビティ A とアクティビティ B を含む分割。A が終了すると B も終了し、タスク ウィンドウは空になります。

アクティビティ A とアクティビティ B を含む分割。B が終了すると A だけがタスク ウィンドウに残ります。

プライマリまたはセカンダリが終了したときに、アクティビティをまとめて終了します。

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

アクティビティ A とアクティビティ B を含む分割。A が終了すると B も終了し、タスク ウィンドウは空になります。

アクティビティ A とアクティビティ B を含む分割。B が終了すると A も終了し、タスク ウィンドウは空になります。

コンテナの複数のアクティビティを終了する

分割コンテナで複数のアクティビティが重なっている場合、その一番下にあるアクティビティが終了しても、上にあるアクティビティが自動的に終了することはありません。

たとえば、2 つのアクティビティがセカンダリ コンテナにあり、B の上に C があるとします。

アクティビティ B の上に重ねられたアクティビティ C を含むセカンダリ アクティビティ スタックが、アクティビティ A を含むプライマリ アクティビティ スタックの上に重ねられています。

分割の設定はアクティビティ A とアクティビティ B の設定によって定義されます。

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

一番上のアクティビティを終了しても分割は保持されます。

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。C が終了すると、アクティビティ分割に A と B が残ります。

セカンダリ コンテナの一番下(ルート)にあるアクティビティを終了しても、その上にあるアクティビティは削除されません。そのため、分割も保持されます。

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。B が終了すると、アクティビティ分割に A と C が残ります。

プライマリ アクティビティとともにセカンダリ アクティビティを終了するなど、アクティビティをまとめて終了するための追加のルールも実施されます。

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

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。A が終了すると、B と C も終了します。

分割がプライマリとセカンダリをまとめて終了するように設定されている場合は次のようになります。

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

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。C が終了すると、アクティビティ分割に A と B が残ります。

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。B が終了すると、アクティビティ分割に A と C が残ります。

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。A が終了すると、B と C も終了します。

ランタイムに分割プロパティを変更する

現在アクティブな、表示されている分割のプロパティは変更できません。分割ルールの変更は、追加のアクティビティ起動と新しいコンテナに影響しますが、既存のアクティブな分割には影響しません。

アクティブな分割のプロパティを変更するには、横のアクティビティまたは分割の中のアクティビティを終了し、新しい設定でアクティビティを再び横に起動します。

分割からアクティビティを抽出してフルウィンドウにする

横のアクティビティをフルウィンドウ表示する新しい設定を作成してから、同じインスタンスに解決されるインテントでそのアクティビティを再起動します。

ランタイムに分割のサポートを確認する

アクティビティの埋め込みは 12L(API レベル 32)の機能ですが、デバイスによっては、それより前のプラットフォーム バージョンでも利用できます。この機能が利用可能かどうかをランタイムに確認するには、SplitController.isSplitSupported() メソッドを使用します。

Kotlin

val splitController = SplitController.Companion.getInstance()
if (splitController.isSplitSupported()) {
    // Device supports split activity features.
}

Java

SplitController splitController = SplitController.Companion.getInstance();
if (splitController.isSplitSupported()) {
  // Device supports split activity features.
}

分割がサポートされていない場合は、(通常のモデルに従って)アクティビティが一番上に起動されます。

制約、制限、注意事項

  • タスク内の他のアクティビティを整理したり、埋め込んだりできるのは、タスクのホストアプリ(タスクのルート アクティビティのオーナーとして識別)に限られます。埋め込みと分割をサポートするアクティビティが、別のアプリに属するタスクで実行される場合、それらのアクティビティでは埋め込みと分割が機能しません。
  • アクティビティは 1 つのタスク内でのみ整理できます。新しいタスクでアクティビティを起動すると、常に既存の分割の外部で新しく開いたウィンドウに配置されます。
  • 整理して分割に配置できるのは、同じプロセスのアクティビティに限られます。異なるプロセスのアクティビティを把握する方法がないため、SplitInfo コールバックは同じプロセスに属するアクティビティのみをレポートします。
  • 各ペアまたは単一のアクティビティ ルールは、ルールが登録された後に発生するアクティビティ起動にしか適用されません。現在のところ、既存の分割やその視覚的なプロパティを更新する方法はありません。
  • 分割ペアのフィルタ設定は、アクティビティを起動するときに使用するインテントと完全に一致する必要があります。この照合はアプリプロセスから新しいアクティビティが起動された時点で起こるため、暗黙的インテントを使用する場合、システム プロセスの後半で解決されるコンポーネント名が不明となることがあります。起動時にコンポーネント名が不明な場合は、代わりにワイルドカード(「*/*」)を使用し、インテントのアクションに基づいてフィルタリングを実施できます。
  • 現在のところ、コンテナ間でアクティビティを移動する方法や、分割の作成後にその内外にアクティビティを移動する方法はありません。分割は、ルールが一致する新しいアクティビティが起動したときにのみ、WindowManager ライブラリによって作成され、分割コンテナ内の最後のアクティビティが終了したときに破棄されます。
  • アクティビティは構成変更に伴って再起動される場合があるため、分割が作成または削除されてアクティビティの境界が変化すると、それまでのインスタンスが完全に破棄され新たなインスタンスが再作成される可能性があります。そのため、アプリ デベロッパーは、ライフサイクル コールバックから新しいアクティビティを起動するなどの処理に注意する必要があります。

参考情報