The Android Developer Challenge is back! Submit your idea before December 2.

タスクとバックスタック

通常、アプリケーションには複数のアクティビティが含まれています。それぞれのアクティビティは、ユーザーが実行して、他のアクティビティを開始するといった特定のアクションを中心に設計されています。 たとえば、メール アプリケーションに新規メッセージ一覧を表示する 1 つのアクティビティがあるとします。ユーザーがメッセージを選択すると、そのメッセージを表示するための新しいアクティビティが開きます。

アクティビティでは、端末上の他のアプリケーションに存在するアクティビティを開始することもできます。たとえば、アプリケーションがメールの送信を求める場合は、「送信」アクションを実行し、メールアドレスや本文などのデータを含めるインテントを定義できます。 その結果、この種のインテントの処理を宣言している別のアプリケーションのアクティビティが開きます。 この場合、インテントはメールを送信することであるため、メール アプリケーションの「作成」アクティビティが開始されます(複数のアクティビティが同じインテントに対応している場合、システムはユーザーに選択を求めます)。 メールが送信されると、元のアクティビティが再開され、まるでメール アクティビティがそのアプリケーションの一部であるように見えます。 アクティビティは異なるアプリケーションのものでも、Android は両方のアクティビティを同じタスク内に保つことによって、このシームレスな操作性を維持しています。

タスクとは、ユーザーが特定の作業を行う時に情報のやり取りを行うアクティビティの集まりです。 アクティビティは、各アクティビティが開かれた順にスタック(バックスタック)形式で配置されます。

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

現在のアクティビティが別のアクティビティを開始すると、新しいアクティビティがスタックの一番上にプッシュされ、アクティブになります。 前のアクティビティはスタックに残りますが、停止されます。アクティビティが停止すると、システムはそのユーザー インターフェースの状態を維持します。 ユーザーが [戻る] ボタンを押すと、現在のアクティビティがスタックの一番上から消え(アクティビティは破棄され)、前のアクティビティが再開します(UI は前の状態で復元されます)。 スタック内のアクティビティが並べ替えられることはなく、スタック上にプッシュされるかスタックから消されるかのみです — 現在のアクティビティにより開始されるとスタック上にプッシュされ、ユーザーが [戻る] ボタンを押すと破棄されます。 このように、バックスタックは「後入れ先出し」オブジェクト構造となっています。 図 1 は、この動作を時系列に視覚化し、アクティビティとそれに伴うその時点でのバックスタックの状態を示したものです。

図 1 タスク内の新しいアクティビティがバックスタックにアイテムを追加するプロセスを示しています。 ユーザーが [戻る] ボタンを押すと、現在のアクティビティは破棄され、前のアクティビティが再開します。

ユーザーが続けて [戻る] ボタンを押すと、スタック内のアクティビティは消えていき、前のアクティビティが表示されます。最終的に、ユーザーはホーム画面(またはタスクの開始時に実行されていたアクティビティ)に戻ります。 すべてのアクティビティがスタックから削除されると、タスクはなくなります。

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

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

タスクは結束した構成単位で、ユーザーが新しいタスクを開始したり [Home] ボタンを使ってホーム画面に移動したりすると「バックグラウンド」に移動できます。 バックグラウンドでは、タスク内のすべてのアクティビティが停止されますが、タスクのバックスタックは元の状態を保ちます。図 2 に示すように、別のタスクが行われている間、タスクはフォーカスを失った状態になります。タスクはその後「フォアグラウンド」に戻り、ユーザーは操作の続きを行うことができます。 たとえば、現在のタスク(タスク A)のスタックに 3 つのアクティビティがあるとします。現在のアクティビティの下に 2 つのアクティビティがある状態です。 ユーザーが [Home] ボタンを押し、アプリケーション ランチャーから新しいアプリケーションを起動します。 ホーム画面が表示されると、タスク A はバックグラウンドに移動します。 新しいアプリケーションが起動すると、システムはそのアプリケーションのタスク(タスク B)を開始します。タスク B には独自のアクティビティ スタックがあります。 アプリケーションの操作が終了すると、ユーザーはホームに戻り、タスク A を開始した元のアプリケーションを選択します。ここでタスク A はフォアグラウンドに移動します — 3 つのアクティビティはすべて元のままで、スタックの一番上にあるアクティビティが再開します。 この時点で、ユーザーはホームに移動してタスク B を開始したアプリケーションを選択して(またはオーバービュー画面でアプリのタスクを選択して)タスク B に切り替えることもできます。これは、Android のマルチタスク操作の一例です。

注: バックグラウンドには複数のタスクを一度に置くことができます。しかし、ユーザーが多数のバックグラウンド タスクを同時に実行すると、システムがメモリを回復するためにバックグラウンド アクティビティを破棄する場合があります。その結果、アクティビティの状態は失われます。アクティビティの状態セクションをご覧ください。

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

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

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

ナビゲーション デザイン

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

アクティビティの状態を保存する

上述のように、デフォルト動作では、システムはアクティビティが停止するとその状態を保持します。 この方法では、ユーザーが前のアクティビティに戻ると、ユーザー インターフェースが前の状態のままで表示されます。 しかし、コールバック メソッドを使って積極的にアクティビティの状態を保持することもできます。破棄されたアクティビティを再作成しなければならないことを考えると、アクティビティの状態は積極的に保持すべきです。

システムが(新しいアクティビティの開始時やバックグラウンドへのタスクの移動時などに)アクティビティの 1 つを停止している時にシステム メモリの回復が必要になると、システムはそのアクティビティを完全に破棄する可能性があります。 これにより、アクティビティの状態に関する情報が失われてしまいます。システムは、アクティビティがバックスタックに留まっていることは認識していますが、アクティビティがスタックの一番上に置かれるとアクティビティを(再開ではなく)再作成しなければなりません。 ユーザーの作業内容が失われるのを回避するには、onSaveInstanceState() コールバック メソッドをアクティビティに実装することによって、作業状態を積極的に保持する必要があります。

アクティビティの状態の保存方法の詳細については、アクティビティのドキュメントをご覧ください。

タスクを管理する

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

<activity> マニフェスト要素の属性と startActivity() に渡すインテント内のフラグを使って、これらの動作とその他の動作を実現できます。

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

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

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

また、タスクとアクティビティの提示方法やオーバービュー画面での管理方法に関する考慮点について別途記載しています。 詳細については、オーバービュー画面をご覧ください。 通常、オーバービュー画面にタスクとアクティビティを提示する方法はシステムが定義できるよう許可し、この動作を変更する必要はありません。

警告: ほとんどのアプリケーションはアクティビティとタスクに対するデフォルトの動作に割り込むべきではありません。 アクティビティがデフォルトの動作を変更する必要があると判断した場合は、起動時と他のアクティビティやタスクからの [戻る] ボタンによる移動時のアクティビティのユーザビリティを慎重にテストする必要があります。ユーザーが想定する動作と競合する恐れのあるナビゲーション時の動作については必ずテストを行ってください。

起動モードを定義する

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

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

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

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

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

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

"standard"(デフォルトのモード)
デフォルトの設定です。システムは、開始されたタスクからアクティビティの新しいインスタンスをタスク内に作成し、インテントを渡します。 アクティビティは複数回インスタンス化できます。各インスタンスは異なるタスクに所属でき、1 つのタスクは複数のインスタンスを持つことができます。
"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 の新しいインスタンスがスタックに追加されます。

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

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

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

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

たとえば、Android Browser アプリケーションは、<activity> 要素の singleTask 起動モードを指定することによって、ウェブブラウザ アクティビティを常に自身のタスクで開くことを宣言しています。これは、開発されたアプリケーションが Android Browser を開くインテントを発行すると、そのアクティビティがアプリケーションの同じタスクには配置されないことを意味します。 代わりに、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 フラグが含まれると、システムは新しいアクティビティを収容する別のタスクを捜します。 これは、通常新しいタスクですが、新しいタスクである必要はありません。 新しいアクティビティとして同じアフィニティを持つ既存のタスクが存在する場合は、アクティビティはそのタスクで開始されます。 存在しない場合は、新しいタスクを開始します。

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

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

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

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

ヒント: .apk ファイルがユーザーの視点で 1 つ以上の「アプリケーション」を含んでいる場合は、各「アプリケーション」に関連付けられたアクティビティに 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 番目の機能が重要になります。 アクティビティが ACTION_MAINCATEGORY_LAUNCHER フィルタを持つ場合にのみ、アクティビティが常にタスクを開始することを示す "singleTask""singleInstance" の 2 つの起動モードを使用すべきなのはこのためです。 たとえば、フィルタがない場合にどうなるのかを想像してみてください。 インテントが "singleTask" アクティビティを起動し、新しいタスクを開始し、ユーザーがそのタスクでしばらくの間作業を行います。 その後、ユーザーは [Home] ボタンを押します。 タスクはバックグラウンドに移動し、見えなくなります。タスクはアプリケーション ランチャーに表示されていないため、この状態ではユーザーがタスクに戻る手段がありません。

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

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