onPause: AndroidでのActivity状態保存とは

著者: IT Sectr 公開日: 2026-03-04 読了時間: 10 分

onPauseはAndroidのライフサイクルメソッドで、Activityが入力フォーカスを失ったものの、画面上に部分的に表示されたままになっているときに呼び出されます。システムは、新しいActivityがフォアグラウンドに表示される前、ダイアログが開かれたとき、最近使ったアプリボタンが押されたとき、または着信があったときにonPauseを呼び出します。このメソッドはユーザーデータを保存するための最後の保証されたポイントであり、onStopとonDestroyの後、システムは追加の呼び出しなしにプロセスを終了できます。onPause内で、開発者は下書きを保存し、アニメーションを一時停止し、カメラを解放し、現在のUI状態をSharedPreferencesに書き込みます。完全なActivityライフサイクルの詳細については、記事Activity Lifecycleをお読みください。

重要なポイント

  • onPause — Activityはフォーカスを失うが表示されたまま; データ保存の最後の保証されたポイント
  • 状態の保存 — onPauseで重要なユーザーデータを保存: 下書き、フォームテキスト、進捗状況
  • リソースの解放 — カメラ、マイク、ビデオプレーヤーをonPauseで解放し、別のアプリに渡す
  • 時間制限 — onPauseは100ミリ秒以内に完了する必要あり; 超過するとANRが発生し遷移が遅延
  • SharedPreferences.apply() — onPauseでの非同期書き込み; commit()はスレッドをブロックしANRの原因に
  • onPause vs onStop — onPauseは部分的可視時(ダイアログ)、onStopは完全非表示時(別のActivity)
  • onSaveInstanceState — onPause後に呼び出され、一時的な状態をBundleに保存

AndroidのonPauseとは

onPauseはActivityライフサイクルの4番目のメソッドで、画面が入力フォーカスを失うものの、ユーザーに部分的に表示されたままになっているときに呼び出されます。これはアプリがアクティブに実行されている状態と非表示の間の「遷移」状態です。システムは次のシナリオでonPauseを呼び出します: 別のActivityが開く(新しい画面が現在の画面を部分的に覆う)、ダイアログウィンドウが表示される(Dialog、PopupWindow、SnackbarはonPauseをトリガーしませんが、DialogFragmentはトリガーします)、最近使ったアプリボタンを押す、着信、画面をロックするために電源ボタンを押す。

onPauseの主な目的は、アプリが非表示または破棄される可能性に備えることです。これはライフサイクルの中で、システムが別のコンポーネントへの遷移を進める前に、開発者が自分のコードが確実に実行されることを確認できる最後のポイントです。onPauseの後、システムはonStopを呼び出し(Activityが完全に非表示になった場合)、その後プロセスは追加の通知なしにいつでも終了される可能性があります。

Android Developersのドキュメント(2025)によると、onPauseは可能な限り軽量で高速であるべきです。onPauseが制御を返すまで、システムは次のActivityを開始できません — つまり、ユーザーは画面遷移の遅延を目にすることになります。Googleは100ミリ秒未満でonPauseを完了することを推奨しており、すべての長時間操作(データベースへの保存、ディスクへの書き込み)はコルーチンまたはapply()を介して非同期に実行する必要があります。

ActivityのonPause

Activityでは、画面がアクティブでなくなるたびにonPauseメソッドが呼び出されますが、部分的に表示され続けることがあります。典型的な例: ユーザーがマップアプリを開き、位置情報を共有をタップすると、マップの上にシステムのアプリ選択ダイアログが表示されます。マップのActivityはonPauseを受け取りますが、ダイアログの下に表示されたままです。ダイアログが閉じられると、マップはonStartが呼び出されずにonResumeを受け取ります(画面は完全に非表示になっていませんでした)。

kotlin
class NoteEditorActivity : AppCompatActivity() {
    private var binding: ActivityNoteEditorBinding? = null
    private val prefs by lazy {
        getSharedPreferences("note_drafts", Context.MODE_PRIVATE)
    }

    override fun onPause() {
        super.onPause()

        // メモの下書きを保存 — 非同期
        prefs.edit()
            .putString("draft_title", binding?.titleInput?.text.toString())
            .putString("draft_body", binding?.bodyInput?.text.toString())
            .putLong("draft_timestamp", System.currentTimeMillis())
            .apply()

        // 動画を一時停止
        binding?.videoPlayer?.pause()

        // 排他リソースを解放
        releaseCamera()
        releaseAudioFocus()
    }

    override fun onResume() {
        super.onResume()
        // 下書きを復元
        binding?.titleInput?.setText(prefs.getString("draft_title", ""))
        binding?.bodyInput?.setText(prefs.getString("draft_body", ""))
        acquireCamera()
        acquireAudioFocus()
    }
}

NoteEditorActivityの例は、onPauseの正しい処理を示しています: apply()を介したSharedPreferencesへの下書き保存、ビデオファイルの一時停止、カメラとオーディオフォーカスの解放。各呼び出しは軽量かつ高速で、ANRを引き起こすほどUIスレッドをブロックしません。順序に注意してください: super.onPause()が最初の行で呼び出されます — これにより、ユーザーコードで例外が発生した場合でもシステムロジックが確実に実行されます。

onPauseでの状態保存

onPauseは、アプリが非表示にされたりシステムによって強制終了されたりする前に、開発者がユーザーデータを確実に保存できる最後のポイントです。onStopの後、システムはメモリ不足の場合にonDestroyを呼び出さずにプロセスを終了することがあります。onSaveInstanceState()メソッドはonPauseの後に呼び出されますが、そのBundleは長期間の保存を目的としていません — 次のonCreateまでしか生存しません。

apply()を使ったSharedPreferences

非同期のapply()を使ったSharedPreferencesは、onPauseで少量のデータを保存する最適な方法です。同期的にデータをディスクに書き込んでブール値を返すcommit()とは異なり、apply()は即座にデータをメモリに保存し、非同期のディスク書き込みをスケジュールします。これにより、commit()の10〜100ミリ秒に対して、UIスレッドでの所要時間は1ミリ秒未満です。

kotlin
override fun onPause() {
    super.onPause()

    // ❌ 悪い: 同期書き込みがスレッドをブロック
    // prefs.edit().putInt("score", score).commit()

    // ✅ 良い: 非同期書き込み
    prefs.edit().putInt("score", score).apply()

    // 複雑なオブジェクトの場合 — ViewModelでのキャッシュ
    viewModel.saveState()
}

Roomとコルーチン

onPauseでの構造化データ(Room経由のSQLite)には、lifecycleScopeを使ったコルーチンを使用します。ViewModelScopeはViewModelが破棄されると自動的にコルーチンをキャンセルし、閉じたデータベースへの書き込みを防ぎます。コルーチンを使ったRoomでの書き込みは5〜15ミリ秒かかり、UIスレッドをブロックしません。

kotlin
// ViewModel内:
fun saveDraft(title: String, body: String) {
    viewModelScope.launch(Dispatchers.IO) {
        noteDao.insert(NoteDraft(title = title, body = body))
    }
}

// Activity.onPause内:
viewModel.saveDraft(
    binding?.titleInput?.text.toString(),
    binding?.bodyInput?.text.toString()
)

FragmentのonPause

FragmentのonPauseは、Fragmentがアクティブでなくなったものの、表示されたままになる可能性があるときに呼び出されます。これは次の場合に発生します: FragmentがFragmentTransactionを介して別のFragmentに置き換えられたとき、FragmentがViewPagerの現在のページでなくなったとき、Fragmentを含むActivityがonPauseを受け取ったとき。ActivityのonPauseとFragmentのonPauseの間の相互作用は厳密に階層的です: 最初にActivityがonPauseを受け取り、次にそのすべてのFragmentが受け取ります。

kotlin
class MapFragment : Fragment() {
    private var mapController: MapController? = null

    override fun onPause() {
        super.onPause()
        mapController?.stopFollowMode()
        binding?.mapContainer?.alpha = 0.7f
    }

    override fun onResume() {
        super.onResume()
        binding?.mapContainer?.alpha = 1.0f
        if (isVisible) {
            mapController?.startFollowMode()
        }
    }
}

onPauseでのマップ操作の詳細: Google MapsとYandex Mapsは、アクティブなフォローモードでかなりのGPUリソースを消費します。フォーカスを失った場合、マップアニメーションを無効にし、マーカーの更新頻度を減らすことが合理的であり、フォーカスが戻ったら完全な機能を復元する必要があります。これにより、画面切り替え時のパフォーマンスが向上し、消費電力が削減されます。

onPause vs onStop: 違いとシナリオ

初心者のAndroid開発者にとって最も一般的な混乱の1つは、onPauseとonStopの違いを理解していないことです。各シナリオを検証し、正しいメソッドを判断しましょう。

シナリオonPauseonStop
ダイアログウィンドウを開く呼び出される呼び出されない
新しいActivity(非透過)を開く呼び出される呼び出される
ホームボタンを押す呼び出される呼び出される
画面ロック呼び出される呼び出される
着信呼び出される呼び出される
透過的なActivityが重なる呼び出される呼び出されない
分割画面(画面半分)呼び出される呼び出されない
PiP(ピクチャーインピクチャー)呼び出される呼び出されない

主なルール: onPauseはフォーカスが失われるたびに呼び出され、onStopは可視性が完全に失われた場合にのみ呼び出されます。Activityが表示されたままの場合(部分的であっても)、onStopは呼び出されません。これは分割画面、PiP、透過的なActivityモードにとって非常に重要です — ここではonPause/onResumeは機能しますが、onStart/onStopは機能しません。

onPauseのタイミングとパフォーマンス

onPauseは、次のActivityのレンダリングをブロックするため、最も時間的に重要なライフサイクルメソッドです。システムは新しいActivityを表示する前に、現在のActivityのonPauseが完了するのを待ちます。onPauseが100ミリ秒以上かかると、ユーザーは遷移の遅延に気づきます; 5秒以上かかると、システムはANRを表示します。

パフォーマンスに関する推奨事項

Google Android Performance Guide(2025)は、onPauseに関して次の推奨事項を提供しています: ネットワークリクエストを実行しない — キャンセルするかWorkManagerに移動する; 大きなファイルをディスクに書き込まない — バックグラウンドスレッドでBufferedWriterを使用する; 複雑なSQLクエリを実行しない — Room操作はコルーチンを介して非同期にする; 新しいオブジェクトの作成を避ける — onPauseでのガベージコレクションは遅延を悪化させる; SharedPreferencesにはcommit()ではなくapply()を使用する。

kotlin
override fun onPause() {
    super.onPause()

    // ❌ 悪い: HTTPリクエストがUIをブロック
    // val response = api.syncSave(data).execute()

    // ❌ 悪い: ファイルへの同期書き込み
    // FileOutputStream(file).write(data)

    // ✅ 良い: 非同期保存
    lifecycleScope.launch {
        withContext(Dispatchers.IO) {
            api.saveData(data)
            fileDao.write(data)
        }
    }

    // ✅ 良い: SharedPreferencesへの軽量書き込み
    prefs.edit().putString("key", value).apply()
}

Android Studio Profiler(CPUトレース)によるonPauseのプロファイリングは、正確な実行時間を示します。onPauseが100ミリ秒を超えると、Profilerはメソッドを黄色で強調表示し、500ミリ秒を超えると赤色で強調表示します。IT Sectrの商用プロジェクトでは、Macrobenchmarkテストを使用して、Activity間の遷移時間を自動的にチェックし、CIパイプラインでのパフォーマンス低下を通知しています。

onPauseのよくある間違い

経験豊富な開発者でもonPauseで間違いを犯します。5つの典型的な問題とその解決策を見てみましょう。

同期的なデータベース書き込み

onPauseで同期的なクエリ(.executeAsObservable()、コルーチンなし)でRoom DAOを呼び出すと、UIスレッドが10〜50ミリ秒ブロックされます。同時にGCや書き込みの競合が発生すると、遅延が200〜500ミリ秒に達する可能性があります。解決策: Dispatchers.IOを使ったコルーチン、またはSharedPreferencesにはapply()を使用します。

新しいリスナーの登録

onPauseはリスナーを登録する場所ではありません。onPauseでBroadcastReceiverを登録すると、Activityが表示されなくなってもアクティブなままになります。登録はonStart/onResumeでのみ行い、onPause/onStopでは登録解除のみを行うべきです。例外は、呼び出し前に登録が必要なIntent駆動のAPIです。

例外の無視

onPauseで未処理の例外が発生すると、システムはonStopとonDestroyを呼び出しません。Activityは未定義の状態で停止し、戻ったときのonResumeで解放されたリソースが正しく復元されない可能性があります。解決策: Log.e()によるログ記録とともに、重要な操作をtry/catchでラップします。

冗長なデータの保存

簡単に復元できるデータをonPauseで保存する必要はありません。たとえば、APIリクエストの結果は、onPauseではなく、取得時にRoomやDataStoreにキャッシュします。ユーザーが手動で入力し、自動的に復元できないものだけを保存します — フィールドのテキスト、選択したアイテム、スクロール位置などです。

super.onPause()の忘れ

super.onPause()は呼び出すべきですが、onCreateとは異なり、省略しても即座にクラッシュはしません。システムはonPauseでのsuperの欠如を「許容」しますが、内部のステートマシンが不正な状態になります。次のonResume呼び出しで入力フォーカスが復元できず、Activityが「フリーズ」したままになる可能性があります。常に可能な限り早くsuper.onPause()を呼び出してください。

よくある質問

onPauseでfinish()を呼び出すとどうなりますか?

onPauseでfinish()を呼び出すと、メソッドから戻った直後にActivityが終了します。これは、フォーカスを失ったときに画面を閉じる必要がある場合(たとえば、アプリを最小化したときの認証画面など)の正当なシナリオです。ただし、finish()は完全な終了サイクル(onStop onDestroy)をトリガーし、遷移に遅延を追加します。onPauseでのfinish()は、本当に必要な場合にのみ使用してください。

onPauseとonSaveInstanceStateの違いは何ですか?

onPauseはプロセス終了後も存続すべきデータを保存するためのものです(SharedPreferences/Roomへの下書き)。onSaveInstanceStateは次のonCreateまでのみ必要な一時的なUI状態を保存するためのものです(スクロール位置、選択されたタブ)。onSaveInstanceStateのBundleはアプリが完全に終了したときには保存されません — メモリにのみ存在します。onPauseのデータはディスクに保存され、再起動後も存続します。

onPauseでダイアログを開くことはできますか?

推奨されません。onPauseでダイアログやポップアップを開くと、Activityがすでに終了している場合にWindowLeakExceptionが発生します。フォーカスを失ったときに通知を表示する必要がある場合は、NotificationManager(システム通知)を使用してください — これは安全で、ユーザーにとって期待される動作です。遅延したアクションには、AlarmManagerまたはWorkManagerを使用してください。

なぜonPauseは保証された保存ポイントなのに、onStopはそうではないのですか?

onPauseはActivityがアクティブでなくなる前に確実に呼び出されます。システムがメモリを解放するためにプロセスを強制終了した場合、onStopは呼び出されない可能性があります — この場合、onDestroyも呼び出されません。onPauseはonResumeの後に、フォーカスを失った理由に関係なく常に呼び出される唯一のメソッドです。したがって、すべての重要なデータは正確にonPauseで保存されます。

ユニットテストでonPauseをテストするには?

onPauseのテストには、RobolectricまたはAndroidX TestのFragmentScenarioを使用します。FragmentScenario.create() moveToState(State.STARTED) moveToState(State.RESUMED) moveToState(State.STARTED)が順次onPauseを呼び出します。その後、データがSharedPreferencesに保存されたか、モックオブジェクトを介してカメラが解放されたかを検証します。Robolectric 4.12+は、物理デバイスなしでonPause/onResumeのエミュレーションをサポートしています。

まとめ

  • onPause — Activityは入力フォーカスを失うが部分的に表示されたまま; データ保存の最後の保証されたポイント
  • 保存 — SharedPreferences.apply()またはコルーチン経由のRoom; commit()と同期操作は禁止
  • リソース解放 — カメラ、オーディオフォーカス、ビデオプレーヤーはonPauseで解放され別のアプリに渡される
  • 100ミリ秒制限 — onPauseは次のActivityのレンダリングをブロック; 制限超過でANRが発生
  • onPause vs onStop — onPauseはフォーカス損失時(可視性保持)、onStopは完全非表示時
  • Fragment.onPause — Activity.onPause後の階層的呼び出し; マップとViewPagerの詳細
  • よくある間違い — 同期書き込み、リスナー登録、try/catchの無視、冗長な保存
  • super.onPause() — できるだけ早く呼び出す; 省略してもクラッシュしないがステートマシンが壊れる

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください