タスクとバックスタック

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

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

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

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

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

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

図 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 つのアクティビティを複数回インスタンス化できます。

バックスタック内のアクティビティは再配置されないため、ユーザーがアプリで複数のアクティビティから特定のアクティビティを開始できるようにする場合、そのアクティビティの以前のインスタンスを一番上に移動するのではなく、そのアクティビティの新しいインスタンスが作成され、スタックに push されます。そのため、図 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 つのタスクは複数のインスタンスを持つことができます(ただし、バックスタックの一番上にあるアクティビティがアクティビティの既存のインスタンスではない場合のみ)。

    たとえば、タスクのバックスタックがルート アクティビティ A で構成され、アクティビティ B、C、D が一番上にあるとします(したがって、スタックは A-B-C-D、D が一番上)。インテントはタイプ D のアクティビティに対して到着します。D の起動モードがデフォルトの "standard" の場合、クラスの新しいインスタンスが起動され、スタックは A-B-C-D-D になります。ただし、D の起動モードが "singleTop" の場合、D の既存のインスタンスは、スタックの一番上にあり、A-B-C-D のままになるため、onNewIntent() を通じてインテントを受け取ります。一方、タイプ 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() を呼び出したアクティビティのタスクで起動されます。呼び出し元と同じバックスタックに push されます。

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

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

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

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

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

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

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

バックスタックを消去する

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

この動作を変更するには、いくつかのアクティビティ属性を使用できます。

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_MAIN フィルタと CATEGORY_LAUNCHER フィルタがある場合、アクティビティを常にタスクを開始するようにマークする "singleTask""singleInstance" の 2 つの起動モードのみを使用してください。

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

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

履歴画面でのタスクとアクティビティの表示方法と管理方法について詳しくは、履歴画面をご覧ください。

その他のリソース