Warm Start は、アプリケーションプロセスが既にメモリに存在している(例えば、最小化後)が、リソース節約のためにActivityがシステムによって破棄されたAndroidアプリ起動シナリオです。Application.onCreateは既に実行され、クラスはロードされていますが、UIは新たに作成されます。Google、2024によると、Warm Startは200〜800ミリ秒かかり、4GB RAMのデバイスでの全起動の約40%を占めます。
重要なポイント
Warm Start はCold StartとHot Startの中間の状態です。アプリプロセスはメモリに存在しますが(Linuxのバックグラウンドキャッシュにあることもあります)、Activityはアクティブではなく新たに作成されます。AndroidのRAMが不足すると、プロセスを生かしたままスタックからActivityを削除することがあります。ユーザーがアプリに戻ると、Warm Startが発生します。新しいActivityインスタンスが作成され、ライフサイクルメソッドonCreate → onStart → onResumeが実行されますが、Application.onCreateとクラスロードはスキップされます。
Androidはプロセスの優先度(重要度ランク)に基づいてActivityを削除するかどうかを決定します。バックグラウンドのActivity(レベルPROCESS_STATE_IMPORTANT_FOREGROUNDまたはPROCESS_STATE_TOP_SLEEPING)は、利用可能なRAMに応じて、アプリ最小化後5〜30分で破棄される可能性があります。3GB RAMのデバイスではActivityは10分以内に削除され、8GB RAMのデバイスでは数時間後になります。重要なのは、Warm Start中はonSaveInstanceStateがActivity破棄前に呼び出され、開発者がUI状態を保存できることです。
ユーザーはWarm StartとCold Startの違いを認識しません — 単にアプリアイコンをタップして待つだけです。ただし、Warm Start中、アプリがカスタム起動ウィンドウテーマを設定していない場合、白い画面が表示されることがあります。Googleは、Warm Start中の白/黒画面のちらつきを防ぐために、マニフェストで起動Activityにカスタムテーマ(Theme.AppCompat.LightまたはTheme.Material3.DayNight)を設定することを推奨しています。Android 12+では、SplashScreen APIもこの効果を隠します。
3つの起動タイプの違いを理解することは、適切なプロファイリングと最適化戦略を選択するために不可欠です。各タイプには独自の所要時間、ボトルネック、測定ツールがあります。
| 基準 | Cold Start | Warm Start | Hot Start |
|---|---|---|---|
| プロセス | 新規作成 | メモリに存在 | メモリに存在 |
| Application.onCreate | 実行される | 実行されない | 実行されない |
| Activity | 新規作成 | 新規作成 | スタックから復元 |
| 時間 | 1〜5秒 | 200〜800ミリ秒 | < 200ミリ秒 |
| Activity.onCreate | 完全 | 完全(復元あり) | スキップ |
実際には、Warm Startはユーザーの習慣とデバイスのRAMに応じて、全アプリ起動の30%から60%を占めます。多くのアプリを開いたままにするユーザー(マルチタスカー)はWarm Startに遭遇する頻度が高くなります。ソーシャルネットワークやメッセンジャーでは、アプリが常にバックグラウンドにあるため、Warm Startが最も一般的なシナリオです。銀行アプリでは逆に、Cold Startが優勢です(セキュリティ上の理由による強制的なプロセスクリア)。
Warm Startは3つのフェーズで構成され、それぞれ測定と最適化が可能です。Cold Startとは異なり、フォークフェーズやクラスロードはありませんが、コストがかかる可能性のある状態復元フェーズがあります。
システムは、アプリに起動ウィンドウのテーマがあるかどうかを確認します。テーマが設定されていない場合、白(またはシステムによっては黒)の画面が表示されます。テーマが設定されている場合、テーマの背景が表示されます。このフェーズは10〜30ミリ秒かかりますが、テーマがアプリの実際のUIと一致しない場合、視覚的に目立ちます。最初の画面の背景色に一致するカスタムwindowBackgroundを持つTheme.Material3.DayNightを使用すると、瞬時読み込み効果が生まれます。
システムは、Activityが破棄される前にonSaveInstanceStateで保存されたBundle savedInstanceStateを渡してonCreateを呼び出します。アプリが状態(フィールドテキスト、スクロール位置、ViewModelデータ)を適切に保存していれば、復元は迅速に行われます。そうでない場合、Activityはゼロから開始され、データが読み込まれるまでユーザーはローダーを表示します。重要なポイント:ViewModelオブジェクトは、プロセスが破棄されなかった場合にのみWarm Startを生き残ります — Warm Start中、ViewModelはメモリに残ります。
onCreateの後、onStart → onResumeが実行され、システムが最初の描画をトリガーします。Warm StartのTTFD(Time To First Draw)は、ミッドレンジデバイスで300ミリ秒未満である必要があります。最初の画面に重いViewsを含む複雑なRecyclerViewが含まれている場合、またはネットワークから画像を読み込む場合、TTFDがしきい値を超える可能性があります。最初のフレーム後のスムーズなコンテンツ読み込みには、PlaceholderとShimmerを使用します。
Warm Startの測定はCold Startよりも複雑です。なぜなら、プロセスは生きているがActivityは破棄された状態をシミュレートする必要があるからです。標準の-Sフラグ付きADBコマンドは機能しません — プロセスを強制終了してしまいます。Warm Startには別のアプローチを使用します。
まず、adb shell monkeyでアプリを起動するかアイコンをタップし、最小化します(adb shell input keyevent 3 keyevent HOME)。システムがActivityを削除できるように5〜10秒待ってから、adb shell am start -W(-Sなし)を実行します。コマンドはCold Startよりも短い起動時間を返します。再現性のために、スクリプトを使用します:起動 → 待機 → ホーム → 待機 → 起動。
# ADBによるWarm Startのシミュレーション
$ adb shell am start -W \
com.example.app/.MainActivity
# 出力(Warm Start):
# ThisTime: 412 ms
# TotalTime: 412 ms
# WaitTime: 423 ms
androidx.benchmark.macroライブラリはWarm Startの測定をサポートしています。テストでstartupMode = StartupMode.WARMを設定すると、ライブラリがアプリを起動し、最小化し、待機し(設定可能な遅延)、再起動を測定します。Macrobenchmarkは10〜20回の反復を実行し、パーセンタイルを計算します。CI/CDでは、しきい値を設定できます:P50 Warm Startが600ミリ秒を超えるとテストは失敗します。これにより、コミットごとにリグレッションを追跡できます。
Firebaseは、最後のアプリ終了からの時間に基づいてColdとWarm Startを自動的に区別します。アプリが過去30分以内に開かれた場合、Firebaseは起動をWarmとして分類します。Firebaseコンソールでは、起動タイプごとに個別のグラフが表示され、最適化の効果を評価できます。例えば、ViewModelに状態保存を実装した後、Warm Start時間が30%減少するのが確認できます。
Warm Startの最適化は2つの分野に焦点を当てています:Activity.onCreateの高速化と適切な状態復元です。Application.onCreateとクラスロードは既に完了しているため、主なボトルネックは最初の画面のUIコードです。
保存された状態(savedInstanceState)にデシリアライズが必要なデータ(Bitmap、String、JSON)が含まれている場合は、バックグラウンドスレッドで行います。onCreateでBundleから直接読み取る代わりに、コルーチンを起動してshimmer画面を表示します。実際には、ミッドレンジデバイスでのBundleのデシリアライズには20〜100ミリ秒かかります — 小さく見えますが、Warm Startでは全体の10〜50%を占めます。JetpackのSaved State Moduleを使用すると、ViewModelの状態をBundleまたはデータベースに自動的に保存および復元できます。
XMLレイアウトのインフレーションは、Warm Startで最もコストのかかる段階の1つです。最初の画面がAppBar、CollapsingToolbar、NestedScrollViewと3つのRecyclerViewを持つ複雑なCoordinatorLayoutを使用している場合、インフレーション時間は300ミリ秒に達する可能性があります。解決策:フラットな階層にはConstraintLayoutを使用し、起動時に表示されないセクション(ボトムシート、ダイアログ)にはViewStubを適用し、AsyncLayoutInflaterを介して重いフラグメントの非同期インフレーションを有効にします。Jetpack Composeではインフレーションは不要ですが、Warm Start中のComposeツリーのコンパイルに同程度の時間がかかる場合があります。
Warm Start中、アプリが前回のセッションで読み込んだデータは既にキャッシュにある可能性があります:Roomデータベース、SharedPreferences、ViewModelのインメモリキャッシュ。最初の画面がサーバーからのリストを表示する場合は、起動時にキャッシュを確認し、バックグラウンドでデータを更新します。cache-then-network戦略を使用します:最初にキャッシュされたデータを表示し(瞬時に)、次にサーバーから非同期で更新します。これにより、認識されるWarm Start時間が100〜200ミリ秒に短縮されます。
// Warm Start用キャッシュ付きViewModel
class FeedViewModel : ViewModel() {
private val cache = MutableStateFlow<List<Item>>(emptyList())
init {
// キャッシュ優先、その後ネットワーク
viewModelScope.launch {
cache.emit(db.getItems()) // Warm Start:データは既にDBにあります
cache.emit(api.fetchItems()) // バックグラウンド更新
}
}
}
適切な状態保存は、良いWarm Startと悪いWarm Startを区別する重要な要素です。ユーザーはアプリに戻ったときに、スクロール位置、フィールドのテキスト、選択したタブを含め、自分が残したものを正確に表示することを期待します。
システムはActivityが破棄されているときにonSaveInstanceStateを呼び出しますが、プロセスが強制終了される前です。Bundleには単純なデータ型(String、Int、Parcelable、Serializable)のみが保存されます。複雑なデータの場合は、ViewModelでSavedStateHandleを使用します — Warm Start中にフィールドを自動的に保存および復元します。onSaveInstanceStateとは異なり、SavedStateHandleはプロセスがWarm Startを生き残った場合でも機能します(ViewModelは破棄されません)。例:EditTextのテキストにはSavedStateHandle.getLiveData(“text”)を使用します — テキストは自動的に保存および復元されます。
Warm Start中にプロセスが強制終了されなかった場合、ViewModelはメモリに残り、onClearedは呼び出されません。つまり、前回のセッションで読み込まれたすべてのデータが瞬時に利用可能です。ただし、プロセスが強制終了された場合(デバイスが30分以上ディープスリープ状態)、ViewModelは破棄され、SavedStateHandleとともに新たに作成されます。Warm Start中の正しいViewModel動作のために、あらゆるシナリオで復元する必要のあるフィールドでSavedStateHandleを使用します。違い:@HiltViewModelを使用するViewModelは自動的にSavedStateHandleをサポートします。
| メカニズム | プロセス生存 | プロセス終了 |
|---|---|---|
| ViewModel | メモリ内データ | 破棄、新規作成 |
| SavedStateHandle | メモリ内データ | Bundleから復元 |
| onSaveInstanceState | Activity削除時に呼出 | 呼出なし |
| Room DB | キャッシュ利用可能 | キャッシュ利用可能(ディスク) |
Warm Startの最も一般的な問題の1つ — スクロール位置の消失。ユーザーが50番目の項目までスクロールし、アプリを最小化し、戻ると — リストの先頭が表示されます。解決策:layoutManager.onSaveInstanceStateを保存し(最初の表示項目の位置とオフセットを保存)、onRestoreInstanceStateで復元します。また、最終表示位置を日時キーとともにSharedPreferencesに保存して、Warm Start中に素早く位置を復元することもできます。
// RecyclerViewのスクロール位置を保存
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(
"rv_state", binding.recyclerView
.layoutManager?.onSaveInstanceState()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.getParcelable<Parcelable>("rv_state")
?.let { binding.recyclerView.layoutManager?.onRestoreInstanceState(it) }
}
Warm Start最適化の2つの実践例:ViewModelでのSavedStateHandleの使用と、起動後の複雑なデータの非同期復元。
SavedStateHandleは自動的にフィールドをBundleに保存し、Warm Start中に復元します。ユーザープロファイルフィールド(String、JSON)は、不要なサーバー要求なしで復元されます。プロセスが強制終了された場合、SavedStateHandleはBundleから最後に保存された状態を読み込みます。
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val profile: StateFlow<Profile?>
get() = savedStateHandle
.getStateFlow("profile", null)
fun loadProfile(id: String) {
viewModelScope.launch {
savedStateHandle["profile"] =
api.getProfile(id)
}
}
}
// Warm Start:profileはnullでない、ローダーなしのUI
// 読み込み後:profileはSavedStateHandleで更新
最初の画面に複雑なレイアウト(地図、グラデーション、複数のリスト)が含まれている場合は、AsyncLayoutInflaterを使用してバックグラウンドで重い要素をインフレートします。レイアウトがインフレートされている間、shimmer効果のあるプレースホルダーを表示します。これは、1ミリ秒が重要なWarm Startで特に重要です。AsyncLayoutInflaterはバックグラウンドスレッドで実行され、準備完了したViewをメインスレッドのコールバックに渡します。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 瞬時レンダリングのためのPlaceholderレイアウト
setContentView(R.layout.placeholder_shimmer)
// 重いレイアウトの非同期読み込み
AsyncLayoutInflater(this).inflate(
R.layout.activity_main_complex,
findViewById(R.id.container)
) { view, resId, parent ->
parent?.removeAllViews()
parent?.addView(view)
}
}
}
よくある質問
はい、Warm Startの瞬間にシステムがアプリプロセスを強制終了することを決定した場合(例えば、別のアプリにメモリを解放するため)、起動はゼロからのCold Startになります。これは、複数のアプリが同時に実行されている2〜3GB RAMのデバイスで発生します。実際、ミッドレンジデバイスでは、最小化後10〜20分間のみWarm Startが保証されます。
はい、プロセスが強制終了されなかった場合、ViewModelはメモリに残り、onClearedは呼び出されません。これはWarm Startの重要な利点です:ネットワーク要求で読み込まれたすべてのデータ、ViewModelのキャッシュ — すべてが瞬時に利用可能です。プロセスが強制終了された場合、ViewModelはViewModelProvider.Factoryまたは@HiltViewModelを介して新たに作成され、SavedStateHandleが保存されたフィールドを復元します。
理論的には、Warm Startは常にCold Startより高速ですが、実際には差が最小限になるシナリオがあります:Application.onCreateが軽量(50ミリ秒)でActivity.onCreateが重い(800ミリ秒)場合、Warm Start(800ミリ秒)はCold Start(850ミリ秒)とほぼ同じです。この場合、ApplicationではなくActivity.onCreateを最適化する必要があります — それがWarm Startのボトルネックになります。
Android 12+のSplashScreen APIは、起動時にシステムスプラッシュ(色付き背景にアイコン)を即座に表示します — ColdとWarm Startの両方。Warm Startの場合、スプラッシュは100〜300ミリ秒のみ表示され、その後アプリのUIに置き換えられます。SplashScreenは起動自体を高速化するのではなく、Activity作成時間を隠して、知覚を改善します。
はい、Warm StartはCold Startの2〜3倍頻繁に発生するためです。Cold Startに1.2秒、Warm Startに600ミリ秒かかる場合、起動の40%(Warm)は依然として0.6秒かかり、これは顕著です。Warm Startを200〜300ミリ秒に最適化すると、ユーザーに瞬時の復帰感を与えます。6GB以上のRAMを搭載したデバイスでは、Warm Startが全起動の最大80%を占める可能性があり、その最適化が優先事項になります。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。