この記事は https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en の翻訳版です。
Activityは、マルチタスクを動作させるAndroidのメモリ機構に対して、よくデザインされた最も華麗なコンセプトの一つです。
一方で注意しておきたいのは、Activityは必ずしも画面表示に合わせて生成されるわけではありません。これが混乱を招く原因となります。この記事で詳しく説明をしていきますが、最も大事になるのはlaunchModeです。
Actitityは、目的を達成するために適切な動作をします。例えばメール作成画面は、複数のメールを作成するために複数のメール作成画面(複数のActivityインスタンス)を立ち上げます。一方で、メールの受信トレイを表示する画面はただ一つ(SingletonなAcrtivity)で構わないでしょう。
launchModeの設定によって、こうした単一のActivityか複数のActivityかを選ぶことができます。また、UXや機能の使いやすさをコントロールできます。それでは詳しい説明に入りましょう。
launchModeを設定する
基本的には、以下のように AndroidManifest.xml の <activity> 要素に属性として設定できます。
launchModeは4種類存在しています。一つずつ見ていきましょう。
standard
デフォルトのlaunchMode設定です。
Intentが送られる度に、新しいActivityが生成されます。例えば10個のメール作成Intentが送られると、10個のActivityが生成されます。standard設定では、デバイス内に上限なくActivityを立ち上げられます。
KitKat以前の挙動
KitKat以前のOSでは、standard設定のActivityが立ち上がると、同一のタスク内のスタックに積まれます。
以下は、ある画像をシェアした後のスタック状態のイメージです。異なるアプリケーションであっても同一タスクに積まれていることがわかります。
タスクマネージャーでこのことを確認できます。アプリ名と表示されている画面が異なるため、やや奇妙な状況に感じないでしょうか。
この状況は例え、別のアプリケーションに移動して戻ってきても変わりません。Galleryアプリケーションのスタックの先頭は、新たに起動したstandard設定のActivityなのです。Galleryアプリに戻りたい場合は、バックキーなどを押してこのActivityを終了する必要があります。
Lollipop以降の挙動
Lollipop以降のOSではやや挙動が異なります。まず、Intentを送ったアプリと起動するActivityが属するアプリが、同一の場合を見てみましょう。この場合の挙動は、KitKat以前と同様に、同一タスクのスタックに積まれます。
一方で、異なるアプリへのIntentだった場合はどうでしょうか。この場合は、新たなタスクが生成され、起動したActivityはその別のタスクの先頭に位置します。
Galleryとは別のアプリが立ち上がっていることが確認できます。これは、Lollipop以降のタスクマネージャで、より直感的な操作ができるように変更されたものです。この変更によって、Galleryアプリにすぐさま戻ることができるようになりました。更に別のIntentを発行すると、以下のように別のタスクが立ち上がります。
launchModeとしてstandardを適用している実際の例は、メール作成画面やSNSの投稿画面です。Intent毎に別の動作をさせたい場合には、standard設定を使いましょう。
singleTop
次はsingleTopです。singleTopは、複数のActivityインスタンスが生成されるという意味では、standardとほとんど同じように動作します。唯一の違いは、既に呼び出したタスクの先頭に同じ型のActivityインスタンスが存在していれば、新たなActivityを生成せず、同じActivityを使いまわすという点です。この使いまわされる時に呼び出されるのが onNewIntent() メソッドです。
singleTopでは、Activityに対する全ての呼び出しパターンに対応するために onCreate() と onNewIntent() の両方に対応する必要があります。
このモードを使う例として検索画面が挙げられます。検索テキストの入力と、検索結果を見ることができるSearchActivityを考えてみましょう。良いUXとして、検索する度にを別の検索画面が立ち上がるのではなく、同じ画面内に留まって検索を続けられることを想像するのではないでしょうか。
反対に、検索の度にSearchActivityが新たに生成されることを考えてみましょう。このとき、10回検索すれば10個のSearchActivitiyが立ち上がります。検索画面以前に戻りたいときにはバックキーを10回も押さなければなりません。
singleTop設定によってSearchActivityがスタックの先頭にあるときは、同じインスタンスを使いまわし、検索結果を更新するようにするとどうでしょうか。検索画面以前に戻る場合も、この状態であれば1回バックキーを押すのみです。とても直感的になりました。
注意すべきは、こうしたsingleTopの動作は呼び出し側と同じタスクに属する場合のみ有効です。他のタスクを立ち上げる、あるいは他のアプリケーションを呼び出すような場合には、Activityの使い回しは起きません。このときの動作はstandardモードと完全に同じです。(KitKat以前であれば同じタスクの先頭に、Lollipop以降では別のアプリなら新たなタスクが生成されます)
singleTask
このモードはstandardやsingleTopとは大きく異なります。singleTaskが設定されたActivityはシステムの中でただ一つのインスタンスしか存在しません。つまりシングルトンです。既にこのモードのActivityが生成されていて、タスクから必要とされた場合には、そのタスクの先頭へと移動します。このとき、 onNewIntent() メソッドが呼ばれます。新たにインスタンスが作られる際には、そのタスクに通常通り属します。
同じアプリケーション内での挙動
まだsingleTaskモードのActivityが作られていない場合は、新たにインスタンスが生成され単純にタスクの先頭に積まれます。
既にインスタンスが生成されていた場合は、singleTaskモードのActivityの上に積まれた全てのActivityを終了する(ライフサイクルメソッドが呼び出される)ことで、スタックの先頭になります。このとき、呼び出し側が送信したIntentは onNewIntent() を通してsingleTaskモードのActivityに渡されます。
UXの観点ではあまり良くない動きですが、このような設計となっています…
ドキュメントには以下のようにあります。
システムは新しいタスクを作成し、新しいタスクのルートにアクティビティをインスタンス化します。
しかし、実際にはこのような動作は行われません。singleTaskのActivityは現在のスタックの先頭に積まれることを、dumpsys activityコマンドで確認することができます。
もしドキュント通りに動作させたいのであれば、 taskAffinity 属性をsingleTask設定したActivityに追加する必要があります。
以下が、このSingleTaskActivityを起動した際の結果です。
taskAffinitiy を設定するかどうかは、状況に応じて選択してください。
他アプリとの連携
あるIntentが、まだ一つもActivityを起動していない他のアプリに送られると、新たに生成されたActivityが配置された新しいタスクが生成されます。
singleTaskのActivityが属するアプリケーションのタスクが既に存在している場合は、そのタスクの先頭に新たに生成されたActivityが積まれます。
Activityのインスタンスがいずれかのタスクに存在するとき、全てのタスクは先頭に移動され、singleTask設定にされたActivityの上に存在していたActivityはライフサイクルに沿って破棄されます。
singleTaskは、例えばメールアプリの受信ボックスやSNSのタイムラインなど、アプリの入り口になるActivityによく使われます。こうしたActivityは複数のインスタンスを持つことを想定しないため、singleTaskの挙動が非常によくマッチします。但し、上述したようにこのモードに関連したActivityはユーザの想定とややずれた形で破棄されることがあるため、賢く使うようにしていきましょう。
singleInstance
このモードは、インスタンスがただ一つしか存在できないという点では、singleTaskに非常に似ています。singleTaskとの違いは、あるタスクはsingleInstanceのActivityをただ一つしか保持できないという点です。singleInstanceモードのActivityから他のActivityが呼ばれると、新たなタスクが生成され、起動したActivityはその新しいタスクに属します。同じように、他の何らかのActivityからsingleInstanceモードのActivityが呼ばれると、新たなタスクが生成され、起動したActivityはそのタスクに属します。
この挙動によって奇妙なことが起きます。dumpsysで確認すると確かに2つのタスクが存在するのに、タスクマネージャの表示では一つのタスクしか表示されなくなることです。これは、タスクマネージャが先頭に来ているタスクを見て表示を行うためです。その結果、バックグランドで動作しているにも関わらず、フォアグラウンドに戻せないタスクが存在するようになります。なんということでしょうか!
以下の図が、既にタスク内にActivityが存在している状態で、singleInstanceモードのActivityを立ち上げた後の状態です。
タスクは2つ存在しているのに、タスクマネージャでは1つしか確認できません。
このタスクは#2で示すように一つしかActivityインスタンスを持っておらず、#1に再び戻ることはできません。戻りたい場合はもう一度ランチャからアプリを立ち上げることですが、その場合は#2で示すsingleInstanceモードのActivityが属するタスクがバックグランドに隠れてしまいます。
こうした状況を回避したい場合は、singleTaskで行ったのと同様に、 taskAffinity 要素をsingleInstanceモードのActivityに設定します。これによってタスクマネージャに複数のタスクを表示することができます。
良いですね。
signleInstanceが使われることは非常にまれです。実際にはランチャアプリや確実に一つのActivityしか存在しないようなアプリに対して使われるでしょう。確実に必要な場合以外は、使わないことをおすすめします。
Intentのフラグ
この記事の冒頭で AndroidManifest.xml 内に直接launchModeを設定する方法を示しましたが、Intentのフラグを使うことでより柔軟に設定を変えることができます。
この場合は、 StandardActivity がsingleTopモードで起動します。
非常にたくさんのフラグを使用できますので、詳しくはIntentをご覧ください。
この記事がみなさまの助けになれば幸いです =)
↓↓Kindle本を出しました
コメントを残す