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 форкује нови процес, учитава 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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође