Activityとアクティビティのライフサイクル【Androidアプリ開発】

2023-08-01Androidアプリ開発入門

Activityとは何ぞや

画面を構成するActivityとそのライフサイクルと画面の破棄について書いていきます。

アクティビティはボタンやテキストなどのUIを表示する事ができる画面を構成するコードを指します。このアクティビティはスマホ画面全体の大きさだったり、ポップアップ画面の様な重なった画面もアクティビティになります。スマホアプリでは複数の画面から作られている事が多いですが、1つの画面に対して1つのアクティビティとなります。

Activityの例

Activityのライフサイクル

次にlifecycleって何って所を・・・。ライフサイクルはプログラム内でインスタンスが初期化されてから破棄されるまでの流れを言います。アクティビティに限った事ではなくプログラム内で生成されたものは何れ破棄されてメモリが解放されるというライフサイクルが存在します。

アクティビティのライフサイクルは基本的に下記の流れになります。

Activityのライフサイクル

①の初期化から始まり、内部的には画面が表示されるまで④までのプロセスがあります。on~は各プロセスの間に呼ばれるActivityクラスのメソッドになります。これらのメソッドをコールバックメソッドといいます。ユーザーがアプリを使ってる所で電話がかかってきたり、ユーザーがアプリを切り替えたり、画面を横にしたり、ユーザーは色々な動作をしますよね。これを割り込みイベントと言いますがその様な時には画面は一旦バックグラウンドにある状態になります。バックグラウンドは裏側にアプリがある状態を指します。逆にフォアグラウンドはアプリがアクティブな状態を指します。ユーザーが再びアプリを戻すと⑤の再開に戻り画面はアクティブ状態になります。

コールバックメソッドはオーバーライドして実装しますが、全てのコールバックで処理を書く必要はありませんがonCreate()のみ必須となっています。

①初期化(Initialized)

インスタンスが生成されて画面を作成する準備をしている所。

②作成(Created)/onCreate()

画面は出来ているけど、表示されていない状態。システムが最初にアクティビティを作成したときにonCreate()が呼び出されてアクティビティは作成済みの状態になります。

onCreate()は必ず実装する必要があります。このメソッドは初期化された後に一度だけ呼び出されるので、レイアウトファイルの読み込みやクリックイベントの定義、データバインディングを設定するなど初期化したい処理を書いておきます。

このメソッドはsavedInstanceStateのパラメーターを受け取ります。後述しますが、ライフサイクルに関係なくアクティビティが破棄される場合があります。これの対処として一時的にUI状態を保存する必要が有り、そうなった時のパラメーターになります。一番最初に起動した時はnullが入ります。

③開始(Started)/onStart()

画面は表示されているけどバックグラウンドに居てアクティブになっていない状態。アクティビティをフォアグラウンドに移動して操作可能な状態にする準備している所です。onStart()が呼び出されてアクティビティがユーザーに表示されます。表示されてるのにアクティブな状態ではないというのは、最初の起動時では一瞬過ぎてよく分からないと思います。次の④のアクティブ状態で表示された後にユーザー操作なとでアプリを離れた時に見る事ができます。下図の様に画面一覧を表示してる時は画面が表示されてるけどアクティブではない状態になります。

アプリのStarted状態

④⑤表示・再開(Resumed)/onResume()

画面が表示されてアクティブになってる状態。onResume()が呼び出されてユーザーが操作可能な状態になります。割り込みイベントが発生するとonPause()が呼び出されます。ユーザーがアプリに戻ると再びonResume()が呼び出されアクティブ状態になります、

onPause()で処理したコンポーネントの初期化やアクティビティが再開状態に必要な処理などを記述します。

⑥開始(Started)/onPause()

割り込みイベントが発生するとonPause()が呼び出されて一時停止されているアプリはバックグラウンド状態になります。ユーザーがアプリを再開するとonResume()を呼び出し再びアクティブな状態へと戻ります。

onPause()は保存を実行するのに十分な時間を確保できない場合がある為、データの保存やネットワーク接続、データベースの実行は完了しない可能性があるので注意が必要です。これらを行うにはonStop()の方で実装した方が良いです。

⑦作成(Created)/onStop()

他のアクティビティへ移動した時にアクティビティが使われなくなった時やホームへ戻った時などにonStop()が呼ばれてアクティビティは停止状態になり画面が表示されなくなります。アクティビティが使われなくなった場合はonDestroy()が呼ばれます。ユーザーがアプリに戻った場合はonRestart()が呼ばれた後にonStart()、onResume()が呼ばれます。

onStop()では、アクティビティがユーザーに表示されていない為不要なリソースを調整する必要があります。停止状態になるとアクティビティはメモリ内に常駐し、全ての状態が保持されます。この時にメモリ不足だった場合にシステムがアクティビティを破棄する場合があります。またこのタイミングでonSaveInstanceState()が呼び出されます。このメソッドについては後述します。

⑧破棄(Destroyed)/onDestroy()

ユーザーがアプリを終了したり、回転させたり、finish()(アクティビティを終了するメソッド)が呼び出されるとonDestroy()が呼ばれてそのアクティビティのリソースが全て破棄されてメモリが解放されます。

Jetpackのライフサイクル

Jetpackコンポーネントを使用する場合と使用しない場合では実装方法が異なります。コールバックメソッドはイベントという形になりますがライフサイクルは同じです。例えばonCreate()はアーキテクチャコンポーネントではON_CREATEイベントを受けとります。Jetpackでの実装については別の記事で紹介するつもりです。

Activityの破棄とビューのインスタンス状態の保存

ライフサイクルに関係なくこちらの意図しないところでActivityが破棄されてしまう場合が存在します。破棄されると画面の再作成が行われてUI状態が消されてしまいます。つまり、起動した時の状態に戻ってしまいます。

  • メモリ不足になった時アプリは強制終了されます。
  • ユーザーが画面の回転をしたり、マルチウィンドウに切り替えた時にActivityは破棄されます。
  • ユーザーが言語変更、キーボード変更した時などOSの構成変更された時にも破棄されます。

対策としてメンバ変数やUIのインスタンスの状態(ビューのテキストなど)を一時的に保存する処理をします。保存する処理は下記の3つの方法があります。Bundleオブジェクトとは、keyと値のセットでメモリ内にデータを保存するオブジェクトです。ViewModelとBundleオブジェクトは一時的に保存するだけでアプリが終了したら消えてしまいます。破棄された場合のデータの仮置き場と考えて下さい。

  1. JetpackアーキテクチャコンポーネントのViewModelを実装する方法
  2. onSaveInstanceState()でbundleオブジェクトを使用して保存する方法
  3. ローカルストレージに保存する方法

これらの方法は下記の様な特徴があります。

ViewModelbundleオブジェクトローカルストレージ
保存先メモリ内ディスクにシリアル化ディスクまたはネットワーク上
設定の変更後の保持
システムによって開始されたプロセス終了後も保持×
ユーザーによるアクティビティ終了/onFinish()時の保持××
データの上限複雑なオブジェクトは問題ないが、使用可能なメモリによってスペースが制限されるプリミティブ型と Stringなどのシンプルな小さなオブジェクトのみネットワーク リソースからのディスク スペースまたはネットワークからの取得コスト/時間によってのみ制限される
読み取り/書き込み時間高速(メモリアクセスのみ)低速(シリアル化/シリアル化解除およびディスクアクセスが必要)低速(ディスクアクセスまたはネットワークトランザクションが必要)
引用:Androidデベロッパー UI状態の保存

小さなデータであればbundleオブジェクトで十分ですが、SavedStateHandleを実装したViewModel(とLiveData)を使う事で画面回転時とシステムによる画面破棄の両方を防げます。これらの画面破棄時の処理方法は別の記事で紹介します。

onSaveInstanceState()メソッド

このメソッドはonStop()の後に呼び出されます。UIのインスタンスの状態をBundleオブジェクトを使って保存した場合に機能します。一部のビューには復元機能が備わっていて例えばEditTextビュー(入力を促す時に使うテキストボックス)にテキストを入力した時にその値は次回ユーザーがアプリを起動した時に復元されます。因みにテキストビューは保存機能はないです。メンバ変数も破棄されてしまうので保存する処理が必要です。

onSaveInstanceState()で保存されたデータはonCreate()でsavedInstanceStateのパラメーターを受けとります。このパラメーターはbundleオブジェクトの状態を判定します。この判定を元に保存したデータを表示する処理をするとインスタンス状態が復元できます。最初の起動時のパラメーターはnullになります。

onSaveInstanceState()とsavedInstanceStateの相関図

onRestoreInstanceState()メソッド

onRestoreInstanceState()メソッドは画面回転やシステムによって破棄された場合のみonStart()の後で呼ばれます。殆どの実装ではonCreate()でインスタンス状態を復元する処理をしますが、縦向き・横向きでレイアウトファイルが違う場合はonCreateで画面の向きの状態を取得してからonStart()の後で呼ばれた時に処理した方が都合が良い場合もあります。

このメソッドもonCreate()と同じくsavedInstanceStateパラメーターを受けとります。onCreate()は初回起動時にも呼ばれるのでBundleオブジェクトがnullかどうかで判定して処理を書きますが、このメソッド内で処理する場合はnull判定しなくて大丈夫です。

onRestoreInstanceStateが呼びだされるタイミング

画面回転時のActivityが破棄された時のライフサイクル

画面回転によってアクティビティが破棄された時は再作成されます。その後、画面を元に戻すと再び再作成されます。

複数のActivityでの移動と移動時のデータ

アクティビティ間を移動させるにはIntentを介して構成します。Intentはアクティビティやサービスなどを繋げる役割を持ちます。通常はAのアクティビティからBのアクティビティへ移る時はAを終了させてからBに遷移する処理をします。この時にメンバ変数は保持されないので何らかの保存する処理が必要になります。Intentにはbundleオブジェクトが備わっていて小さいデータであればアクティビティ間のデータのやり取りが可能です。他にもViewModel、ローカル・ネットワークストレージ、データベースに保存する方法があります。

複数のアクティビティを使う場合は、画面遷移の為の保存破棄された場合の保存の2つの保存処理が必要になる訳です。

タスクとバックスタックとライフサイクルの関係

システムの内部的な話になるのであまり気にしなくて良いと思いますが一応書いておきます。タスクはアプリととそのアクティビティをどの順番で実行させるかなど管理するユニットになります。スタック(バックスタック)はアクティビティを保存しておく配列になります。1つのタスクのスタックにアクティビティが何個も入ってる感じです。タスクが作業員、スタックがアプリのラベルが貼られたカードケース、アクティビティがカードだとすると、作業員がカードケースを棚へ出し入れしてるみたいなイメージを持ってくれると分りやすいかも。

まずアプリが新たに起動すると新しいラベルとカードケースが作られメインアクティビティはカードケースに入ります。ユーザーがホームボタンを押すとタスク君は棚にカードケースを置きます。再びアプリが再開されると棚からカードケースを持って来ます。カードケースの一番手前にあるカードがアクティブになります。

ユーザーが他のアクティビティBを開くとカードケースにアクティビティカードが追加されます。この時に手前にあるカードは奥に追いやられてしまいます。更にアクティビティCを開くと手前からC→B→Aと並びます、停止やや一時停止になるとその状態のまま棚に置かれます。ユーザーがアプリに戻るとまた棚から取り出します。この時にアクティブなのは手前にあるカードのアクティビティCになります。

アクティビティが破棄されるとカードケースからCが削除されます。この状態でユーザーが戻るボタンを押した場合はBが破棄されます。逆に再びCを開くとカードが手前に追加されます。戻るを3回押すとC→B→Aの順にカードが破棄されます。

アクティビティがなくなるとおそらく、カードケースとラベルも捨てられます。タスク君は休憩してるんじゃないでしょうか。スタックは先入れ後出し方式で入れ換えられることはありません。

タスクとバックスタックとActivityライフサイクルの関係図

Activityの設定をするマニフェストファイル

マニフェストファイルは、アプリに関する設定をするファイルです。Android Studioだと[manifests/AndroidManifest.xml]ファイルになります。

このファイルはビルドツール、Android OS、Google Playに対して使われます。下記のコンポーネントを使う場合は設定する必要があります。

  • アクティビティ
  • サービス
  • ブロードキャスト レシーバ
  • コンテンツ プロバイダ

アクティビティの最低限宣言しないといけない属性は下記になりますが、Android Studioではプロジェクトを新規作成すると書かれています。アクティビティを追加した場合はname属性を追記しますがこれも自動で追加されています。

<activity android:name=".MainActivity">

アクティビティの属性は回転した時破棄しない設定など色々あります。一杯有りすぎて説明出来ないので詳しくはAndroidデベロッパーのマニフェストactivityへどうぞ。