Not Running — начално състояние на жизнения цикъл на мобилно приложение, в което то все още не е стартирано или вече е приключило работа. Научете как системата iOS и Android управляват това състояние, кои събития водят до преход от Not Running и как правилно да обработвате стартирането и прекратяването на приложението в Swift и Kotlin.
Основни точки
Not Running — е основното състояние на жизнения цикъл на мобилно приложение, в което то не е заредено в RAM паметта на устройството и не консумира системни ресурси. В iOS и Android това състояние означава пълно отсъствие на процеси и нишки, свързани с приложението. Потребителят вижда иконата на приложението на работния плот, но самото приложение не е активно и не се намира в списъка на скорошните.
Когато потребителят докосне иконата, системата създава нов процес, зарежда изпълнимия код в паметта и инициализира всички необходими структури от данни. Този процес се нарича студено стартиране (cold start) и е най-ресурсоемък от гледна точка на време за зареждане.
Системата може да премести приложението в Not Running от всяко друго състояние. Ако приложението е във фонов режим (Background) или е спряно (Suspended), операционната система има право да го разтовари при липса на RAM памет за по-приоритетни задачи — например за активно приложение на преден план.
Разработчикът трябва да има предвид, че приложението може да бъде прекратено от системата по всяко време, когато е във фонов режим. Това означава, че всички незапазени данни могат да бъдат загубени. Поради това е критично важно да запазвате състоянието в хранилища ключ-стойност (UserDefaults, SharedPreferences) или в локална база данни при преходите от Active към Background.
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 — прекратява се последно |
Студено стартиране (cold start) настъпва, когато приложението преминава от Not Running директно в Active. Системата създава нов процес, зарежда класовете, инициализира статичните полета, създава главната нишка и стартира UI рамката. В iOS това означава извикване на application(_:didFinishLaunchingWithOptions:), в Android — извикване на Application.onCreate() и Activity.onCreate(). Времето за студено стартиране може да варира от 200 ms до няколко секунди в зависимост от сложността на приложението.
Горещо стартиране (warm start или hot start) — приложението е било в състояние Suspended и се връща към работа без пълно презареждане. Системата възстановява последния UI стек от паметта и потребителят продължава работа от същото място. Горещото стартиране е значително по-бързо от студеното, тъй като по-голямата част от кода вече е заредена в паметта. В iOS горещото стартиране не извиква application(_:didFinishLaunchingWithOptions:), само applicationWillEnterForeground и applicationDidBecomeActive.
Разликата между студено и горещо стартиране е критична за потребителското изживяване. При студено стартиране разработчикът трябва да осигури стартирането да стане възможно най-бързо — мързелива инициализация на модули, отложено зареждане на тежки ресурси, минимизиране на работата в главната нишка при стартиране. Google препоръчва студено стартиране не повече от 500 ms, Apple — не повече от 400 ms за iOS.
// Измерване на времето за студено стартиране в 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. Системата може да разтовари приложението от паметта във фонов режим. Приложението може аварийно да прекрати (crash). Във всички случаи всички обекти, създадени по време на стартиране, се унищожават. Състоянието, което не е запазено, се губи безвъзвратно. В iOS 13+ за запазване на състояние се препоръчва използването на NSUserActivity или механизма state restoration чрез 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
)
}
}Кодът показва правилната обработка на Not Running в iOS. applicationWillTerminate се извиква само при ръчно прекратяване от потребителя. Запазването на критични данни е дублирано в applicationDidEnterBackground, тъй като този метод гарантирано се извиква преди преминаване във фонов режим. State restoration позволява запазване на UI стека за последващо възстановяване при студено стартиране.
В Android Not Running означава, че процесът на приложението не съществува. Linux системата, на която се базира Android, управлява процесите чрез механизма Zygote. При стартиране на приложението Zygote fork-ва нов процес, зарежда Dalvik/ART и извиква Application.onCreate(). В Android няма пряк аналог на applicationWillTerminate — системата може да прекрати процеса по всяко време без предупреждение.
Когато Activity се извика за първи път, системата създава процес, Application и Activity чрез веригата onCreate → onStart → onResume. Ако потребителят натисне Back, Activity се унищожава (onDestroy) и процесът може да бъде прекратен от системата. Ключова разлика от iOS: в Android процесът може да продължи да съществува дори без активни Activity — например ако работи Foreground Service или има активен BroadcastReceiver.
// Обработка на Not Running чрез SavedStateHandle в ViewModel
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 и го възстановява при студено стартиране. ViewModel, създаден чрез ViewModelProvider, оцелява при завъртане на екрана и унищожаване на Activity. При прекратяване на процеса данните от SavedStateHandle се сериализират в Bundle и се запазват в saved instance state.
Not Running настъпва по няколко причини. Потребителят ръчно затваря приложението. Системата разтоварва приложението при липса на памет. Приложението аварийно прекратява с изключение. В Android системата може да прекрати процеса при масово обновяване на приложения или рестартиране на устройството. iOS може да прекрати приложението при изтичане на времето за фонова задача (обикновено 30 секунди).
| Причина | iOS | Android | Възможност за предотвратяване |
|---|---|---|---|
| Ръчно затваряне от потребителя | Плъзгане в App Switcher | Плъзгане от Recents | Не — действие на потребителя |
| Липса на памет | Задействане на memory warning | onTrimMemory / LMK | Частично — оптимизация на паметта |
| Краш на приложението | NSException / сигнал | UncaughtException / ANR | Да — обработка на грешки и crash-reporting |
| Изтичане на време за фонова задача | 30 сек за Background task | 10 мин за JobScheduler | Да — правилно планиране на задачи |
| Рестартиране на ОС | Извикване на applicationWillTerminate | Broadcast 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 ms за отлично UX.
Трето правило — имплементирайте State Restoration. В iOS използвайте UIApplication.stateRestorationIdentifier и NSUserActivity. В Android използвайте SavedStateHandle в ViewModel в комбинация с onSaveInstanceState. Това ще позволи на потребителя да продължи работа от същото място след рестартиране на приложението.
Четвърто правило — обработвайте launchOptions и Intent, с които приложението е стартирано след Not Running. 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 паметта — променливи, кеш, състояние на ViewModel без SavedStateHandle — се губят безвъзвратно. Поради това е критично важно да запазвате състоянието на приложението при всеки преход във фонов режим.
При студено стартиране се извиква application(_:didFinishLaunchingWithOptions:). При горещо стартиране (връщане от Suspended) този метод не се извиква — активират се само applicationWillEnterForeground и applicationDidBecomeActive. Ако трябва да изпълните действие само при студено стартиране, задайте флаг в didFinishLaunchingWithOptions.
Да. Foreground Service с постоянно известие предотвратява прекратяването на процеса от системата, дори ако всички Activity са унищожени. Background Service (startService без foreground) може да бъде спрян от системата по всяко време. Работещ Service означава, че процесът съществува и това вече не е Not Running.
На iOS симулатора прекратете приложението чрез App Switcher (Cmd+Shift+H два пъти, плъзнете нагоре). На Android емулатора използвайте adb shell am force-stop com.example.app или бутона Stop в Logcat. След това стартирайте приложението отново — това ще бъде чисто студено стартиране от Not Running.
Kill-switch — сървърна команда за аварийно прекратяване на приложението. Използва се в банкови и корпоративни приложения за отдалечено блокиране на достъпа. Ако приложението е получило kill команда, при следващото студено стартиране блокира UI и изисква повторна авторизация. В iOS kill-switch се имплементира чрез remote notifications с флаг за блокиране.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също