Not Running — starea inițială a ciclului de viață al unei aplicații mobile, în care aceasta nu a fost încă lansată sau și-a încheiat deja activitatea. Aflați cum sistemul iOS și Android gestionează această stare, ce evenimente duc la tranziția din Not Running și cum să gestionați corect lansarea și terminarea aplicației în Swift și Kotlin.
Principalele puncte
Not Running — este starea de bază a ciclului de viață al unei aplicații mobile, în care aceasta nu este încărcată în memoria RAM a dispozitivului și nu consumă resurse de sistem. În iOS și Android această stare înseamnă absența completă a proceselor și firelor de execuție asociate cu aplicația. Utilizatorul vede pictograma aplicației pe desktop, dar aplicația în sine nu este activă și nu se află în lista recentelor.
Când utilizatorul atinge pictograma, sistemul creează un nou proces, încarcă codul executabil în memorie și inițializează toate structurile de date necesare. Acest proces se numește pornire la rece (cold start) și este cel mai consumator de resurse din punct de vedere al timpului de încărcare.
Sistemul poate muta aplicația în Not Running din orice altă stare. Dacă aplicația se află în fundal (Background) sau este suspendată (Suspended), sistemul de operare are dreptul să o descarce la lipsa memoriei RAM pentru sarcini mai prioritare — de exemplu, pentru o aplicație activă în prim-plan.
Dezvoltatorul trebuie să țină cont că aplicația poate fi terminată de sistem în orice moment, când se află în fundal. Aceasta înseamnă că toate datele nesalvate pot fi pierdute. Prin urmare, este critic să salvați starea în depozitele cheie-valoare (UserDefaults, SharedPreferences) sau în baza de date locală la tranzițiile din Active în Background.
iOS utilizează priorități bazate pe starea curentă a aplicației: Active are cea mai mare prioritate, apoi Inactive, Background, Suspended și în final Not Running — prioritate minimă. Android utilizează o ierarhie de procese similară: procesul Foreground are prioritatea OOM_ADJ = 0, procesul Visible = 100, procesul Service = 200, procesul Background = 300, procesul Empty = 400. Cu cât valoarea este mai mare, cu atât probabilitatea ca procesul să fie terminat la lipsa de memorie este mai mare.
| Platformă | Stare | Prioritate descărcare | Descriere |
|---|---|---|---|
| iOS | Not Running | Cea mai mare | Aplicația nu este încărcată — resursa de sistem nu este consumată |
| iOS | Suspended | Ridicată | Aplicația în memorie, dar codul nu se execută — prima țintă pentru descărcare |
| iOS | Background | Medie | Aplicația execută o sarcină în fundal — descărcată după expirarea timpului |
| iOS | Active | Scăzută | Aplicație activă — descărcată doar la lipsa critică de memorie |
| Android | Empty Process | Cea mai mare | Proces fără componente active — șters primul |
| Android | Background Process | Ridicată | Proces în fundal fără Activity vizibil |
| Android | Foreground Service | Scăzută | Serviciu cu notificare — rareori terminat |
| Android | Foreground Process | Minimă | Activity activ — terminat ultimul |
Pornirea la rece (cold start) are loc când aplicația trece din Not Running direct în Active. Sistemul creează un nou proces, încarcă clasele, inițializează câmpurile statice, creează firul principal și pornește cadrul UI. În iOS aceasta înseamnă apelarea application(_:didFinishLaunchingWithOptions:), în Android — apelarea Application.onCreate() și Activity.onCreate(). Timpul de pornire la rece poate varia de la 200 ms la câteva secunde, în funcție de complexitatea aplicației.
Pornirea la cald (warm start sau hot start) — aplicația a fost în starea Suspended și revine la funcționare fără o reîncărcare completă. Sistemul restaurează ultima stivă UI din memorie, iar utilizatorul continuă lucrul din același loc. Pornirea la cald este semnificativ mai rapidă decât cea la rece, deoarece cea mai mare parte a codului este deja încărcată în memorie. În iOS, pornirea la cald nu apelează application(_:didFinishLaunchingWithOptions:), ci doar applicationWillEnterForeground și applicationDidBecomeActive.
Diferența dintre pornirea la rece și la cald este critică pentru experiența utilizatorului. La pornirea la rece, dezvoltatorul trebuie să se asigure că lansarea are loc cât mai rapid posibil — inițializare lentă a modulelor, încărcare amânată a resurselor grele, minimizarea muncii în firul principal la pornire. Google recomandă pornirea la rece de cel mult 500 ms, Apple — de cel mult 400 ms pentru iOS.
// Măsurarea timpului de pornire la rece în Android
class App : Application() {
private var startTime: Long = 0L
override fun onCreate() {
super.onCreate()
startTime = System.currentTimeMillis()
}
fun getStartupTime(): Long {
return System.currentTimeMillis() - startTime
}
}
// Lansarea Activity cu inițializare lentă
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)
// Doar minimul necesar pentru primul cadru
setupNavigation()
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// Inițializare grea după randare
initializeHeavyModules()
}
}Exemplul arată măsurarea timpului de pornire la rece în Android. Application.onCreate() este apelat la trecerea din Not Running în Active. Marca temporală este înregistrată la pornirea procesului. Activity utilizează inițializarea lentă prin delegatul lazy pentru a nu bloca primul cadru. onPostCreate este locul optim pentru inițializarea modulelor grele, deoarece UI este deja randat.
În iOS Not Running este gestionat prin delegatul UIApplicationDelegate. Metodele cheie: application(_:didFinishLaunchingWithOptions:) este apelat după pornirea la rece, applicationWillTerminate(_:) este apelat înainte de terminarea aplicației de către utilizator. Sistemul poate totuși termina aplicația fără a apela applicationWillTerminate — de exemplu, la terminarea de urgență sau descărcarea memoriei. iOS nu garantează apelarea acestei metode, prin urmare datele trebuie salvate în applicationDidEnterBackground.
Utilizatorul poate termina manual aplicația cu swipe în App Switcher. Sistemul poate descărca aplicația din memorie în fundal. Aplicația se poate termina de urgență (crash). În toate cazurile, toate obiectele create în timpul lansării sunt distruse. Starea care nu a fost salvată se pierde iremediabil. În iOS 13+ pentru salvarea stării se recomandă utilizarea NSUserActivity sau mecanismul state restoration prin UIApplication.stateRestorationIdentifier.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// Pornire la rece: aplicația a trecut din Not Running
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Inițializarea setului minim de servicii
setupAnalytics()
configureAppearance()
return true
}
// Aplicația își încheie activitatea — doar închidere manuală
func applicationWillTerminate(
_ application: UIApplication
) {
saveCriticalData()
}
// Salvarea datelor înainte de trecerea în fundal
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
)
}
}Codul arată gestionarea corectă a Not Running pe iOS. applicationWillTerminate este apelat doar la terminarea manuală de către utilizator. Salvarea datelor critice este duplicată în applicationDidEnterBackground, deoarece această metodă este garantat apelată înainte de trecerea în fundal. State restoration permite salvarea stivei UI pentru restaurarea ulterioară la pornirea la rece.
În Android Not Running înseamnă că procesul aplicației nu există. Sistemul Linux pe care se bazează Android gestionează procesele prin mecanismul Zygote. La lansarea aplicației, Zygote fork-uiește un nou proces, încarcă Dalvik/ART și apelează Application.onCreate(). În Android nu există un analog direct al applicationWillTerminate — sistemul poate termina procesul în orice moment fără avertizare.
Când Activity este apelat pentru prima dată, sistemul creează procesul, Application și Activity prin lanțul onCreate → onStart → onResume. Dacă utilizatorul apasă Back, Activity este distrus (onDestroy), iar procesul poate fi terminat de sistem. Diferența cheie față de iOS: în Android procesul poate continua să existe chiar și fără Activity-uri active — de exemplu, dacă rulează un Foreground Service sau există un BroadcastReceiver activ.
// Gestionarea Not Running prin SavedStateHandle în 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 — primul callback după Not Running
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initCrashReporter()
initDependencyInjection()
}
}SavedStateHandle — componentă Android Architecture Components care salvează automat starea la trecerea în Not Running și o restaurează la pornirea la rece. ViewModel-ul creat prin ViewModelProvider supraviețuiește rotirii ecranului și distrugerii Activity. La terminarea procesului, datele din SavedStateHandle sunt serializate în Bundle și salvate în saved instance state.
Not Running survine din mai multe motive. Utilizatorul închide manual aplicația. Sistemul descarcă aplicația la lipsa de memorie. Aplicația se termină de urgență cu o excepție. În Android, sistemul poate termina procesul la actualizarea în masă a aplicațiilor sau la repornirea dispozitivului. iOS poate termina aplicația la expirarea timpului sarcinii de fundal (de obicei 30 de secunde).
| Cauză | iOS | Android | Posibilitatea de prevenire |
|---|---|---|---|
| Închiderea manuală de utilizator | Swipe în App Switcher | Swipe din Recents | Nu — acțiunea utilizatorului |
| Lipsa de memorie | Declanșarea memory warning | onTrimMemory / LMK | Parțial — optimizarea memoriei |
| Crash-ul aplicației | NSException / semnal | UncaughtException / ANR | Da — gestionarea erorilor și crash-reporting |
| Expirarea sarcinii de fundal | 30 sec pentru Background task | 10 min pentru JobScheduler | Da — planificarea corectă a sarcinilor |
| Repornirea OS | Apelarea applicationWillTerminate | Broadcast ACTION_SHUTDOWN | Nu — eveniment de sistem |
| Actualizarea aplicației | Nu are loc (iOS Sandbox) | Procesul se termină la actualizarea APK | Nu — actualizare de sistem |
Pentru iOS, utilizați logarea în consolă în applicationWillTerminate și applicationDidFinishLaunching. Adăugați un flag în UserDefaults la fiecare lansare — dacă la următoarea pornire flagul lipsește, aplicația a fost terminată incorect. În Android utilizați ActivityManager.isBackgroundRestricted() pentru a verifica dacă aplicația poate rula sarcini în fundal. De asemenea, urmăriți onTrimMemory(TRIM_MEMORY_COMPLETE) — acesta este un semnal că procesul va fi terminat.
Prima regulă — nu presupuneți niciodată că applicationWillTerminate sau onDestroy vor fi apelate. Salvați datele critic de importante la fiecare tranziție din Active în Background. Utilizați depozitele cheie-valoare pentru setări simple și SQLite/Room pentru date structurate.
A doua regulă — măsurați timpul de pornire la rece și optimizați-l. Inițializarea lentă, minimizarea muncii în firul principal, încărcarea prealabilă a resurselor, utilizarea SplashScreen API — toate acestea îmbunătățesc percepția timpului de lansare. Google recomandă pornirea la rece sub 200 ms pentru un UX excelent.
A treia regulă — implementați State Restoration. Pe iOS utilizați UIApplication.stateRestorationIdentifier și NSUserActivity. Pe Android utilizați SavedStateHandle în ViewModel împreună cu onSaveInstanceState. Acest lucru va permite utilizatorului să continue lucrul din același loc după repornirea aplicației.
A patra regulă — gestionați launchOptions și Intent cu care aplicația a fost lansată după Not Running. Deep link-uri, notificări push, link-uri universale — toate sunt transmise prin parametrii de lansare. Dezvoltatorul trebuie să extragă corect aceste date și să direcționeze utilizatorul către ecranul corespunzător.
// Gestionarea deep link după pornirea la rece
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Verificarea dacă a sosit notificarea
if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
handleNotification(notification)
}
// Verificarea 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)
}Codul arată gestionarea parametrilor de lansare la pornirea la rece în iOS. launchOptions conține datele cu care sistemul a lansat aplicația. Notificările, deep link-urile și link-urile universale sunt transmise prin acest dicționar. Dezvoltatorul trebuie să gestioneze corect toate scenariile posibile de lansare pentru a asigura o experiență de utilizare fără probleme.
Întrebări frecvente
Datele care au fost salvate în stocarea permanentă (UserDefaults, Core Data, SharedPreferences, Room) se păstrează. Datele din memoria RAM — variabilele, cache-ul, starea ViewModel fără SavedStateHandle — se pierd iremediabil. Prin urmare, este critic să salvați starea aplicației la fiecare trecere în fundal.
La pornirea la rece este apelată application(_:didFinishLaunchingWithOptions:). La pornirea la cald (revenirea din Suspended) această metodă nu este apelată — se activează doar applicationWillEnterForeground și applicationDidBecomeActive. Dacă trebuie să executați o acțiune doar la pornirea la rece, setați un flag în didFinishLaunchingWithOptions.
Da. Foreground Service cu o notificare permanentă împiedică terminarea procesului de către sistem, chiar dacă toate Activity-urile sunt distruse. Background Service (startService fără foreground) poate fi oprit de sistem în orice moment. Un Service care rulează înseamnă că procesul există și nu mai este Not Running.
Pe simulatorul iOS, terminați aplicația prin App Switcher (Cmd+Shift+H de două ori, swipe în sus). Pe emulatorul Android, utilizați adb shell am force-stop com.example.app sau butonul Stop din Logcat. Apoi lansați aplicația din nou — va fi o pornire la rece curată din Not Running.
Kill-switch — o comandă server pentru terminarea de urgență a aplicației. Este utilizată în aplicațiile bancare și corporative pentru blocarea remote a accesului. Dacă aplicația a primit comanda kill, la următoarea pornire la rece blochează UI și solicită reautorizarea. Pe iOS, kill-switch este implementat prin remote notifications cu un flag de blocare.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și