タスクとバックスタックについて

タスクは、ユーザーが特定のジョブを実行する際に操作するアクティビティのコレクションです。アクティビティは、スタック(バックスタック)に、各アクティビティが開かれた順に配列されます。たとえば、メールアプリで新しいメッセージのリストを表示するアクティビティが 1 つあるとします。ユーザーがメッセージを選択すると、新しいアクティビティが開いてそのメッセージが表示されます。この新しいアクティビティはバックスタックに追加されます。ユーザーが戻るボタンを押すと、新しいアクティビティは終了し、スタックから消えます。次の動画では、バックスタックの仕組みについて簡単に説明します。

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

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

現在のアクティビティが別のアクティビティを開始すると、新しいアクティビティがスタックの一番上にプッシュされ、フォーカスを取得します。前のアクティビティはスタックに残りますが、停止します。アクティビティが停止すると、システムはそのユーザー インターフェースの現在の状態を保持します。ユーザーが戻るボタンを押すと、現在のアクティビティがスタックの一番上から消え(アクティビティが破棄され)、以前のアクティビティが再開(UI の以前の状態が復元)されます。スタック内のアクティビティは並べ替えられることはなく、スタックにプッシュされるか、スタックから削除されるのみです。アクティビティは、現在のアクティビティによって開始されるとスタックにプッシュされ、ユーザーが戻るボタンを使用して離れるとスタックから消去されます。そのため、バックスタックは「後入れ先出し」のオブジェクト構造として動作します。図 1 はこの動作を図示したもので、アクティビティ間の進捗を表すタイムラインと各時点におけるバックスタックを示しています。

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

ユーザーが戻るボタンを押し続けると、ユーザーがホーム画面(または、タスクが開始されたときに実行中だったアクティビティ)に戻るまで、スタック内の各アクティビティが消えていき、前のアクティビティに遡っていきます。すべてのアクティビティがスタックから削除されると、タスクは存在しなくなります。

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

タスクは、ユーザーが新しいタスクを開始したとき、またはホームボタンを使用してホーム画面に移動したときに、「バックグラウンド」に移動できるまとまったユニットです。図 2 に示すとおり、バックグラウンドでは、タスク内のすべてのアクティビティは停止されますが、タスクのバックスタックはそのまま残されます。つまり、他のタスクに入れ替わる場合も、このタスクへのフォーカスが失われるだけです。その後、タスクは「フォアグラウンド」に戻ることができ、ユーザーは中断した場所から再開できます。たとえば、現在のタスク(タスク A)のスタックに、3 つのアクティビティがあり、そのうち 2 つが現在のアクティビティにあるとします。ユーザーはホームボタンを押して、アプリ ランチャーから新しいアプリを起動します。ホーム画面が表示されたら、タスク A がバックグラウンドに移動します。新しいアプリが起動すると、システムはそのアプリのタスク(タスク B)を、それ自身のアクティビティのスタックを使用して開始します。ユーザーはこのアプリを操作した後、再びホーム画面に戻り、最初にタスク A を開始したアプリを選択します。すると、タスク A がフォアグラウンドに移動します。スタック内の 3 つのアクティビティはそのまま残り、スタックの一番上のアクティビティが再開します。この時点で、ユーザーはホーム画面に移動して、タスク B を開始したアプリのアイコンを選択する(または、オーバービュー画面からそのアプリのタスクを選択する)ことで、タスク B に切り替えることもできます。これは、Android でのマルチタスクの例です。

注: 複数のタスクを同時にバックグラウンドで保持できます。ただし、ユーザーが多くのバックグラウンド タスクを同時に実行している場合、システムがメモリを回復するためにバックグラウンド アクティビティの破棄を開始し、アクティビティの状態が失われる場合があります。

図 3. 1 つのアクティビティが複数回インスタンス化されています。

バックスタック内のアクティビティが並べ替えられることはないため、ユーザーが特定のアクティビティを複数のアクティビティから開始することをアプリで許可している場合、(以前のアクティビティのいずれかのインスタンスを一番上に移動させるのではなく)そのアクティビティの新しいインスタンスが作成され、スタックにプッシュされます。そのため、アプリ内の 1 つのアクティビティは、図 3 に示すように、複数回(別のタスクからも)インスタンス化される場合があります。そのため、ユーザーが戻るボタンを使用して戻ると、アクティビティの各インスタンスが、(UI の状態はそれぞれそのままで)開いた順に表示されます。ただし、アクティビティを複数回インスタンス化したくない場合は、この動作を変更できます。その方法については、後述のセクションタスクを管理するで説明します。

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

  • アクティビティ A がアクティビティ B を開始すると、アクティビティ A は停止しますが、システムはその状態(スクロールの位置やフォームに入力されたテキストなど)を保持します。アクティビティ B が開いた状態でユーザーが戻るボタンを押すと、アクティビティ A が再開し、その状態が復元されます。
  • ユーザーがホームボタンを押してタスクを離れると、現在のアクティビティは停止し、そのタスクはバックグラウンドに移動します。システムは、タスク内のすべてのアクティビティの状態を保持します。その後、ユーザーがタスクを開始したランチャー アイコンを選択してタスクを再開すると、タスクはフォアグラウンドに移動し、スタックの一番上にあるアクティビティを再開します。
  • ユーザーが戻るボタンを押すと、現在のアクティビティはスタックから消え、破棄されます。スタック内にある以前のアクティビティが再開されます。破棄されたアクティビティの状態は保持されません
  • アクティビティは、別のタスクからでも複数回インスタンス化できます。

ナビゲーション デザイン

Android におけるアプリのナビゲーションの仕組みについて、詳しくは Android デザインのナビゲーション ガイドをご覧ください。

タスクを管理する

上記のように、Android は連続して開始したすべてのアクティビティを同じタスクの「後入れ先出し」式スタックに置くことによって、タスクとバックスタックを管理します。この方法はほとんどのアプリで正しく機能するため、アクティビティがどのようにタスクに関連付けられ、どのようにバックスタックに置かれているのかを心配する必要はありません。ただし、通常の動作に割り込むことが必要な場合もあるでしょう。アプリ内のアクティビティが開始されたときに(現在のタスク内に置かれる代わりに)新しいタスクを開始するようにする、またはアクティビティを開始したときに(新しいインスタンスをバックスタックの一番上に作成する代わりに)アクティビティの既存のインスタンスを前に持ってくる、あるいは、ユーザーがタスクを離れるときに、ルート アクティビティ以外のすべてのアクティビティをバックスタックからクリアすることが必要な場合も考えられます。

<activity> マニフェスト要素の属性と、startActivity() に渡すインテント内のフラグを使用して、これらの操作やその他の操作を行うことができます。

この件に関して、使用できる主な <activity> 属性には以下のものがあります。

使用できる主なインテント フラグには以下のものがあります。

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

オーバービュー画面でのタスクとアクティビティの提示方法と管理方法に関する注意事項についても別途記載しています。詳しくは、オーバービュー画面をご覧ください。なお、オーバービュー画面にタスクとアクティビティを提示する方法については、通常はシステムによる定義を許可し、動作を変更する必要はありません。

注意: ほとんどのアプリについて、アクティビティとタスクのデフォルト動作への割り込みをしないようにする必要があります。アクティビティがデフォルト動作を変更する必要があると判断した場合は、アクティビティのユーザビリティについて、特に起動時と戻るボタンをクリックして他のアクティビティやタスクから戻った場合のユーザビリティを慎重にテストする必要があります。ユーザーが想定する動作と競合するおそれのあるナビゲーション時の動作については、必ずテストを行ってください。

起動モードを定義する

起動モードでは、アクティビティの新しいインスタンスを現在のタスクに関連付ける方法を定義できます。次の 2 つの方法で、さまざまな起動モードを定義できます。

そのため、アクティビティ A がアクティビティ B を開始する場合、アクティビティ B は自身を現在のタスクと関連付ける方法(もしもあれば)をマニフェストで定義でき、アクティビティ A もアクティビティ B を現在のタスクと関連付ける方法を要求できます。両方のアクティビティがアクティビティ B をタスクに関連付ける方法を定義している場合は、アクティビティ A の要求(インテントで定義される)がアクティビティ B の要求(マニフェストで定義される)よりも優先されます。

注: マニフェスト ファイルで使用できる起動モードの中には、インテントのフラグとして使用できないものがあります。同様に、インテントのフラグとして使用できる起動モードの中には、マニフェストで定義できないものもあります。

マニフェスト ファイルを使用する

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

launchMode 属性は、アクティビティをタスク内で起動する方法についての指示を定めます。launchMode 属性には、次の 4 つの起動モードを割り当てることができます。

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

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

注: アクティビティの新しいインスタンスが作成されると、ユーザーは戻るボタンを押して前のアクティビティに戻ることができます。しかし、アクティビティの既存のインスタンスが新しいインテントに対応すると、ユーザーは新しいインテントが onNewIntent() で届く前のアクティビティの状態に戻るボタンを押して戻ることができません。

"singleTask"
システムは新しいタスクを作成し、新しいタスクのルートにアクティビティをインスタンス化します。しかし、アクティビティのインスタンスが別のタスクにすでに存在する場合は、システムは新しいインスタンスを作成せずに、onNewIntent() メソッドを呼び出して、インテントを既存のインスタンスに渡します。同時に存在できるアクティビティのインスタンスは、1 つのみです。

注: アクティビティは新しいタスク内で開始されますが、ユーザーは戻るボタンを押して前のアクティビティに戻ることができます。

"singleInstance".
システムが、インスタンスを保持しているタスクで他のアクティビティを起動しないことを除いて "singleTask" と同じです。アクティビティは、常にタスクの唯一の構成要素となります。このアクティビティによって開始されたアクティビティは別のタスクで開きます。

別の例として、Android Browser アプリは、<activity> 要素の singleTask 起動モードを指定することによって、ウェブブラウザ アクティビティを常に自身のタスクで開くことを宣言しています。これは、アプリが Android ブラウザを開くインテントを発行すると、そのアクティビティはアプリと同じタスクには配置されないことを意味します。代わりに、Android Browser の新しいタスクが開始されるか、Android Browser にすでに実行中のバックグラウンド タスクがある場合は、そのタスクがフォアグラウンドに移動され、新しいインテントに対応します。

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

図 4. 「singleTask」起動モードを持つアクティビティをバックスタックに追加する方法を示しています。アクティビティがすでに自身のバックスタックを持つバックグラウンド タスクの一部である場合は、バックスタック全体がフォアグラウンドに移動し、現在のタスクの上に置かれます。

マニフェスト ファイルで起動モードを使用する方法について詳しくは、<activity> 要素のドキュメントをご覧ください。launchMode 属性と利用できる値について詳しく説明しています。

注: 次のセクションで説明しますが、launchMode 属性を使用してアクティビティに対して指定する動作は、アクティビティを開始するインテントに含まれるフラグによって上書きされる場合があります。

インテント フラグを使用する

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

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

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

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

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

FLAG_ACTIVITY_CLEAR_TOP
開始されるアクティビティが、すでに現在のタスクで実行されている場合は、そのアクティビティの新しいインスタンスを起動する代わりに、上に置かれた他のアクティビティをすべて破棄し、一番上に移動したアクティビティの再開されたインスタンスに、onNewIntent() を介してこのインテントを渡します。

この動作を起こす launchMode 属性には値がありません。

ほとんどのケースで FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_NEW_TASK と併用されます。同時に使用することで、これらのフラグは、別のタスクにある既存のアクティビティを捜し出し、インテントに対応できる位置に置く手段として活用できます。

注: 指定されたアクティビティの起動モードが "standard" である場合は、やはりスタックから削除されます。渡されるインテントに対応する新しいインスタンスがその場所で開始されます。これは、起動モードが "standard" である場合は、新しいインテントに対して常に新しいインスタンスが作成されるためです。

アフィニティを処理する

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

<activity> 要素の taskAffinity 属性を使用して、任意のアクティビティのアフィニティを変更できます。

taskAffinity 属性には、文字列値を指定します。この値は、<manifest> 要素で宣言されたデフォルトのパッケージ名とは異なる一意のものであることが必要です。これは、システムがアプリに対するデフォルトのタスク アフィニティを識別する目的でその名前を使用するためです。

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

  • アクティビティを開始するインテントが FLAG_ACTIVITY_NEW_TASK フラグを含んでいる場合

    デフォルトでは、新しいアクティビティは startActivity() を呼び出したアクティビティのタスクで開始され、呼び出し元と同じバックスタックにプッシュされます。しかし、startActivity() に渡されたインテントに FLAG_ACTIVITY_NEW_TASK フラグが含まれている場合、システムは新しいアクティビティを収容する別のタスクを捜します。通常、これは新しいタスクですが、必ずしも新しいタスクである必要はありません。新しいアクティビティとして、同じアフィニティを持つ既存のタスクがすでに存在する場合は、アクティビティはそのタスクで開始されます。存在しない場合は、新しいタスクを開始します。

    このフラグによってアクティビティが新しいタスクを開始し、ユーザーがホームボタンを押してタスクを離れる場合は、ユーザーがそのタスクに戻るための手段が必要です。エンティティの中には(通知マネージャーなど)、自身の一部としてではなく、常に外部タスクでアクティビティを開始するものがあります。そのため、これらのエンティティが startActivity() に渡すインテントには常に FLAG_ACTIVITY_NEW_TASK が含まれます。このフラグを使用する外部エンティティによって起動される可能性があるアクティビティがある場合は、起動アイコンなどを使用して、アクティビティが開始したタスクにユーザーが戻れるよう独立した手段を確保します(タスクのルート アクティビティには、CATEGORY_LAUNCHER インテント フィルタがあります。以下のタスクを開始するセクションをご覧ください)。

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

    この場合は、タスクがフォアグラウンドに移動したときに、アクティビティは自身が開始したタスクから、アフィニティがあるタスクへと移動できます。

    たとえば、選択した都市の天気を予報するアクティビティが、旅行アプリの一部として定義されているケースを考えてみましょう。このアクティビティは、同じアプリ内の他のアクティビティと同じアフィニティ(アプリのデフォルト アフィニティ)を持ち、この属性による親への再割り当てが許可されています。いずれかのアクティビティにより開始された天気予報アクティビティは、最初はそのアクティビティと同じタスクに属しています。しかし、旅行アプリのタスクがフォアグラウンドに移動すると、天気予報アクティビティはそのタスクに再度割り当てられ、タスク内に表示されます。

ヒント: APK ファイルに、ユーザーの視点で見た場合に複数の「アプリ」が含まれている場合は、taskAffinity 属性を使用して、それぞれの「アプリ」に関連付けられているアクティビティに異なるアフィニティを割り当てることをおすすめします。

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

ユーザーがタスクを長時間離れると、システムはルート アクティビティ以外のすべてのアクティビティのタスクをクリアします。ユーザーがタスクに再び戻ると、ルート アクティビティのみが復元されます。システムがこの動作を行うのは、しばらく時間が経過した後には、ユーザーは前に行っていた作業を放棄した可能性が高く、新しいことを始めるためにタスクに戻ったとみなされるためです。

この動作を変更するために使用できるアクティビティの属性には、次のものがあります。

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 つ目の機能は重要です。また同様の理由から、アクティビティが常にタスクを開始することを示す "singleTask""singleInstance" の 2 つの起動モードは、アクティビティが ACTION_MAINCATEGORY_LAUNCHER フィルタを持つ場合にのみ使用する必要があります。たとえば、フィルタがない場合にどうなるかを想像してみてください。インテントにより "singleTask" アクティビティが起動され、新しいタスクが開始されます。ユーザーがそのタスクでしばらく作業を行い、ホームボタンを押します。すると、タスクはバックグラウンドに移動し、非表示になります。タスクがアプリ ランチャーに表示されていないため、ユーザーはタスクに戻ることができません。

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

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

その他のリソース