Androidのアクティビティライフサイクル
画面回転で状態が消える不具合はライフサイクルの誤解が原因。コールバックの意味とonSaveInstanceState、ViewModelの役割を正確に押さえれば防げる。
- 1.Activityは onCreate/onStart/onResume/onPause/onStop/onDestroy の順で遷移し、システムがフォアグラウンド維持のためにいつでもプロセスを終了できる前提で設計する。
- 2.画面回転などのconfiguration changeは既定でActivityを破棄・再生成する。onSaveInstanceStateはUI状態の小さなスナップショット用で、大きなデータや進行中の非同期処理の保持には向かない。
- 3.ViewModelはActivity再生成をまたいで生き残るため、configuration change対策の主役はViewModel、プロセスキル対策の主役はonSaveInstanceStateやSavedStateHandleと役割が分かれる。
なぜライフサイクルを正確に理解する必要があるのか
Androidのアプリは、開発者が思うほど自分のプロセスを制御できません。ユーザーが他のアプリに切り替えた瞬間、あるいはメモリが逼迫した瞬間、システムはバックグラウンドのActivityを予告なく終了させます。さらに画面回転やダークモード切り替えのような些細な操作でも、既定ではActivityそのものが一度破棄されて作り直されます。この「いつ何が起きるか分からない」環境で状態を失わないためのルールが、アクティビティライフサイクルです。単なるコールバックの暗記ではなく、OSがなぜこの設計を選んだのかを理解すると、状態管理の判断を誤らなくなります。
6つのコールバックが表す状態遷移
Activityのライフサイクルは、onCreate・onStart・onResume・onPause・onStop・onDestroy という6つのコールバックで区切られます。これらは3つの「生存期間」に対応します。
| 期間 | 開始〜終了 | 意味 |
|---|---|---|
| Entire lifetime | onCreate 〜 onDestroy | Activityオブジェクトが存在する全期間。setContentViewなど一度きりの初期化はここ |
| Visible lifetime | onStart 〜 onStop | 画面に見えている期間(フォアグラウンドとは限らない)。UI更新の購読開始/停止はここ |
| Foreground lifetime | onResume 〜 onPause | ユーザーと直接やり取りできる期間。カメラやセンサーなど占有リソースの確保/解放はここ |
onCreateはActivityインスタンスが生成された直後に一度だけ呼ばれ、レイアウトの構築やViewModelの取得を行います。onStartで画面が可視になり、onResumeでフォーカスを得てユーザー操作を受け付け始めます。ここまでが起動側の遷移です。
終了側は逆順ではなく非対称です。他のActivityが前面に来る、あるいはユーザーがホームに戻るとonPauseが呼ばれ、続けて画面が完全に隠れるとonStopが呼ばれます。onPauseは非常に短時間で完了させる必要がある点が重要です。次のActivityのonResumeは、現在のActivityのonPauseが完了するまで開始されないため、ここで重い処理を行うとアプリ全体の応答性が落ちます。永続化やアニメーション停止など軽い処理に留め、重い保存処理はonStop側に置くのが原則です。
onPause止まりで復帰する典型例はダイアログ表示や通知シェードを開いた場合です。この場合はActivityが部分的に見えており、プロセスが終了される可能性も低いままです。一方onStopまで進むのは他アプリへの完全な切り替えなどで、画面が全く見えなくなった状態です。ここまで来るとシステムによるプロセス終了の対象になり得ます。
onDestroyは明示的なfinish()呼び出し、あるいはconfiguration changeによる再生成の際に最終的に呼ばれます。ただし後述の通り、プロセスキル時にはonDestroyすら呼ばれずにプロセスごと消えることがあります。
configuration changeによる再生成
画面回転、言語設定変更、ダークモード切り替え、キーボード種別の変化などは「configuration change」と呼ばれ、既定の挙動としてシステムは現在のActivityをonPause → onStop → onDestroyで破棄し、新しいConfigurationを持つ全く別のActivityインスタンスをonCreateから作り直します。
これは事故ではなく設計判断です。回転後のレイアウトは、文字列リソースやdpベースの寸法が異なる値セット(values-landなど)から再解決される必要があり、既存のViewツリーを微調整するより「作り直す」方が一貫性を保ちやすいという考え方に基づきます。
この挙動には重要な帰結があります。onCreate内でメンバ変数に代入しただけの状態は、再生成のたびに初期値へ戻ります。ネットワーク取得結果やユーザーが入力途中のフォーム内容をメンバ変数だけに置いていると、回転一つで消えます。
マニフェストでandroid:configChanges="orientation|screenSize"を指定すると再生成を止め、代わりにonConfigurationChangedが呼ばれるだけになります。一見便利ですが、リソース再解決やレイアウトの再計算を自前で行う責任が開発者に移り、対応漏れが起きやすくなります。多くの構成変更(フォントスケールなど)はこの方法では捕捉しきれないため、標準的には再生成を受け入れた上でViewModelとonSaveInstanceStateで状態を守る設計が推奨されます。
プロセスキルと onSaveInstanceState
configuration changeとは別に、より厳しい状況として「プロセスキル」があります。ユーザーがバックグラウンドに送ったActivityは、システムがメモリ確保のためにActivityインスタンスごとプロセスを終了させる対象になります。この場合onDestroyさえ呼ばれない可能性があり、メンバ変数もViewModelもプロセスと運命を共にして消えます。
この状況に備える仕組みがonSaveInstanceState(Bundle)です。呼び出しタイミングはAPIレベルで変わっており、Android 9(API 28)以降はonStopの後に呼ばれるよう変更されました(それ以前はonStopの前、onPauseの前後という順序でした)。渡されたBundleはシステムプロセスの外(ActivityManager側)に保持されます。ユーザーが「戻る」操作などでアプリに戻ってきた際、新しいActivityインスタンスのonCreateとonRestoreInstanceStateにこのBundleが渡され、UI状態を復元できます。
| 観点 | onSaveInstanceState | ViewModel |
|---|---|---|
| 生き残る範囲 | configuration change、プロセスキルの両方 | configuration changeのみ(プロセスキルには無力) |
| 適したデータ | スクロール位置や入力中のテキストなど小さいUI状態 | 取得済みデータ、進行中の非同期処理の参照 |
| サイズ制約 | Bundleはプロセス間通信(Binder)経由のため数百KB程度が実務上の上限 | 同一プロセス内の参照保持なので実質無制限 |
| 書き込みタイミング | onStop前後(API 28以降はonStop後)にシステムが呼ぶ(明示的なsave操作は不要) | 生成は初回のみ。以降は同一インスタンスを再取得するだけ |
つまり両者は排他ではなく補完関係にあります。ViewModelは再生成のたびに走る重い初期化処理(APIコール、DB読み込み)を1回に減らす役割を担い、onSaveInstanceStateはViewModelごと消えるプロセスキルからでも最低限のUI状態(現在のタブ、スクロール位置、フォームの下書きなど)を復元する最後の砦として働きます。
ViewModelがライフサイクルを生き延びる仕組み
ViewModelが回転後も同一インスタンスであり続けるのは魔法ではなく、保持先が異なるためです。Activityが再生成される際、ViewModelProviderはまずViewModelStoreを探しに行きますが、このViewModelStore自体はonRetainNonConfigurationInstanceの仕組みを通じて、破棄される直前の古いActivityから新しいActivityへ引き継がれます。Activityインスタンス自体は毎回新しく作られても、ViewModelStoreという保管箱だけがすり替えられずに渡されるため、その中のViewModelインスタンスは同一のまま維持されます。
一方でこの保管箱はActivityが本当に終了するとき(finish()呼び出しやプロセスキル)には解放され、ViewModel.onCleared()が呼ばれます。この境界を区別するためにisChangingConfigurations()のようなAPIがあり、システムは「再生成のための破棄」と「本当の終了」を内部的に見分けています。
「回転で入力内容が消えた」という不具合報告があれば、まず切り分けます。ViewModelに状態を持たせているのに消えるなら、そもそもViewModelを介さずメンバ変数に直接持たせている実装ミスを疑います。ViewModel対応済みでもアプリをバックグラウンドで長時間放置後に戻ると消えるなら、それはconfiguration changeではなくプロセスキルであり、対策はonSaveInstanceStateまたはSavedStateHandle(ViewModelにBundle相当の永続キーを紐付ける仕組み)の導入です。原因のレイヤーを取り違えると効果のない修正を重ねることになります。
まとめ
Androidのライフサイクルは、限られたメモリの中で複数アプリを同時に生かすというOS側の都合を、開発者が受け入れるための契約です。onCreateからonDestroyまでの6コールバックは単なる通知ではなく、初期化・可視化・フォーカス取得それぞれの責任範囲を区切る境界線であり、onPauseを軽く保つことは前面アプリの応答性に直結します。configuration changeによる再生成とプロセスキルは似て非なる脅威で、前者はViewModelが、後者はonSaveInstanceState(およびSavedStateHandle)がそれぞれ異なる保存先を用いて対処します。この二層構造を正確に切り分けて設計することが、回転や中断に強いAndroidアプリの土台になります。
モバイル開発 Article
Androidのアクティビティライフサイクルを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Android
比較で見る軸
難易度: advanced / カテゴリ: モバイル開発 / タグ数: 5
導入後に効く点
画面回転などのconfiguration changeは既定でActivityを破棄・再生成する。onSaveInstanceStateはUI状態の小さなスナップショット用で、大きなデータや進行中の非同期処理の保持には向かない。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- モバイル開発
- タグ数
- 5
判断チェックリスト
- 自社の用途が「Android / モバイル開発」に近いか確認する。
- 強みである「Activityは onCreate/onStart/onResume/onPause/onStop/onDestroy の順で遷移し、システムがフォアグラウンド維持のためにいつでもプロセスを終了できる前提で設計する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。