Not Running — まだ起動されていないか、すでに終了したモバイルアプリケーションのライフサイクルの初期状態。 iOSおよびAndroidがこの状態をどのように管理するか、どのようなイベントがNot Runningからの遷移を引き起こすか、SwiftおよびKotlinでアプリケーションの起動と終了を正しく処理する方法を学びましょう。
ポイント
Not Running は、モバイルアプリケーションがデバイスのRAMにロードされておらず、システム資源を消費しない、ライフサイクルの基本状態です。 iOSやAndroidでは、この状態はアプリケーションに関連するプロセスやスレッドが完全に存在しないことを意味します。ユーザーはホーム画面にアプリアイコンを見ていますが、アプリケーション自体はアクティブではなく、最近使用したアプリの一覧にもありません。
ユーザーがアプリアイコンをタップすると、システムは新しいプロセスを作成し、実行可能コードをメモリにロードし、必要なデータ構造をすべて初期化します。 このプロセスをコールドスタートと呼びます。ロード時間の観点から最もリソースを消費するプロセスです。
システムは、他のどんな状態からでもアプリケーションをNot Runningに移動できます。アプリケーションがバックグラウンドまたはSuspendedにある場合、オペレーティングシステムはより優先度の高いタスク(例えばフォアグラウンドのアクティブなアプリケーション)のために十分なRAMがない場合、アンロードする権利を有します。
開発者は、アプリケーションがバックグラウンドにあるとき、システムによって何時でも終了される可能性があることを考慮する必要があります。これは、保存されていないすべてのデータが失われる可能性があることを意味します。そのため、ActiveからBackgroundへの遷移中に、キーバリューストア(UserDefaults、SharedPreferences)またはローカルデータベースに状態を保存することが極めて重要です。
iOSは、現在のアプリケーションの状態に基づいて優先度を使用します。Activeが最も高く、次いでInactive、Background、Suspended、そして最後にNot Runningが最も低くなります。Androidは似たようなプロセス階層を使用します。ForegroundプロセスはOOM_ADJ = 0、Visibleプロセス = 100、Serviceプロセス = 200、Backgroundプロセス = 300、Emptyプロセス = 400の優先度です。値が高いほど、メモリが不足しているときにプロセスが終了される可能性が高くなります。
| プラットフォーム | 状態 | アンロード優先度 | 説明 |
|---|---|---|---|
| iOS | Not Running | 最高 | アプリがロードされていない — システムリソースを消費しない |
| iOS | Suspended | 高い | アプリがメモリにあるがコードを実行していない — アンロードの第一目標 |
| iOS | Background | 中程度 | アプリがバックグラウンドタスクを実行中 — タイムアウト後にアンロード |
| iOS | Active | 低い | アクティブなアプリ — メモリの危機的不足時にのみアンロード |
| Android | Empty Process | 最高 | アクティブなコンポーネントのないプロセス — 最初に削除 |
| Android | Background Process | 高い | 見えるActivityのないバックグラウンドプロセス |
| Android | Foreground Service | 低い | 通知ありのサービス — めったに終了される |
| Android | Foreground Process | 最低 | アクティブなActivity — 最後に終了 |
コールドスタートは、アプリケーションがNot Runningから直接Activeに遷移するときに発生します。システムは新しいプロセスを作成し、クラスをロードし、スタティックフィールドを初期化し、メインスレッドを作成し、UIフレームワークを起動します。iOSでは、application(_:didFinishLaunchingWithOptions:)を呼び出し、AndroidではApplication.onCreate()とActivity.onCreate()を呼び出します。コールドスタートの所要時間は、アプリケーションの複雑さに応じて200 msから数秒までの範囲です。
ホットスタート(ウォームスタート) — アプリケーションがSuspended状態にあり、完全な再ロードなしで再開します。システムは最終のUIスタックをメモリから復元し、ユーザーは同じ場所から作業を継続します。 ホットスタートはコールドスタートよりもはるかに高速です。その理由は、コードの大半がすでにメモリにロードされているからです。iOSでは、ホットスタートはapplication(_:didFinishLaunchingWithOptions:)を呼び出さず、applicationWillEnterForegroundとapplicationDidBecomeActiveのみを呼び出します。
コールドスタートとホットスタートの違いは、ユーザーエクスペリエンスにとって重要です。コールドスタート中は、アプリケーションの起動ができるだけ高速に行われるように、開発者が確実にする必要があります。モジュールの遅延初期化、重いリソースの遅延ロード、起動時のメインスレッドでの作業を最小限にすることなどがあります。 Googleはコールドスタートを500 ms以下、AppleはiOSで400 ms以下を推奨しています。
// Androidでのコールドスタート時間の測定
class App : Application() {
private var startTime: Long = 0L
override fun onCreate() {
super.onCreate()
startTime = System.currentTimeMillis()
}
fun getStartupTime(): Long {
return System.currentTimeMillis() - startTime
}
}
// 遅延初期化でActivityを起動
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by lazy {
ViewModelProvider(this).get(MainViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 最初のフレームに必要な最小限のみ
setupNavigation()
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// レンダリング後の重い初期化
initializeHeavyModules()
}
}例は、Androidでのコールドスタート時間の測定を示しています。 Application.onCreate()はNot RunningからActiveへの遷移時に呼び出されます。タイムスタンプはプロセス起動時に記録されます。Activityは、最初のフレームをブロックすることを避けるために、lazyデリゲートを介して遅延初期化を使用します。onPostCreateは、UIがすでにレンダリングされているため、重いモジュールを初期化するのに最適な場所です。
iOSでは、Not RunningはUIApplicationDelegateプロトコルを通して管理されます。主なメソッドは次のとおりです。application(_:didFinishLaunchingWithOptions:)はコールドスタート後に呼び出され、applicationWillTerminate(_:)はユーザーがアプリケーションを終了する前に呼び出されます。しかし、システムはapplicationWillTerminateを呼び出さずにアプリケーションを終了することがあります — 例えば、緊急終了やメモリ足りない場合です。iOSはこのメソッドが呼び出されることを保証しないため、データはapplicationDidEnterBackgroundで保存する必要があります。
ユーザーはApp Switcherでスワイプすることで、マニュアルでアプリケーションを終了できます。システムは、バックグラウンドにある間にアプリケーションをメモリからアンロードできます。アプリケーションがクラッシュする可能性もあります。いずれの場合でも、起動中に作成されたすべてのオブジェクトは破壊されます。 保存されなかった状態は永遠に失われます。 iOS 13+では、状態を保存するためにNSUserActivityまたはUIApplication.stateRestorationIdentifierを介した状態復元メカニズムを使用することをおすすめします。
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// コールドスタート: アプリがNot Runningから遷移
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 最小限のサービスのセットを初期化
setupAnalytics()
configureAppearance()
return true
}
// アプリが終了 — マニュアルクローズのみ
func applicationWillTerminate(
_ application: UIApplication
) {
saveCriticalData()
}
// バックグラウンドに入る前にデータを保存
func applicationDidEnterBackground(
_ application: UIApplication
) {
saveApplicationState()
}
private func saveCriticalData() {
UserDefaults.standard.synchronize()
}
private func saveApplicationState() {
let state = ["lastScreen": "main", "timestamp": Date()]
try? NSKeyedArchiver.archivedData(
withRootObject: state,
requiringSecureCoding: true
)
}
}このコードは、iOSでのNot Runningの正しい処理を示しています。 applicationWillTerminateは、ユーザーがマニュアルでアプリを終了した場合にのみ呼び出されます。重要なデータの保存はapplicationDidEnterBackgroundで二重に行われます。このメソッドは、バックグラウンドに行く前に呼び出されることが保証されているからです。状態復元により、コールドスタート中に後で復元できるようにUIスタックを保存できます。
Androidでは、Not Runningはアプリケーションのプロセスが存在しないことを意味します。Androidの基盤となるLinuxシステムは、Zygoteメカニズムを通してプロセスを管理します。アプリケーションが起動すると、Zygoteは新しいプロセスをフォークし、Dalvik/ARTをロードし、Application.onCreate()を呼び出します。AndroidにはapplicationWillTerminateの直接の似役はありません — システムは予告なしに何時でもプロセスを終了できます。
Activityが初めて呼び出されると、システムはチェーンonCreate → onStart → onResumeを通して、プロセス、Application、Activityを作成します。ユーザーがBackを押すと、Activityは破壊され(onDestroy)、システムによってプロセスが終了される可能性があります。 iOSとの主な違いは、Androidでは、Foreground Serviceが実行中だったり、アクティブなBroadcastReceiverがあったりすると、アクティブなActivityがなくてもプロセスが存続できることです。
// ViewModelのSavedStateHandleを介したNot Runningの処理
class MainViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
companion object {
private const val KEY_LAST_SCREEN = "last_screen"
private const val KEY_USER_DATA = "user_data"
}
fun saveCurrentState(screen: String, data: String) {
savedStateHandle[KEY_LAST_SCREEN] = screen
savedStateHandle[KEY_USER_DATA] = data
}
fun restoreState(): AppState? {
val screen = savedStateHandle.get<String>(KEY_LAST_SCREEN)
val data = savedStateHandle.get<String>(KEY_USER_DATA)
return if (screen != null && data != null) {
AppState(screen, data)
} else null
}
}
// Application — Not Running後の最初のコールバック
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initCrashReporter()
initDependencyInjection()
}
}SavedStateHandleは、Not Runningへの遷移中に状態を自動的に保存し、コールドスタートで復元するAndroid Architecture Componentsのコンポーネントです。ViewModelProviderを通して作成されたViewModelは、画面の回転やActivityの破壊でも生き残ります。プロセスが終了すると、SavedStateHandleからのデータはBundleにシリアライズされ、保存されたインスタンス状態に保存されます。
Not Runningはいくつかの理由で発生します。ユーザーがマニュアルでアプリケーションを閉じる。システムがメモリ不足によりアプリケーションをアンロードする。アプリケーションが例外でクラッシュする。Androidでは、システムが一括アプリ更新やデバイスの再起動中にプロセスを終了することがあります。iOSは、バックグラウンドタスクがタイムアウト(通常30秒)すると、アプリケーションを終了する可能性があります。
| 原因 | iOS | Android | 防止可能性 |
|---|---|---|---|
| ユーザーによるマニュアルクローズ | App Switcherでスワイプ | Recentsからスワイプ | なし — ユーザーのアクション |
| メモリ不足 | メモリ警告が発灯 | onTrimMemory / LMK | 一部可能 — メモリ最適化 |
| アプリのクラッシュ | NSException / シグナル | UncaughtException / ANR | あり — エラー処理とクラッシュレポート |
| バックグラウンドタスクのタイムアウト | バックグラウンドタスクに30秒 | JobSchedulerに10分 | あり — 正しいタスクスケジューリング |
| OSの再起動 | applicationWillTerminateが呼ばれる | Broadcast ACTION_SHUTDOWN | なし — システムイベント |
| アプリ更新 | 発生しない (iOSサンドボックス) | APK更新時にプロセス終了 | なし — システム更新 |
iOSでは、applicationWillTerminateとapplicationDidFinishLaunchingでコンソールログを使用します。起動ごとにUserDefaultsにフラグを追加します — 次回の起動時にフラグがない場合、アプリは不正に終了されたことになります。 Androidでは、ActivityManager.isBackgroundRestricted()を使用してアプリケーションがバックグラウンドタスクを実行できるかどうかを確認します。また、onTrimMemory(TRIM_MEMORY_COMPLETE)を監視します — これはプロセスが終了されることを示すシグナルです。
第一のルール — applicationWillTerminateやonDestroyが呼び出されると仮定しないでください。ActiveからBackgroundへの遷移ごとに、重要なデータを保存してください。単純な設定にはキーバリューストアを、構造化データにはSQLite/Roomを使用します。
第二のルール — コールドスタート時間を測定し、最適化します。遅延初期化、メインスレッドでの作業を最小限にする、リソースの事前読み込み、SplashScreen APIの使用 — これらはすべて起動時間のイメージを向上させます。 Googleは優れたUXのためにコールドスタートを200 ms未満にすることを推奨しています。
第三のルール — 状態復元を実装します。iOSでは、UIApplication.stateRestorationIdentifierとNSUserActivityを使用します。Androidでは、onSaveInstanceStateと組み合わせたViewModelのSavedStateHandleを使用します。これにより、アプリの再起動後にユーザーが同じ場所から作業を継続できます。
第四のルール — Not Running後にアプリケーションが起動されたlaunchOptionsやIntentを処理します。ディープリンク、プッシュ通知、ユニバーサルリンク — これらはすべて起動パラメータを通して伝えられます。開発者は、これらのデータを正しく抽出し、ユーザーを適切な画面にナビゲートする必要があります。
// コールドスタート後のディープリンク処理
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 通知が届いたか確認
if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
handleNotification(notification)
}
// ディープリンクを確認
if let url = launchOptions?[.url] as? URL {
handleDeepLink(url)
}
return true
}
private func handleDeepLink(_ url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let screenId = components.queryItems?.first(where: { $0.name == "screen" })?.value
else { return }
openScreen(screenId)
}このコードは、iOSのコールドスタートでの起動パラメータの処理を示しています。 launchOptionsには、システムがアプリケーションを起動したデータが含まれています。通知、ディープリンク、ユニバーサルリンクは、この辞書を通して伝えられます。開発者は、スムーズなユーザーエクスペリエンスを確保するために、あらゆる可能な起動シナリオを正しく処理する必要があります。
よくある質問
永続ストレージ(UserDefaults、Core Data、SharedPreferences、Room)に保存されたデータは保持されます。RAM内のデータ — 変数、キャッシュ、SavedStateHandleのないViewModelの状態 — は徹底的に失われます。したがって、バックグラウンドへの遷移ごとにアプリケーションの状態を保存することが極めて重要です。
コールドスタート中には、application(_:didFinishLaunchingWithOptions:)が呼び出されます。ホットスタート(Suspendedからの戻り)中には、このメソッドは呼び出されません — applicationWillEnterForegroundとapplicationDidBecomeActiveのみが発火されます。コールドスタートでのみアクションを実行する必要がある場合は、didFinishLaunchingWithOptionsにフラグを設定します。
はい。永続的な通知を持つForeground Serviceは、すべてのActivityが破壊されても、システムがプロセスを終了するのを防ぎます。Background Service(foregroundなしのstartService)は、システムによって何時でも停止される可能性があります。実行中のServiceはプロセスが存在することを意味し、これはもうNot Runningではありません。
iOSシミュレータでは、App Switcher(Cmd+Shift+Hを何度か押し、上にスワイプ)を通してアプリを終了します。Androidエミュレータでは、adb shell am force-stop com.example.appまたはLogcatのStopボタンを使用します。その後、アプリケーションを再び起動します — これがNot Runningからのクリーンなコールドスタートになります。
Kill-switchは、アプリケーションを緊急終了させるためのサーバーコマンドです。バンキングや企業向けアプリで、リモートアクセスをブロックするために使用されます。アプリがkillコマンドを受け取ると、次回のコールドスタート時にUIをブロックし、再認証を要求します。iOSでは、ブロックフラグのリモート通知を介して実装されます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。