Not Running — początkowy stan cyklu życia aplikacji mobilnej, w którym nie została jeszcze uruchomiona lub już zakończyła działanie. Dowiedz się, jak system iOS i Android zarządzają tym stanem, jakie zdarzenia prowadzą do przejścia z Not Running i jak prawidłowo obsługiwać uruchamianie i zakończenie aplikacji w Swift i Kotlin.
Najważniejsze
Not Running — to podstawowy stan cyklu życia aplikacji mobilnej, w którym nie jest załadowana do pamięci RAM urządzenia i nie zużywa zasobów systemowych. W iOS i Android ten stan oznacza całkowity brak procesów i wątków związanych z aplikacją. Użytkownik widzi ikonę aplikacji na pulpicie, ale sama aplikacja nie jest aktywna i nie znajduje się na liście ostatnich.
Gdy użytkownik dotyka ikony, system tworzy nowy proces, ładuje kod wykonywalny do pamięci i inicjalizuje wszystkie niezbędne struktury danych. Ten proces nazywa się zimnym startem (cold start) i jest najbardziej zasobożerny pod względem czasu ładowania.
System może przenieść aplikację do Not Running z dowolnego innego stanu. Jeśli aplikacja znajduje się w tle (Background) lub jest zawieszona (Suspended), system operacyjny ma prawo zwolnić ją przy braku pamięci RAM dla bardziej priorytetowych zadań — na przykład dla aktywnej aplikacji na pierwszym planie.
Deweloper musi pamiętać, że aplikacja może zostać zakończona przez system w dowolnym momencie, gdy znajduje się w tle. Oznacza to, że wszystkie niezapisane dane mogą zostać utracone. Dlatego krytycznie ważne jest zapisywanie stanu w magazynach klucz-wartość (UserDefaults, SharedPreferences) lub w lokalnej bazie danych przy przejściach z Active do Background.
iOS używa priorytetów na podstawie bieżącego stanu aplikacji: Active ma najwyższy priorytet, następnie Inactive, Background, Suspended i wreszcie Not Running — minimalny priorytet. Android używa podobnej hierarchii procesów: proces Foreground ma priorytet OOM_ADJ = 0, proces Visible = 100, proces Service = 200, proces Background = 300, proces Empty = 400. Im wyższa wartość, tym większe prawdopodobieństwo, że proces zostanie zakończony przy braku pamięci.
| Platforma | Stan | Priorytet zwolnienia | Opis |
|---|---|---|---|
| iOS | Not Running | Najwyższy | Aplikacja nie jest załadowana — zasób systemowy nie jest zużywany |
| iOS | Suspended | Wysoki | Aplikacja w pamięci, ale kod nie jest wykonywany — pierwszy cel do zwolnienia |
| iOS | Background | Średni | Aplikacja wykonuje zadanie w tle — zwalniana po przekroczeniu limitu czasu |
| iOS | Active | Niski | Aktywna aplikacja — zwalniana tylko przy krytycznym braku pamięci |
| Android | Empty Process | Najwyższy | Proces bez aktywnych komponentów — usuwany jako pierwszy |
| Android | Background Process | Wysoki | Proces w tle bez widocznego Activity |
| Android | Foreground Service | Niski | Usługa z powiadomieniem — rzadko kończona |
| Android | Foreground Process | Minimalny | Aktywne Activity — kończone jako ostatnie |
Zimny start (cold start) występuje, gdy aplikacja przechodzi z Not Running bezpośrednio do Active. System tworzy nowy proces, ładuje klasy, inicjalizuje pola statyczne, tworzy główny wątek i uruchamia framework UI. W iOS oznacza to wywołanie application(_:didFinishLaunchingWithOptions:), w Android — wywołanie Application.onCreate() i Activity.onCreate(). Czas zimnego startu może wynosić od 200 ms do kilku sekund w zależności od złożoności aplikacji.
Gorący start (warm start lub hot start) — aplikacja była w stanie Suspended i wraca do działania bez pełnego przeładowania. System przywraca ostatni stos UI z pamięci, a użytkownik kontynuuje pracę od tego samego miejsca. Gorący start jest znacznie szybszy niż zimny, ponieważ większość kodu jest już załadowana do pamięci. W iOS gorący start nie wywołuje application(_:didFinishLaunchingWithOptions:), tylko applicationWillEnterForeground i applicationDidBecomeActive.
Różnica między zimnym a gorącym startem jest krytyczna dla doświadczenia użytkownika. Przy zimnym starciu deweloper musi upewnić się, że uruchomienie następuje jak najszybciej — leniwa inicjalizacja modułów, odroczone ładowanie ciężkich zasobów, minimalizacja pracy w głównym wątku na starcie. Google zaleca zimny start nie dłuższy niż 500 ms, Apple — nie dłuższy niż 400 ms dla iOS.
// Pomiar czasu zimnego startu w Android
class App : Application() {
private var startTime: Long = 0L
override fun onCreate() {
super.onCreate()
startTime = System.currentTimeMillis()
}
fun getStartupTime(): Long {
return System.currentTimeMillis() - startTime
}
}
// Uruchomienie Activity z leniwą inicjalizacją
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)
// Tylko niezbędne minimum dla pierwszej klatki
setupNavigation()
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// Ciężka inicjalizacja po renderowaniu
initializeHeavyModules()
}
}W przykładzie pokazano pomiar czasu zimnego startu w Android. Application.onCreate() jest wywoływane przy przejściu z Not Running do Active. Znacznik czasu jest rejestrowany przy starcie procesu. Activity używa leniwej inicjalizacji przez delegat lazy, aby nie blokować pierwszej klatki. onPostCreate to optymalne miejsce do inicjalizacji ciężkich modułów, ponieważ UI jest już narysowany.
W iOS Not Running jest zarządzane przez delegata UIApplicationDelegate. Kluczowe metody: application(_:didFinishLaunchingWithOptions:) jest wywoływane po zimnym starcie, applicationWillTerminate(_:) jest wywoływane przed zakończeniem aplikacji przez użytkownika. System może jednak zakończyć aplikację bez wywołania applicationWillTerminate — na przykład przy awaryjnym zakończeniu lub zwolnieniu pamięci. iOS nie gwarantuje wywołania tej metody, dlatego dane należy zapisywać w applicationDidEnterBackground.
Użytkownik może ręcznie zakończyć aplikację przesunięciem w App Switcher. System może zwolnić aplikację z pamięci w tle. Aplikacja może zakończyć się awaryjnie (crash). We wszystkich przypadkach wszystkie obiekty utworzone podczas uruchamiania są niszczone. Stan, który nie został zapisany, jest tracony bezpowrotnie. W iOS 13+ do zapisywania stanu zaleca się używanie NSUserActivity lub mechanizmu state restoration przez UIApplication.stateRestorationIdentifier.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// Zimny start: aplikacja przeszła z Not Running
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Inicjalizacja minimalnego zestawu usług
setupAnalytics()
configureAppearance()
return true
}
// Aplikacja kończy działanie — tylko ręczne zamknięcie
func applicationWillTerminate(
_ application: UIApplication
) {
saveCriticalData()
}
// Zapisywanie danych przed przejściem w tło
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
)
}
}Kod pokazuje prawidłową obsługę Not Running na iOS. applicationWillTerminate jest wywoływane tylko przy ręcznym zakończeniu przez użytkownika. Zapisywanie krytycznych danych jest powielone w applicationDidEnterBackground, ponieważ ta metoda jest gwarantowanie wywoływana przed przejściem w tło. State restoration pozwala zachować stos UI do późniejszego przywrócenia przy zimnym starcie.
W Android Not Running oznacza, że proces aplikacji nie istnieje. System Linux, na którym oparty jest Android, zarządza procesami przez mechanizm Zygote. Przy uruchomieniu aplikacji Zygote forka nowy proces, ładuje Dalvik/ART i wywołuje Application.onCreate(). W Android nie ma bezpośredniego odpowiednika applicationWillTerminate — system może zakończyć proces w dowolnym momencie bez ostrzeżenia.
Gdy Activity jest wywoływane po raz pierwszy, system tworzy proces, Application i Activity przez łańcuch onCreate → onStart → onResume. Jeśli użytkownik naciśnie Back, Activity jest niszczone (onDestroy), a proces może zostać zakończony przez system. Kluczowa różnica w stosunku do iOS: w Android proces może nadal istnieć nawet bez aktywnych Activity — na przykład jeśli działa Foreground Service lub jest aktywny BroadcastReceiver.
// Obsługa Not Running przez SavedStateHandle w 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 — pierwsze wywołanie zwrotne po Not Running
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initCrashReporter()
initDependencyInjection()
}
}SavedStateHandle — komponent Android Architecture Components, który automatycznie zapisuje stan przy przejściu do Not Running i przywraca go przy zimnym starcie. ViewModel utworzony przez ViewModelProvider przetrwa obrót ekranu i zniszczenie Activity. Przy zakończeniu procesu dane z SavedStateHandle są serializowane do Bundle i zapisywane w saved instance state.
Not Running następuje z kilku powodów. Użytkownik ręcznie zamyka aplikację. System zwalnia aplikację przy braku pamięci. Aplikacja kończy się awaryjnie z wyjątkiem. W Android system może zakończyć proces przy masowej aktualizacji aplikacji lub restarcie urządzenia. iOS może zakończyć aplikację przy przekroczeniu limitu czasu zadania w tle (zwykle 30 sekund).
| Przyczyna | iOS | Android | Możliwość zapobieżenia |
|---|---|---|---|
| Ręczne zamknięcie przez użytkownika | Przesunięcie w App Switcher | Przesunięcie z Recents | Nie — działanie użytkownika |
| Brak pamięci | Wyzwolenie memory warning | onTrimMemory / LMK | Częściowo — optymalizacja pamięci |
| Crash aplikacji | NSException / sygnał | UncaughtException / ANR | Tak — obsługa błędów i crash-reporting |
| Limit czasu zadania w tle | 30 sek na Background task | 10 min na JobScheduler | Tak — prawidłowe planowanie zadań |
| Restart OS | Wywołanie applicationWillTerminate | Broadcast ACTION_SHUTDOWN | Nie — zdarzenie systemowe |
| Aktualizacja aplikacji | Nie występuje (iOS Sandbox) | Proces kończy się przy aktualizacji APK | Nie — aktualizacja systemowa |
Dla iOS używaj logowania konsolowego w applicationWillTerminate i applicationDidFinishLaunching. Dodaj flagę w UserDefaults przy każdym uruchomieniu — jeśli przy następnym starcie flagi nie ma, aplikacja została zakończona nieprawidłowo. W Android używaj ActivityManager.isBackgroundRestricted(), aby sprawdzić, czy aplikacja może uruchamiać zadania w tle. Śledź również onTrimMemory(TRIM_MEMORY_COMPLETE) — to sygnał, że proces zostanie zakończony.
Pierwsza zasada — nigdy nie zakładaj, że applicationWillTerminate lub onDestroy zostaną wywołane. Zapisz krytycznie ważne dane przy każdym przejściu z Active do Background. Używaj magazynów klucz-wartość dla prostych ustawień i SQLite/Room dla danych strukturalnych.
Druga zasada — mierz czas zimnego startu i optymalizuj go. Leniwa inicjalizacja, minimalizacja pracy w głównym wątku, wstępne ładowanie zasobów, używanie SplashScreen API — wszystko to poprawia odbiór czasu uruchamiania. Google zaleca zimny start poniżej 200 ms dla doskonałego UX.
Trzecia zasada — zaimplementuj State Restoration. W iOS używaj UIApplication.stateRestorationIdentifier i NSUserActivity. W Android używaj SavedStateHandle w ViewModel w połączeniu z onSaveInstanceState. Pozwoli to użytkownikowi kontynuować pracę od tego samego miejsca po restarcie aplikacji.
Czwarta zasada — obsługuj launchOptions i Intent, z którymi aplikacja została uruchomiona po Not Running. Deep linki, powiadomienia push, linki uniwersalne — wszystkie są przekazywane przez parametry uruchomienia. Deweloper musi prawidłowo wyodrębnić te dane i skierować użytkownika na odpowiedni ekran.
// Obsługa deep link po zimnym starcie
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Sprawdzenie, czy przyszło powiadomienie
if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
handleNotification(notification)
}
// Sprawdzenie 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)
}Kod pokazuje obsługę parametrów uruchomienia przy zimnym starcie iOS. launchOptions zawiera dane, z którymi system uruchomił aplikację. Powiadomienia, deep linki i linki uniwersalne są przekazywane przez ten słownik. Deweloper musi prawidłowo obsłużyć wszystkie możliwe scenariusze uruchomienia, aby zapewnić bezproblemowe doświadczenie użytkownika.
Często zadawane pytania
Dane, które zostały zapisane w stałym magazynie (UserDefaults, Core Data, SharedPreferences, Room), są zachowywane. Dane w pamięci RAM — zmienne, cache, stan ViewModel bez SavedStateHandle — są tracone bezpowrotnie. Dlatego krytycznie ważne jest zapisywanie stanu aplikacji przy każdym przejściu w tło.
Przy zimnym starcie wywoływane jest application(_:didFinishLaunchingWithOptions:). Przy gorącym starcie (powrót z Suspended) ta metoda nie jest wywoływana — działają tylko applicationWillEnterForeground i applicationDidBecomeActive. Jeśli chcesz wykonać akcję tylko przy zimnym starcie, ustaw flagę w didFinishLaunchingWithOptions.
Tak. Foreground Service ze stałym powiadomieniem zapobiega zakończeniu procesu przez system, nawet jeśli wszystkie Activity są zniszczone. Background Service (startService bez foreground) może zostać zatrzymany przez system w dowolnym momencie. Działający Service oznacza, że proces istnieje i nie jest to już Not Running.
Na symulatorze iOS zakończ aplikację przez App Switcher (Cmd+Shift+H dwa razy, przesuń w górę). Na emulatorze Android użyj adb shell am force-stop com.example.app lub przycisku Stop w Logcat. Następnie uruchom aplikację ponownie — będzie to czysty zimny start z Not Running.
Kill-switch — polecenie serwerowe awaryjnego zakończenia aplikacji. Używane w aplikacjach bankowych i korporacyjnych do zdalnego blokowania dostępu. Jeśli aplikacja otrzymała polecenie kill, przy następnym zimnym starcie blokuje UI i żąda ponownej autoryzacji. W iOS kill-switch jest implementowany przez remote notifications z flagą blokady.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również