タスクとバックスタック

タスクは、ユーザーがアプリで何かを実行する際に操作するアクティビティの集まりです。これらのアクティビティは、バックスタックと呼ばれるスタック内に、各アクティビティが開かれた順序で配置されます。

たとえば、メールアプリに新しいメッセージのリストを表示するアクティビティが 1 つあるとします。ユーザーがメッセージを選択すると、新しいアクティビティが開き、そのメッセージが表示されます。この新しいアクティビティはバックスタックに追加されます。その後、ユーザーが「戻る」をタップするかジェスチャーを行うと、その新しいアクティビティが終了し、スタックからポップされます。

タスクのライフサイクルとそのバックスタック

デバイスのホーム画面は、ほとんどのタスクが開始される場所です。ユーザーがアプリ ランチャーまたはホーム画面でアプリまたはショートカットのアイコンをタップすると、そのアプリのタスクがフォアグラウンドに移動します。アプリのタスクが存在しない場合は、新しいタスクが作成され、そのアプリのメイン アクティビティがスタック内のルート アクティビティとして開きます。

現在のアクティビティが別のアクティビティを開始すると、新しいアクティビティがスタックの一番上にプッシュされ、フォーカスされます。以前のアクティビティはスタックに残りますが、停止します。アクティビティが停止すると、システムはユーザー インターフェースの現在の状態を保持します。ユーザーが「戻る」アクションを実行すると、現在のアクティビティがスタックの一番上からポップされ、破棄されます。前のアクティビティが再開され、UI の以前の状態が復元されます。

スタック内のアクティビティは再配置されません。現在のアクティビティによって開始され、ユーザーが [戻る] ボタンまたは操作で閉じたときにのみ、スタックにプッシュおよびポップされます。したがって、バックスタックは後入れ先出しのオブジェクト構造として動作します。図 1 は、バックスタックに対してアクティビティが push またはポップされるタイムラインを示しています。

図 1. タスク内の新しいアクティビティがそれぞれどのようにしてバックスタックにアイテムを追加しているかを表しています。ユーザーがタップするかジェスチャーで戻ると、現在のアクティビティは破棄され、前のアクティビティが再開されます。

ユーザーがタップや「戻る」ジェスチャーを続けると、ユーザーがホーム画面またはタスク開始時に実行されていたアクティビティに戻るまで、スタック内の各アクティビティがポップオフされ、以前のアクティビティが表示されます。すべてのアクティビティがスタックから削除されると、タスクは存在しなくなります。

ルート ランチャー アクティビティのバックタップの動作

ルート ランチャー アクティビティは、ACTION_MAINCATEGORY_LAUNCHER の両方でインテント フィルタを宣言するアクティビティです。これらのアクティビティは、アプリ ランチャーからアプリへのエントリ ポイントとして機能し、タスクの開始に使用することから一意です。

ユーザーがルート ランチャー アクティビティからタップするかジェスチャーで戻ると、デバイスが実行している Android のバージョンに応じて、システムはイベントの処理方法が異なります。

Android 11 以前のシステム動作
システムがアクティビティを終了します。
Android 12 以降のシステム動作

システムはアクティビティを完了するのではなく、アクティビティとそのタスクをバックグラウンドに移動します。この動作は、ホームボタンまたはホーム操作を使用してアプリから移動する際のデフォルトのシステム動作と一致します。

ほとんどの場合、この動作により、ユーザーはコールド状態からアプリを完全に再起動する代わりに、ウォーム状態から迅速にアプリを再開できます。

カスタムの「戻る」ナビゲーションを提供する必要がある場合は、onBackPressed() をオーバーライドするのではなく、AndroidX Activity API を使用することをおすすめします。AndroidX Activity API は、システムの「戻る」タップをインターセプトするコンポーネントがない場合、自動的に適切なシステム動作に従います。

ただし、アプリが onBackPressed() をオーバーライドして「戻る」ナビゲーションを処理し、アクティビティを終了する場合は、終了ではなく super.onBackPressed() を呼び出すように実装を更新してください。super.onBackPressed() を呼び出すと、必要に応じてアクティビティとそのタスクがバックグラウンドに移動し、アプリ間で一貫したナビゲーション エクスペリエンスを提供できます。

バックグラウンド タスクとフォアグラウンド タスク

図 2. 2 つのタスク: タスク B はフォアグラウンドでユーザー操作を受け取り、タスク A はバックグラウンドで再開を待機します。

タスクは、ユーザーが新しいタスクを開始するかホーム画面に移動したときにバックグラウンドに移動できる、まとまった単位です。バックグラウンドで実行されている間、タスク内のすべてのアクティビティは停止されますが、タスクのバックスタックはそのままです。図 2 に示すように、別のタスクが行われる間、タスクはフォーカスを失います。タスクはフォアグラウンドに戻り、ユーザーは中断したところから再開できます。

次のタスクフローについて考えてみましょう。現在のタスク A には、スタックに 3 つのアクティビティがあり、現在のアクティビティの下には 2 つのアクティビティがあります。

  1. ユーザーはホームボタンまたはジェスチャーを使用して、アプリ ランチャーから新しいアプリを起動します。

    ホーム画面が表示されると、タスク A はバックグラウンドに移動します。新しいアプリが起動すると、システムはそのアプリのタスク(タスク B)を独自のアクティビティ スタックで開始します。

  2. そのアプリを操作した後、ユーザーは再びホームに戻り、最初にタスク A を開始したアプリを選択します。

    これで、タスク A がフォアグラウンドに移動します。スタック内の 3 つのアクティビティはすべてそのままで、スタックの一番上のアクティビティが再開されます。この時点で、ユーザーはホームに移動してタスクを開始したアプリアイコンを選択するか、履歴画面からアプリのタスクを選択して、タスク B に戻ることができます。

複数のアクティビティ インスタンス

図 3. 1 つのアクティビティを複数回インスタンス化できます。

バックスタック内のアクティビティは再配置されないため、ユーザーが複数のアクティビティから特定のアクティビティを開始できるアプリの場合、そのアクティビティの以前のインスタンスを一番上に移動するのではなく、そのアクティビティの新しいインスタンスが作成されてスタックにプッシュされます。そのため、図 3 に示すように、アプリ内の 1 つのアクティビティが、異なるタスクからでも複数回インスタンス化される場合があります。

ユーザーが [戻る] ボタンまたはジェスチャーを使用して戻ると、アクティビティのインスタンスが開いた順序で表示され、それぞれが固有の UI 状態を持ちます。ただし、アクティビティを複数回インスタンス化したくない場合は、この動作を変更できます。詳しくは、タスクの管理のセクションをご覧ください。

マルチウィンドウ環境

Android 7.0(API レベル 24)以降でサポートされているマルチウィンドウ環境でアプリを同時に実行する場合、システムはウィンドウごとにタスクを個別に管理します。各ウィンドウには複数のタスクを含めることができます。Chromebook で実行される Android アプリについても同様です。システムは、ウィンドウごとにタスク(またはタスクのグループ)を管理します。

ライフサイクルのまとめ

以下は、アクティビティとタスクのデフォルトの動作をまとめたものです。

  • アクティビティ A がアクティビティ B を開始すると、アクティビティ A は停止しますが、システムはその状態(スクロール位置やフォームに入力されたテキストなど)を保持します。アクティビティ B でユーザーが戻るジェスチャーをタップまたは使用すると、アクティビティ A の状態が復元された状態で再開します。

  • ユーザーがホームボタンまたは操作を使用してタスクから離れると、現在のアクティビティは停止し、そのタスクはバックグラウンドに移動します。システムは、タスク内のすべてのアクティビティの状態を保持します。タスクを開始したユーザーがランチャー アイコンを選択してタスクを再開すると、タスクはフォアグラウンドに移動し、スタックの一番上でアクティビティを再開します。

  • ユーザーがタップするかジェスチャーで戻ると、現在のアクティビティはスタックからポップされ、破棄されます。スタック内の以前のアクティビティが再開されます。アクティビティが破棄されても、システムはアクティビティの状態を保持しません

    Android 12 以降を搭載したデバイスでアプリを実行している場合、ルート ランチャー アクティビティではこの動作が異なります

  • アクティビティは、別のタスクからでも複数回インスタンス化できます。

用事を管理する

Android は、タスクとバックスタックを、連続して開始したすべてのアクティビティを同じタスクに配置し、最後のインファースト アウトスタックに配置します。これはほとんどのアプリでうまく機能し、通常は、アクティビティがタスクにどのように関連付けられているか、またはバックスタック内でどのように存在するかについて気にする必要はありません。

ただし、通常の動作を中断することもできます。たとえば、アプリ内のアクティビティを現在のタスク内に配置するのではなく、起動時に新しいタスクを開始するように設定できます。また、アクティビティを開始する際に、バックスタックの上に新しいインスタンスを作成するのではなく、アクティビティの既存のインスタンスを転送することもできます。ユーザーがタスクを離れたときに、ルート アクティビティを除くすべてのアクティビティからバックスタックを消去することもできます。

<activity> マニフェスト要素の属性と、startActivity() に渡すインテントのフラグを使用して、このような処理などを行うことができます。

タスクの管理に使用できる主要な <activity> 属性は次のとおりです。

使用できるプリンシパル インテント フラグは次のとおりです。

以降のセクションでは、これらのマニフェスト属性とインテント フラグを使用して、アクティビティとタスクの関連付けと、バックスタックでの動作を定義する方法について説明します。

また、履歴画面でタスクとアクティビティを表示して管理する方法についての考慮事項についても説明します。通常は、履歴画面でのタスクとアクティビティの表示方法をシステムに定義させるため、この動作を変更する必要はありません。詳細については、履歴画面をご覧ください。

起動モードを定義する

起動モードを使用すると、アクティビティの新しいインスタンスを現在のタスクに関連付ける方法を定義できます。起動モードは、この後のセクションで説明するように 2 つの方法で定義できます。

したがって、アクティビティ A がアクティビティ B を起動した場合、アクティビティ B はマニフェストで現在のタスクに関連付ける方法を定義し、アクティビティ A はインテント フラグを使用してアクティビティ B を現在のタスクに関連付ける方法をリクエストできます。

両方のアクティビティがアクティビティ B をタスクに関連付ける方法を定義している場合、インテント内で定義されているアクティビティ A のリクエストが、マニフェスト内で定義されているように、アクティビティ B のリクエストよりも優先されます。

マニフェスト ファイルを使用して起動モードを定義する

マニフェスト ファイルでアクティビティを宣言する際に、<activity> 要素の launchMode 属性を使用してアクティビティをタスクに関連付ける方法を指定できます。

launchMode 属性に割り当てることができる起動モードは 5 つあります。

  1. "standard"
    デフォルトのモード。システムは、開始されたタスク内にアクティビティの新しいインスタンスを作成し、そのインスタンスにインテントを渡します。アクティビティは複数回インスタンス化でき、各インスタンスは異なるタスクに属することができ、1 つのタスクは複数のインスタンスを持つことができます。
  2. "singleTop"
    アクティビティのインスタンスが現在のタスクの一番上にすでに存在する場合、システムはアクティビティの新しいインスタンスを作成するのではなく、onNewIntent() メソッドを呼び出して、インテントをそのインスタンスに渡します。アクティビティは複数回インスタンス化され、各インスタンスは異なるタスクに属することができます。また、1 つのタスクが複数のインスタンスを持つこともできます(ただし、バックスタックの一番上にあるアクティビティがアクティビティの既存のインスタンスではない場合に限られます)。

    たとえば、タスクのバックスタックが、アクティビティ B、C、D が一番上にあるルート アクティビティ A で構成されているとします(この場合、スタックは A-B-C-D で、D が一番上です)。インテントがタイプ D のアクティビティで到着します。D の起動モードがデフォルトの "standard" の場合、クラスの新しいインスタンスが起動され、スタックは A-B-C-D-D になります。ただし、D の起動モードが "singleTop" の場合、D の既存のインスタンスは onNewIntent() を介してインテントを受け取ります。これはスタックの一番上にあり、スタックは A-B-C-D のままであるためです。一方、タイプ B のアクティビティに対してインテントが到着した場合は、起動モードが "singleTop" であっても、B の新しいインスタンスがスタックに追加されます。

  3. "singleTask"
    システムは、新しいタスクのルートにアクティビティを作成するか、同じアフィニティを持つ既存のタスクにアクティビティを配置します。アクティビティのインスタンスがすでに存在する場合、システムは新しいインスタンスを作成せずに、onNewIntent() メソッドを呼び出して、インテントを既存のインスタンスに渡します。その間、その上にある他のアクティビティはすべて破棄されます。
  4. "singleInstance"
    動作は "singleTask" の場合と同じですが、インスタンスを保持しているタスクで他のアクティビティを起動しません。アクティビティは常に、そのタスクの唯一のメンバーです。このアクティビティによって開始されたアクティビティは、別のタスクで開きます。
  5. "singleInstancePerTask"
    アクティビティは、タスクのルート アクティビティ(タスクを最初に作成したアクティビティ)としてしか実行できないため、このアクティビティのインスタンスは、タスク内に 1 つしか存在できません。singleTask 起動モードとは対照的に、FLAG_ACTIVITY_MULTIPLE_TASK フラグまたは FLAG_ACTIVITY_NEW_DOCUMENT フラグが設定されている場合、このアクティビティは異なるタスクの複数のインスタンスで起動できます。

別の例として、Android ブラウザアプリは、<activity> 要素で singleTask 起動モードを指定することにより、ウェブブラウザ アクティビティを常に独自のタスクで開くことを宣言しています。つまり、アプリが Android ブラウザを開くインテントを発行しても、そのアクティビティはアプリと同じタスクに配置されません。代わりに、ブラウザで新しいタスクが開始されるか、ブラウザにすでにバックグラウンドで実行されているタスクがある場合は、新しいインテントを処理するためにそのタスクが転送されます。

アクティビティが新しいタスクで開始されるか、アクティビティを開始したアクティビティと同じタスクで開始されるかにかかわらず、[戻る] ボタンと操作で常に前のアクティビティに戻ります。ただし、singleTask 起動モードを指定するアクティビティを起動し、そのアクティビティのインスタンスがバックグラウンド タスクに存在する場合は、そのタスク全体がフォアグラウンドに移動します。この時点で、バックスタックにはスタックの一番上に移動されたタスクのすべてのアクティビティが含まれます。図 4 に、このタイプのシナリオを示します。

図 4. 起動モードが "singleTask" のアクティビティがどのようにバックスタックに追加されるかを示しています。アクティビティがすでに独自のバックスタックを持つバックグラウンド タスクの一部である場合、そのバックスタック全体も現在のタスクの上に移動されます。

マニフェスト ファイルで起動モードを使用する方法については、<activity> 要素のドキュメントをご覧ください。

インテント フラグを使用して起動モードを定義する

アクティビティの開始時に、startActivity() に配信するインテントにフラグを含めることで、アクティビティとタスクのデフォルトの関連付けを変更できます。デフォルトの動作を変更するために使用できるフラグは次のとおりです。

FLAG_ACTIVITY_NEW_TASK

システムは新しいタスクでアクティビティを開始します。開始中のアクティビティに対してタスクがすでに実行されている場合、そのタスクは最後の状態が復元された状態でフォアグラウンドに移動し、アクティビティは onNewIntent() で新しいインテントを受け取ります。

これにより、前のセクションで説明した "singleTask"launchMode 値と同じ動作になります。

FLAG_ACTIVITY_SINGLE_TOP

開始されるアクティビティが現在のアクティビティの場合、バックスタックの一番上にある既存のインスタンスは、アクティビティの新しいインスタンスを作成する代わりに、onNewIntent() に対する呼び出しを受け取ります。

これにより、前のセクションで説明した "singleTop"launchMode 値と同じ動作になります。

FLAG_ACTIVITY_CLEAR_TOP

開始されるアクティビティがすでに現在のタスクで実行されている場合は、そのアクティビティの新しいインスタンスを起動するのではなく、その上にある他のアクティビティがすべて破棄されます。インテントは、onNewIntent() を介して、アクティビティの再開されたインスタンス(現在は一番上)に配信されます。

launchMode 属性には、この動作をもたらす値はありません。

FLAG_ACTIVITY_CLEAR_TOP はほとんどの場合、FLAG_ACTIVITY_NEW_TASK と組み合わせて使用します。これらのフラグを組み合わせて使用すると、別のタスク内の既存のアクティビティを見つけ、インテントに応答できる位置に配置できます。

アフィニティを処理する

アフィニティは、アクティビティが「優先」するタスクを示します。デフォルトでは、同じアプリのすべてのアクティビティは互いにアフィニティを持ちます。つまり、同じタスク内にあることが「優先」されます。

ただし、アクティビティのデフォルト アフィニティは変更できます。異なるアプリで定義されたアクティビティはアフィニティを共有できます。また、同じアプリで定義されたアクティビティに異なるタスク アフィニティを割り当てることができます。

アクティビティのアフィニティを変更するには、<activity> 要素の taskAffinity 属性を使用します。

taskAffinity 属性は、<manifest> 要素で宣言されたデフォルトのパッケージ名とは異なる文字列値を受け取ります。システムがその名前を使用してアプリのデフォルトのタスク アフィニティを識別するためです。

アフィニティは、以下の 2 つの状況で役立ちます。

  1. アクティビティを起動するインテントに FLAG_ACTIVITY_NEW_TASK フラグが含まれている場合。

    デフォルトでは、新しいアクティビティは startActivity() を呼び出したアクティビティのタスクで起動されます。それが呼び出し元と同じバックスタックにプッシュされます。

    ただし、startActivity() に渡されたインテントに FLAG_ACTIVITY_NEW_TASK フラグが含まれている場合、システムは新しいアクティビティを格納する別のタスクを探します。多くの場合、これは新しいタスクです。ただし、そうする必要はありません。新しいアクティビティと同じアフィニティを持つ既存のタスクがある場合、アクティビティはそのタスクで起動します。存在しない場合は、新しいタスクを開始します。

    このフラグによってアクティビティが新しいタスクを開始し、ユーザーがホームボタンまたはジェスチャーを使用してタスクから離れる場合、ユーザーがタスクに戻るなんらかの方法が必要です。通知マネージャーなどの一部のエンティティは、常に外部タスクでアクティビティを開始しますが、そのエンティティ自体の一部としては起動しないため、startActivity() に渡すインテントには常に FLAG_ACTIVITY_NEW_TASK が入ります。

    このフラグを使用する可能性のある外部エンティティがアクティビティを呼び出せる場合は、開始されたタスクに戻るための手段をユーザーに提供する必要があります。たとえば、タスクのルート アクティビティに CATEGORY_LAUNCHER インテント フィルタがあるランチャー アイコンなどを使用します。詳細については、タスクの開始をご覧ください。

  2. アクティビティの allowTaskReparenting 属性が "true" に設定されている場合。

    その場合、タスクは、開始したタスクからアフィニティを持つタスクに、そのタスクがフォアグラウンドに移動したときに移動できます。

    たとえば、選択した都市の気象状況を報告するアクティビティが、旅行アプリの一部として定義されているとします。このアプリには、同じアプリの他のアクティビティと同じアフィニティ(デフォルトのアプリ アフィニティ)があり、この属性で親を変更できます。

    アクティビティの 1 つが天気予報アクティビティを開始すると、最初はそのアクティビティと同じタスクに属します。ただし、旅行アプリのタスクがフォアグラウンドに移動すると、天気予報アクティビティはそのタスクに再割り当てされ、タスク内に表示されます。

バックスタックをクリアする

ユーザーがタスクを長時間離れると、システムはルート アクティビティを除くすべてのアクティビティからタスクを消去します。ユーザーがタスクに戻ると、ルート アクティビティのみが復元されます。システムは、長時間の後、ユーザーが以前の作業を放棄し、新しいことを始めるためにタスクに戻っているという前提に基づいて、このような動作を行います。

この動作を変更するために使用できるアクティビティ属性がいくつかあります。

alwaysRetainTaskState
タスクのルート アクティビティでこの属性を "true" に設定した場合、ここで説明したデフォルトの動作は行われません。タスクは、期間が長い場合でも、スタック内のすべてのアクティビティを保持します。
clearTaskOnLaunch

タスクのルート アクティビティでこの属性が "true" に設定されている場合、ユーザーがタスクから離れて戻るたびに、タスクはルート アクティビティまでクリアされます。つまり、alwaysRetainTaskState の逆です。ユーザーは常に初期状態でタスクから戻ります。タスクを離れた時間が短い場合でも、そのタスクに戻ります。

finishOnTaskLaunch

この属性は clearTaskOnLaunch に似ていますが、タスク全体ではなく 1 つのアクティビティに作用します。ルート アクティビティ以外のアクティビティが終了してしまうこともあります。"true" に設定すると、アクティビティは現在のセッションの間のみ、タスクの一部のままとなります。ユーザーがタスクから離れて戻ると、そのタスクは存在しません。

タスクを開始する

アクティビティをタスクのエントリ ポイントとして設定するには、指定されたアクションとして "android.intent.action.MAIN"、指定されたカテゴリとして "android.intent.category.LAUNCHER" を持つインテント フィルタを設定します。

<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

この種のインテント フィルタを使用すると、アクティビティのアイコンとラベルがアプリ ランチャーに表示されます。これにより、ユーザーはアクティビティを起動したり、起動後に作成したタスクに戻ったりすることができます。

この 2 つ目の能力は重要です。ユーザーは、このアクティビティ ランチャーを使用して、タスクを離れても、後でそのタスクに戻ることができる必要があります。このため、アクティビティに ACTION_MAINCATEGORY_LAUNCHER のフィルタがある場合、アクティビティを常にタスクを開始するようにマークする 2 つの起動モード("singleTask""singleInstance")のみを使用してください。

たとえば、フィルタがない場合にどうなるか、想像してみてください。インテントが "singleTask" アクティビティを起動して新しいタスクを開始し、ユーザーがそのタスクに時間を費やすなどです。その後、ユーザーはホームボタンまたはジェスチャーを使用します。すると、タスクはバックグラウンドに移動し、非表示になります。タスクはアプリ ランチャーに表示されないため、ユーザーはタスクに戻ることができません。

ユーザーがアクティビティに戻れないようにするには、<activity> 要素の finishOnTaskLaunch"true" に設定します。詳しくは、バックスタックをクリアするをご覧ください。

履歴画面でタスクとアクティビティをどのように表示、管理するかについては、履歴画面をご覧ください。

その他のリソース