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-delegate για να μην μπλοκάρει το πρώτο καρέ. Το onPostCreate είναι το βέλτιστο σημείο για αρχικοποίηση βαρέων μονάδων, καθώς το UI έχει ήδη αποδοθεί.
Στο iOS, το Not Running διαχειρίζεται μέσω του delegate UIApplicationDelegate. Βασικές μέθοδοι: το application(_:didFinishLaunchingWithOptions:) καλείται μετά από ψυχρή εκκίνηση, το applicationWillTerminate(_:) καλείται πριν τον τερματισμό της εφαρμογής από τον χρήστη. Το σύστημα μπορεί να τερματίσει την εφαρμογή χωρίς να καλέσει το applicationWillTerminate — για παράδειγμα, σε επείγοντα τερματισμό ή εκφόρτωση μνήμης. Το iOS δεν εγγυάται την κλήση αυτής της μεθόδου, επομένως τα δεδομένα πρέπει να αποθηκεύονται στο applicationDidEnterBackground.
Ο χρήστης μπορεί να τερματίσει χειροκίνητα την εφαρμογή με swipe στο 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 | Δυνατότητα πρόληψης |
|---|---|---|---|
| Χειροκίνητο κλείσιμο από χρήστη | Swipe στο App Switcher | Swipe από Recents | Όχι — ενέργεια χρήστη |
| Έλλειψη μνήμης | Ενεργοποίηση memory warning | onTrimMemory / LMK | Μερικώς — βελτιστοποίηση μνήμης |
| Crash εφαρμογής | NSException / σήμα | UncaughtException / ANR | Ναι — διαχείριση σφαλμάτων και crash-reporting |
| Χρονικό όριο εργασίας παρασκηνίου | 30 δευτ. για Background task | 10 λεπτά για JobScheduler | Ναι — σωστός προγραμματισμός εργασιών |
| Επανεκκίνηση OS | Κλήση 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 links, 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 links και οι καθολικοί σύνδεσμοι μεταδίδονται μέσω αυτού του λεξικού. Ο προγραμματιστής πρέπει να χειριστεί σωστά όλα τα πιθανά σενάρια εκκίνησης για να εξασφαλίσει μια απρόσκοπτη εμπειρία χρήστη.
Συχνές Ερωτήσεις
Τα δεδομένα που αποθηκεύτηκαν σε μόνιμη αποθήκευση (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 δύο φορές, swipe προς τα πάνω). Στον εξομοιωτή 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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης