2010年8月10日火曜日

Android Activity, Task, Stack, Launch mode

アクティビティ と タスク と スタック と 起動モード (と ライフサイクル)

開発の基礎のアクティビティとタスク以降
がとっても(日本語が)わかりにくかったので、
私なりの解釈を書くことにしました。
(もしかしたら間違ってるかもしれないよ)

なんで、わかりにくいかと言うと、用語の定義がされてないからなんですね。

なので、ちゃんと定義(みたいなの)

・アクティビティ

 これはいいよね。いわゆる Activity です。


・ルートアクティビティ

タスクを開始するアクティビティのこと。
通常だと、AndroidManifest.xml の タグのなかに

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

になってるアクティビティのことです。


・スタック

 現在生きている(destroyされていない)アクティビティ
 を管理する入れ物みないなもの
 例えば、起動時のアクティビティが Activity A で、
 そこから、startActivity で別の Activity B に飛んで、
 さらにそこから startActivity で Activity C に飛んだとすると
 スタックの中は

 ---------
 | A-B-C
 ---------

 になっている
 ここで、Activity C を finish() したり、ユーザが[戻る]ボタンを押したりすると、

 ---------
 | A-B
 ---------

 になる。OK?


・タスク

 元ページだと、
「アクティビティをスタックにまとめたものがタスク」
と書いてあるのですが、意味がさっぱりです。
で、原文(英語)を見ると
「Simply put, a task is what the user experiences as an "application." It's a group of related activities, arranged in a stack」
つまり、
ユーザーがあるアプリケーションを使ったとして、
その状態遷移に含まれるアクティビティの集合をタスクといいましょう、
ということです。

で、この(ここでいう)スタックとタスクの違いがよくわからない
つまり、図にするとこういうことなんです(たぶん)

 タスク1  タスク2  タスク3
 --------------------------------
 | A-B-C | A'-B'-C'-D' | A''-B''
 --------------------------------
      スタック

ここで、画面に表示されてるのは B''、
各タスクのルートアクティビティが A, A', A''
全体がスタック


ここまでで、用語はOK

説明を簡単にするために、
MyApp というアプリを仮定します。

MyApp は4個のアクティビティから構成されています。

     A : MainActivity
      ↓    ↓
B : SubActivity1   C : SubActivity2
 ↓          ↑
D : SubSubActivity1 ------


← : startActivity() / startActivityForResult()


こんな感じ。ここで、MainActivity がルートアクティビティ
なのはわかりますよね。


"タスク内のアクティビティは、1 つのユニットとして一緒に移動します。
タスク全体(アクティビティスタック全体)をフォアグラウンドに移動したり、バックグラウンドに移動したりできます。"

これは、

 タスク1  タスク2  タスク3
 --------------------------------
 | A-B-C | A'-B'-C'-D' | A''-B''
 --------------------------------
      スタック

の状態で、例えばホーム長押しでタスク2のアプリを開いたとすると、

 タスク1 タスク3  タスク2
 --------------------------------
 | A-B-C | A''-B'' | A'-B'-C'-D'
 --------------------------------
      スタック

になる、ということ(だと思います)

ただし、この動作はアクティビティを開始した Intent オブジェクトのフラグセットと、
マニフェストに指定されているアクティビティの <activity> 要素の属性セット
によって変更可能

そのための Intent フラグ としては

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
FLAG_ACTIVITY_SINGLE_TOP

主に使用する <activity> 属性は以下を使う

taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch



親和性と新しいタスク

まず、親和性ってなに?
。。。
原文をみると affinity

"デフォルトでは、アプリケーション内のすべてのアクティビティは相互に親和性があり、すべてのアクティビティができる限り同じタスクに属そうとします。"

ええと、つまり同じアプリから起動したアクティビティは同じタスクに
属するということですね。はい。

"ただし、 要素の taskAffinity 属性を使用して、アクティビティごとに個別の親和性を設定することもできます。"

つまり、標準なら

 タスク1 タスク3  タスク2
 --------------------------------
 | A-B-C | A''-B'' | A'-B'-C'-D'
 --------------------------------
      スタック

だけど、

 タスク1 タスク3  タスク2
           ↓ ↓ ↓
 --------------------------------
 | A-B-C | A''-B'' | A'-B'-C'-D'
 --------------------------------
                ↑
              タスク3

みたいにできるということかな。はい。

こうするための方法は2つ

1. アクティビティを起動する Intent オブジェクトに
  FLAG_ACTIVITY_NEW_TASK フラグを入れる

  この場合だと、C'から D'を起動するときの Intent に
  FLAG_ACTIVITY_NEW_TASK をセットする

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

  この場合だと、AndroidManifest.xml の D' の <activity> タグに
  allowTaskReparenting="true" をセットする

詳しくは親和性と新しいタスクのこの部分を見てください。



起動モード

これがわかりにくかった。というかややこしい。

launchMode 属性の 要素は、次の4種類

"standard"(デフォルト モード)
"singleTop"
"singleTask"
"singleInstance"

まとめてみた



*1 Intent オブジェクトに FLAG_ACTIVITY_NEW_TASK フラグが含まれている場合は、前のセクション親和性と新しいタスクで説明したとおり、別のタスクが選択

*2 複数のインスタンスを複数のタスクに割り当てることも、特定のタスクに同じアクティビティの複数のインスタンスを割り当てることも可能




スタックのクリア


"ユーザーがタスクを長時間放置すると、タスクのルート アクティビティを除くすべてのアクティビティがクリアされます。"

つまり

 タスク1  タスク2  タスク3
 --------------------------------
 | A-B-C | A'-B'-C'-D' | A''-B''
 --------------------------------
      スタック

の状態で長時間放置すると

タスク1  タスク2  タスク3
 --------------------------------
 | A | A' | A''-B''
 --------------------------------
      スタック

になる。これがデフォルト。
この動作を変更したい場合は、次のアクティビティ属性を使用する

alwaysRetainTaskState 属性
タスクのルート アクティビティでこの属性を "true" に設定すると、長時間経過しても、タスク内のすべてのアクティビティはそのまま残される。

つまり

 タスク1  タスク2  タスク3
 --------------------------------
 | A-B-C | A'-B'-C'-D' | A''-B''
 --------------------------------
      スタック



clearTaskOnLaunch 属性
タスクのルート アクティビティでこの属性を "true" に設定した場合、ユーザーがいったんタスクを離れると、戻ったときにはルートを含むすべてのアクティビティがクリアされる。

つまり

 タスク3
 --------------------------------
 | A''-B''
 --------------------------------
      スタック



finishOnTaskLaunch 属性
clearTaskOnLaunch 属性と似ているが、タスク全体ではなく単一のアクティビティに作用する。
また、ルート アクティビティを含むどのアクティビティもクリアの対象となりえる。
この属性が "true" に設定されたアクティビティは、現在のセッションの間のみタスクの一部を形成する。
ユーザーがいったんそのタスクから離れてから、再度タスクに戻ると、このアクティビティはクリアされている。

つまり、B にこの属性を設定すると、

 タスク1  タスク2  タスク3
 --------------------------------
 | A-C | A'-C'-D' | A''-B''
 --------------------------------
      スタック



アクティビティをスタックから削除する他の方法

Intent オブジェクトに FLAG_ACTIVITY_CLEAR_TOP フラグが含まれている
 +
そのインテントを処理すべきタイプのアクティビティのインスタンスが
対象タスクのスタック内に存在する
 ↓ 
そのインスタンスがスタックの最上位になってインテントに応答できるよう、
それより上位のアクティビティはすべてクリアされる


つまり、B'' が呼んだ Intent オブジェクトに FLAG_ACTIVITY_CLEAR_TOP が設定されていて、このインテントを処理するのが A' だとすると

 タスク1  タスク2   タスク3
 --------------------------------
 | A-B-C | A'-B'-C'-D' | A''-B''
 --------------------------------
      スタック

こうなる

 タスク1  タスク3  タスク2
 --------------------------------
 | A-B-C | A''-B'' | A'
 --------------------------------
      スタック

タスク3がバックグラウンドに移動して、A'より上位のアクティビティ B', C', D' はクリアされる


指定されたアクティビティの起動モードが "standard"
 ↓
そのアクティビティもスタックから削除され、
新しいインスタンスが起動してインテントを処理します。

つまり、
上記の場合、(B', C', D' は同じようにクリアされ)A' もクリアされて新しく A' のインスタンスが起動する


FLAG_ACTIVITY_CLEAR_TOP は、ほとんどの場合 FLAG_ACTIVITY_NEW_TASK と組み合わせて使用する
 ↓
別のタスクに既に存在しているアクティビティを探し、それをインテントに応答できる位置に配置できる



タスクの開始

アクティビティをタスクのエントリ ポイントとして設定

アクションに "android.intent.action.MAIN"
カテゴリに  "android.intent.category.LAUNCHER"

を指定したインテント フィルタをアクティビティに追加

このタイプのフィルタを追加すると、アクティビティのアイコンとラベルがアプリケーションランチャに表示される

アクティビティに MAIN と LAUNCHER フィルタが指定されている場合は、必ずタスクが開始される起動モード("singleTask" または "singleInstance")を使用する必要がある。


例えば、

インテントが "singleTask" アクティビティを起動し
 ↓
新しいタスクが開始
 ↓
ユーザーが作業
 ↓
ユーザーが [ホーム] キーを押す
 ↓
ホーム画面が表示=先ほどのタスクはバックグラウンドに移動

もし、このフィルタを指定しなかったら、ユーザーがタスクに戻るための手段がない!
また、"standard" や "singleTop" を指定した場合アクティビティは複数回インスタンス可能なため、ここでランチャーから新しく起動した場合別のインスタンスが作成される可能性がある。

FLAG_ACTIVITY_NEW_TASK フラグを指定したアクティビティでは、新しいタスクを開始した後にユーザーが [ホーム] キーを押してそのタスクを離れた場合に、タスクに戻るための手段を用意しておく必要がある。

外部エンティティから呼び出すことのできるアクティビティでこのフラグが使用されている可能性がある場合は、開始されたタスクにユーザーが戻るための手段を別途提供する。

ユーザーがアクティビティに戻ることができるようにしない場合は、 要素の finishOnTaskLaunch を "true" に設定する

3 件のコメント:

  1. タスク1  タスク2 
    -------------------
    | A-B-C | A' |
    -------------------
      スタック

    A' → Cに対して遷移することは可能でしょうか?

    返信削除