Not Running — 移动应用程序生命周期的初始状态,应用程序尚未启动或已完成运行。了解 iOS 和 Android 系统如何管理此状态,哪些事件导致从 Not Running 转换,以及如何在 Swift 和 Kotlin 中正确处理应用程序的启动和终止。
要点
Not Running — 是移动应用程序生命周期的基本状态,应用程序未加载到设备的 RAM 中且不消耗系统资源。在 iOS 和 Android 中,此状态意味着与应用程序相关的进程和线程完全不存在。用户在桌面上看到应用程序图标,但应用程序本身不活动且不在最近列表中。
当用户点击图标时,系统创建一个新进程,将可执行代码加载到内存中并初始化所有必要的数据结构。此过程称为冷启动(cold start),是加载时间方面最消耗资源的过程。
系统可以将应用程序从任何其他状态移动到 Not Running。如果应用程序在后台(Background)或挂起状态(Suspended),操作系统有权在 RAM 不足时卸载它以执行更优先的任务——例如,用于前台的活动应用程序。
开发人员必须注意,应用程序在后台时可能随时被系统终止,这意味着所有未保存的数据都可能会丢失。因此,在从 Active 转换到 Background 时,将状态保存到键值存储(UserDefaults、SharedPreferences)或本地数据库至关重要。
iOS 根据应用程序的当前状态使用优先级:Active 具有最高优先级,然后是 Inactive、Background、Suspended,最后是 Not Running — 最低优先级。Android 使用类似的进程层次结构:前台进程的优先级为 OOM_ADJ = 0,可见进程 = 100,服务进程 = 200,后台进程 = 300,空进程 = 400。值越高,进程在内存不足时被终止的可能性越大。
| 平台 | 状态 | 卸载优先级 | 描述 |
|---|---|---|---|
| iOS | Not Running | 最高 | 应用程序未加载 — 不消耗系统资源 |
| iOS | Suspended | 高 | 应用程序在内存中,但代码不执行 — 卸载的首要目标 |
| iOS | Background | 中 | 应用程序执行后台任务 — 超时后卸载 |
| iOS | Active | 低 | 活动应用程序 — 仅在内存严重不足时卸载 |
| Android | Empty Process | 最高 | 没有活跃组件的进程 — 首先被删除 |
| Android | Background Process | 高 | 没有可见 Activity 的后台进程 |
| Android | Foreground Service | 低 | 带有通知的服务 — 很少被终止 |
| Android | Foreground Process | 最低 | 活跃的 Activity — 最后被终止 |
冷启动(cold start)发生在应用程序从 Not Running 直接转换到 Active 时。系统创建一个新进程,加载类,初始化静态字段,创建主线程并启动 UI 框架。在 iOS 中这意味着调用 application(_:didFinishLaunchingWithOptions:),在 Android 中——调用 Application.onCreate() 和 Activity.onCreate()。冷启动时间根据应用程序的复杂程度可以从 200 毫秒到几秒不等。
热启动(warm start 或 hot start)——应用程序处于 Suspended 状态,无需完全重新加载即可恢复运行。系统从内存中恢复最后的 UI 堆栈,用户从同一位置继续工作。热启动明显快于冷启动,因为大部分代码已加载到内存中。在 iOS 中,热启动不调用 application(_:didFinishLaunchingWithOptions:),只调用 applicationWillEnterForeground 和 applicationDidBecomeActive。
冷启动和热启动之间的区别对用户体验至关重要。在冷启动时,开发人员必须确保启动尽可能快——模块的惰性初始化、延迟加载重型资源、启动时最小化主线程的工作。Google 建议冷启动不超过 500 毫秒,Apple 建议 iOS 不超过 400 毫秒。
// 测量 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-delegate 使用惰性初始化,以避免阻塞第一帧。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 中,即使没有活跃的 Activity,进程也可以继续存在——例如,如果前台服务正在运行或有活跃的 BroadcastReceiver。
// 通过 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——Android Architecture Components 的一个组件,在转换到 Not Running 时自动保存状态并在冷启动时恢复它。通过 ViewModelProvider 创建的 ViewModel 能够承受屏幕旋转和 Activity 销毁。进程终止时,来自 SavedStateHandle 的数据序列化为 Bundle 并保存在 saved instance state 中。
Not Running 由几个原因引起。用户手动关闭应用程序。系统在内存不足时卸载应用程序。应用程序因异常紧急终止。在 Android 中,系统可以在应用程序批量更新或设备重启时终止进程。iOS 可以在后台任务超时(通常为 30 秒)时终止应用程序。
| 原因 | iOS | Android | 预防可能性 |
|---|---|---|---|
| 用户手动关闭 | 在 App Switcher 中滑动 | 从 Recents 滑动 | 否 — 用户操作 |
| 内存不足 | 触发 memory warning | onTrimMemory / LMK | 部分 — 内存优化 |
| 应用程序崩溃 | NSException / 信号 | UncaughtException / ANR | 是 — 错误处理和崩溃报告 |
| 后台任务超时 | 后台任务 30 秒 | JobScheduler 10 分钟 | 是 — 正确的任务规划 |
| 操作系统重启 | 调用 applicationWillTerminate | 广播 ACTION_SHUTDOWN | 否 — 系统事件 |
| 应用程序更新 | 不发生(iOS Sandbox) | APK 更新时进程终止 | 否 — 系统更新 |
对于 iOS,在 applicationWillTerminate 和 applicationDidFinishLaunching 中使用控制台日志记录。每次启动时向 UserDefaults 添加标志——如果下次启动时标志不存在,应用程序未正确终止。在 Android 中使用 ActivityManager.isBackgroundRestricted() 检查应用程序是否可以运行后台任务。同时监控 onTrimMemory(TRIM_MEMORY_COMPLETE)——这是进程将被终止的信号。
第一条规则——永远不要假设会调用 applicationWillTerminate 或 onDestroy。在每次从 Active 转换到 Background 时保存关键重要数据。使用键值存储来保存简单设置,使用 SQLite/Room 来保存结构化数据。
第二条规则——测量冷启动时间并优化它。惰性初始化、最小化主线程中的工作、预加载资源、使用 SplashScreen API——所有这些都能改善启动时间的感知。Google 建议冷启动低于 200 毫秒以获得出色的用户体验。
第三条规则——实现状态恢复(State Restoration)。在 iOS 中使用 UIApplication.stateRestorationIdentifier 和 NSUserActivity。在 Android 中使用 ViewModel 中的 SavedStateHandle 结合 onSaveInstanceState。这将允许用户在应用程序重启后从同一位置继续工作。
第四条规则——处理应用程序在 Not Running 后启动的 launchOptions 和 Intent。Deep link、push 通知、通用链接——所有这些都通过启动参数传递。开发人员必须正确提取这些数据并将用户引导到相应的屏幕。
// 冷启动后处理 deep link
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 检查是否收到通知
if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
handleNotification(notification)
}
// 检查 deep link
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 包含系统启动应用程序所用的数据。通知、deep link 和通用链接通过此字典传递。开发人员必须正确处理所有可能的启动场景,以确保无缝的用户体验。
常见问题
保存在永久存储(UserDefaults、Core Data、SharedPreferences、Room)中的数据得以保留。RAM 中的数据——变量、缓存、没有 SavedStateHandle 的 ViewModel 状态——将永久丢失。因此,每次转换到后台时保存应用程序状态至关重要。
冷启动时会调用 application(_:didFinishLaunchingWithOptions:)。热启动时(从 Suspended 返回)不调用此方法——只激活 applicationWillEnterForeground 和 applicationDidBecomeActive。如果需要在冷启动时执行操作,请在 didFinishLaunchingWithOptions 中设置标志。
能。带有永久通知的 Foreground Service 可防止系统终止进程,即使所有 Activity 都被销毁。Background Service(不带前台启动的 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 中,kill-switch 通过带有阻止标志的远程通知实现。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。