Android 5.0がリリースされアプリケーション開発を行う際に、改めて Android アプリの基本である Activity について再確認してみようと思いライフサイクルを調査してみました。
標準的なライフサイクル
Android Developers では [Activity Lifecycle] (http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle) は以下のように説明されています。
この図では基本的な Activity のライフサイクルを示し、ライフサイクルが変化するタイミングで呼び出される Activity のメソッドを示しています。
ただ最新の Android 5.0 (API Level:21) で示されているメソッドを見ていくと、図の中で省略されているメソッドがいくつかあります。
Activity の launchMode など状態遷移を変化させる属性を指定されている場合から、実際に動作確認しながらライフサイクルの内容を整理・確認してみました。
LaunchMode = Default の場合
Activity の標準的な動作で AndroidManifest で launchMode を指定しない場合も該当します。
Activity が実行されるまで
まず標準の図で示される Activity launched から Activity running に遷移する場合に呼ばれるケースでは下記のようになります。
標準の図では省略されている Activity#onCreate() と Activity#onResume()のあとに Activity#onPostCreate() や Activity#onPostResume() の呼び出しが行われます。
普段は利用することは少ないメソッドですが SDK に付属しているサンプルのいくつかでは利用されていることは確認しました。
どちらも API Level 1 なので API Level によって処理の有無が変化するなどの問題は置きませんが API Level 1 であるにも関わらず、あまり知られていないメソッドだと思います。
Activity が一時停止・停止・終了するまで
Activity running から Activity shut down までのライフサイクルを整理すると下記のようになりました。
この中で呼び出されるタイミングについてはメソッドの説明には一切無いモノも含まれていますので以下の情報は Nexus 6 での動作検証を元にしています。
少しわかりにくですが Activity running の状態でActivity#onWindowFocusChange()が発生すると、すぐに Activity#onUserLeaveHint() と Activity#onSaveInstanceStateが呼び出されます。
LinkBubble や Facebook Messenger の ChatHead に代表される特定の Activity を持たず WindowManager へ直接 View を追加してスクリーン内に常駐するよう別タスクの View がフォーカスを得た場合も Activity#onWindowFocusChange() が呼び出されます。通知バーを下に引き出した場合も同じようにライフサイクルが遷移します。
特徴的なのは [Activity#onCreateThumbnail()](http://developer.android.com/reference/android/app/Activity.html#onCreateThumbnail(android.graphics.Bitmap, android.graphics.Canvas))のように、用途はわかるんだけど実際にいつ呼び出されるのか明記されていないメソッドなども、きちんと Activity のライフサイクルの中で呼びだれていることがわかりました。
全体
基本的なライフサイクルだけでも、実際に Activity の Launched から shut down までの間に呼び出されているライフサイクルメソッドは随分多いことがわかります。
他にも、いくつかのライフサイクルメソッドがあるのですが調査の時間がなく割愛しておりますが調査ができ次第追記する予定です。
LaunchMode = singleTop の場合
singleTop や singleInstance はアプリケーションがバックグラウンドに遷移してから、再びフォアグラウンドに遷移する際の状態遷移を制御するために利用されることが多いと思います。
Activity が実行されるまで
ここで初めて Activity#onNewIntent() がライフサイクルの中に登場します。ホームアプリを例に取ると分かりやすいかもしれませんが、ホームボタンをおした時の Action に対してデフォルト動作に指定されたアプリケーションは、バックグランドに遷移したあとに、ホームボタンを押すと Activity#onNewIntent() を経由してから Activity#onStart() が呼び出されます。
図が見きれてしまうのは、図を書いたツールの都合です。
Activity が一時停止・停止・終了するまで
際立って特徴的な動作はないものの Activity#onRestoreInstanceState() に対応する Activity#onSaveInstanceState() が Activity のライフサイクルに加わります。
全体
Activity が初めて起動する際には大きな変化はありませんが停止状態であるActivity#onStop()から復帰して Activity running までに呼び出されているメソッドは増加しているのと Activity が復帰時に渡される Intent が Activity#setIntent() されている場合も Activity#onNewIntent() で取得するなど処理の流れも変わっていることがわかります。
persistableMode = persistAcrossReboots の場合
ここで利用する persistableMode は Android 5.0(API Level 21) から追加された新たな属性で Activity の永続化に関連する設定を示します。ここでは Activity の情報をpersistAcrossReboots を指定することで最近使った画面(Recent Screen)からタスクが復帰した際に処理を継続させることを示しています。
Activity が実行されるまで
ライフサイクルの中で明確に変わるのは、永続化のためデータを引き継ぐためのメソッドがいくつかライフサイクルの中に登場することです。
引数に Bundle が指定されているメソッドには、通常の Bundle のみのメソッドの直後に PersistableBundle を引数に追加されているメソッドが呼び出されるように動作が変わります。
例えば起動処理の定番である Activity#onCreate() は
Activity#onCreate(android.os.Bundle) と
[Activity#onCreate(Bundle savedInstanceState, PersistableBundle persistentState)](http://developer.android.com/reference/android/app/Activity.html#onCreate(android.os.Bundle, android.os.PersistableBundle))
のようになるので従来の実装を変えることなく、新たに追加された PersistableBundle のあるメソッド側に永続化にために必要な処理を追加することで、大きな変更を行わずとも永続化などの新たなライフサイクルにも対応できるように実装できると思います。
この PersistableBundle を引数に持つメソッドは全て API Level 21 になります。
Activity が一時停止・停止・終了するまで
終了時のケースではActivity#onSaveInstanceState(Bundle outState)が永続化用の PersistableBundle を引数に持つ [Activity#onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState)](http://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState(android.os.Bundle, android.os.PersistableBundle))が追加されています。
全体
図が...かなり分かりにくいですね。
特徴としては、ライフサイクルメソッドが基本的なものに比べても Activity Launched から Activity running までの起動処理のライフサイクルは override していない状態でも多くのメソッドを経由してライフサイクルが変化している事がわかります。同時に、経由するメソッドが多いため起動処理でシビアなタイミングの調整をしていると思わぬ影響を受けることがあるかもしれません。
API 仕様から見る Activity の機能変遷
API Level 21 の仕様から Activity のメソッドに定義されている API の状況を調査してみました。
現在の Activity のメソッドの多くは API Level 1 から定義されており、Activity 自体のライフサクルに大きく影響を与える変更があった API Level では多くの機能が追加され現在まで利用されていることが分かります。
そこから多くの API が追加された API Level の特徴を見てみます。
Honeycomb(API Level:11/Android 3.0)
Jelly Bean(API Level:16/Android 4.1)
Lollipop(API Level:21/Android 5.0)
Lollipop での特徴は下記のようになります。
Recent Screen
Kitkat までの履歴画面は「1アプリに1つ」というルールで履歴に登録されています。
Lollipop では、アプリケーションが必要に応じて、複数の画面を履歴に登録することができるようになりました。
これは Activity の永続化である Persistable を利用することで継続的な動作を行うことができます。ライフサイクルメソッドではないので前述では説明しておりませんが、Activity#setTaskDescription()で Activity のアイコンやラベルなど視覚的な情報を変化させることができます。
Screen Pinning
Lollipop から UI/UXに大きく影響をあたえるのがスクリーンの固定(Pin)です。
Pin されたスクリーンは一時的にタスクの切り替えや Notification からの遷移を遮断することができます。
例えば用途や目的が明確なデバイスなどで利用される(教育向けとかキオスク)アプリケーションなどでの用途を想定しているようです。
Pin を利用するにはシステムの設定からセキュリティから当該機能を有効にする必要があります。
Pin されたスクリーンでは下記のような特徴を持ちます。
- ステータスバーはブランク(空っぽ)になります。
- Notification は非表示なります。
- ホームボタンや履歴ボタンは非常時になります。
- 他のアプリケーションは新たな Activity を起動できません。
- 新しいタスクを作らなければカレントにあるアプリケーションは新しい Activity を起動できます。
- スクリーンのPinがデバイスのオーナー権限で実行されている場合にはアプリがActivity#stopLockTask()を呼ぶまでスクリーンは Pin の状態を維持します。
戻るボタンと履歴ボタンを長押しすることでもスクリーンの Pin を解除することはできますがセキュリティによってパスワードを設定されている場合は、Pin の解除を行うためにロックスクリーンのセキュリティ画面に遷移して認証を要求します。
雑感
Android 5.0 では、新しい画面の状態が加わり特殊なアプリケーションをハック的なことをすることもなく実装できるようになりました。
ライフサイクルでは Dialog や Fragment のような Activity と連動しながらも独自のライフサイクルを持つ機能が増えてきて、基本的な部分や基礎的な仕様の理解が重要性を増している感じがしています。