Suspended — az iOS-alkalmazás életciklusának felfüggesztett állapota, amelyben az alkalmazás a memóriában van befagyasztva, de nem hajt végre kódot. Megmutatjuk, hogyan működik a Suspended, milyen kockázatokat hordoz az alkalmazás háttérben történő befagyasztása, hogyan kezeli az iOS a Suspended alkalmazások kiürítését, és hogyan valósítsuk meg a state restoration-t a zökkenőmentes helyreállításhoz a Suspended-ből való visszatérés után.
Főbb pontok
Suspended — az iOS-alkalmazás életciklusának az az állapota, amelyben az alkalmazás a készülék operatív memóriájában található, de nem hajt végre semmilyen kódot. Ez a végső állapot a teljes befejezés előtt: az alkalmazás a Background-ból Suspended-be lép az összes háttérfeladat befejezése után vagy az időtúllépés lejártakor. Suspended-ben az alkalmazás teljesen le van fagyasztva — az összes szál fel van függesztve, az időzítők nem működnek, hálózati tevékenység nincs.
Suspended — az iOS egyedi jellemzője, amely nem található meg az Android szabványos életciklusában. Az ok a folyamatkezelés eltérő architektúrája. Az iOS megőrzi az alkalmazás képét a memóriában (analóg a desktop hibernálással), hogy a felhasználó visszatérésekor azonnal helyreállítsa a felületet hideg indítás nélkül. Az Android nem rendelkezik Suspended-del — a folyamat vagy létezik és képes kódot végrehajtani (Background), vagy befejeződött (Not Running), bár az Android az LMK-n keresztül felfüggesztheti a szálak végrehajtását.
A felhasználó számára a Suspended azonnali helyreállításnak tűnik: az App Switcher-en keresztül váltogat az alkalmazások között, és minden alkalmazás ott nyílik meg, ahol hagyta. Ez azt az illúziót kelti, hogy az összes alkalmazás egyszerre fut. A valóságban a legtöbbjük Suspended-ben van befagyasztva. A Suspended-ből való forró indítás sokszor gyorsabb, mint a Not Running-ból való hideg indítás, mivel a kód már be van töltve a memóriába.
Az iOS nyomon követi az összes alkalmazás állapotát, és a rendelkezésre álló memória alapján dönt a Suspended alkalmazások kiürítéséről. Memóriahiány esetén a rendszer elkezdi kiüríteni a Suspended alkalmazásokat, kezdve azokkal, amelyek a leghosszabb ideje vannak ebben az állapotban. Ha a memória továbbra is elégtelen, a rendszer áthelyezi az alkalmazásokat a Background-ból és az Inactive-ből Suspended-be, majd kiüríti őket. Ez a folyamat teljesen átlátható a felhasználó számára — ő csak az alkalmazás ikonját látja az App Switcher-ben, amelyre kattintva egy hideg indítás indul.
| Jellemző | Suspended (iOS) | Background (iOS) | Background (Android) |
|---|---|---|---|
| Kód fut | Nem | Igen (korlátozott) | Igen (korlátozott) |
| Memóriában | Igen | Igen | Igen |
| CPU-fogyasztás | 0% | Alacsony | Alacsony |
| Forró indítás | Igen — azonnali helyreállítás | Igen — Inactive-en keresztül | Nem — a folyamat lehet, hogy meg lett ölve |
| Időtúllépés | Nem — órákig lehet a memóriában | ~30 másodperc (beginBackgroundTask után) | API-verziótól függ |
| Rendszer általi kiürítés | Memóriahiány esetén | Kritikus memóriahiány esetén | LMK (Low Memory Killer) |
| Visszatérés munkához | App Switcher-ből — azonnal | App Switcher-ből — Inactive-en keresztül | Hideg indítás |
| State Restoration | Ajánlott | Nem szükséges | SavedStateHandle |
iOS-ben a Suspended automatikusan elérhetővé válik az összes háttérfeladat befejezése után. A rendszer meghívja az applicationDidEnterBackground-ot, időt ad a beginBackgroundTask végrehajtására (körülbelül 30 másodperc), majd erőszakosan felfüggeszti az összes szálat, és átviszi az alkalmazást a Suspended állapotba. A memóriában lévő objektumok megmaradnak, de semmilyen kód nem fut — az alkalmazás az aktuális állapotban van befagyasztva.
Kritikusan fontos pillanat: az applicationDidEnterBackground — az utolsó metódus, amely garantáltan meghívódik a Suspended előtt. Ezután az alkalmazás nem kap semmilyen értesítést a memóriából való kiürítésről. Ha a felhasználó vagy a rendszer megöli a Suspended állapotban lévő alkalmazást, sem az applicationWillTerminate, sem az applicationDidEnterBackground nem hívódik meg újra. Ezért minden adatmentésnek az applicationDidEnterBackground-ben kell történnie, nem az applicationWillTerminate-ben.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// Utolsó garantált hívás a Suspended előtt
func applicationDidEnterBackground(_ application: UIApplication) {
// Elmentünk mindent, aminek túl kell élnie a memóriából való kiürítést
savePersistentState()
saveNavigationStack()
// További időt kérünk, ha szükséges
let task = application.beginBackgroundTask {
application.endBackgroundTask(task)
}
}
// Visszatérés a Suspended-ből — forró indítás
func applicationWillEnterForeground(_ application: UIApplication) {
// Az alkalmazás Suspended-ben volt, visszatérünk a munkához
print("Visszatérés Suspended-ből vagy Background-ból")
}
// Teljes helyreállítás a memóriából való kiürítés után
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Ha ez a kiürítés utáni hideg indítás a Suspended-ből —
// visszaállítjuk a state restoration-t
return true
}
private func savePersistentState() {
UserDefaults.standard.set(Date(), forKey: "lastActiveDate")
}
private func saveNavigationStack() {
guard let rootVC = window?.rootViewController else { return }
// Elmentjük az aktuális navigációs vermet
if let navController = rootVC as? UINavigationController {
let vcClasses = navController.viewControllers.map { type(of: $0) }
UserDefaults.standard.set(vcClasses.map { NSStringFromClass($0) }, forKey: "navStack")
}
}
}A kód a Suspended kritikus kezelését mutatja iOS-ben. applicationDidEnterBackground — az utolsó garantált hívás. Minden adatmentésnek itt kell történnie: felhasználói állapot, navigációs verem, piszkozatok, időzítők. Az applicationWillEnterForeground a Suspended-ből vagy Background-ból való visszatéréskor hívódik meg. A didFinishLaunchingWithOptions — csak hideg indításkor, amikor az alkalmazás ki lett ürítve a memóriából a Suspended után.
Androidban nincs közvetlen analógja az iOS Suspended-nek. Az Android nem fagyasztja be az alkalmazásokat a memóriában a végrehajtási kontextus megőrzésével. Ehelyett az Android vagy a háttérben tartja a folyamatot (Background), vagy befejezi azt (Not Running). Azonban Android 11+ (API 30) készülékeken megjelent az App Freezer mechanizmus, amely a SIGSTOP jel segítségével felfüggeszti a háttérfolyamatok végrehajtását. Ez a Suspended funkcionális analógja, de fontos különbségekkel.
App Freezer — az Android memóriakezelő rendszerének része. Amikor egy alkalmazás hosszú ideig a háttérben van, és nincs aktív értesítése, a rendszer SIGSTOP-ot küld neki, felfüggesztve az összes szálat. Amikor az alkalmazás visszatér az előtérbe, SIGCONT kerül elküldésre, és a végrehajtás folytatódik. Fő különbség az iOS-hez képest: az App Freezer nem garantálja az állapot megőrzését — a memóriában lévő adatok elveszhetnek, ha a folyamatot megölik a befagyasztás során.
Androidban ajánlott a SavedStateHandle használata a ViewModel-ben az állapot automatikus mentéséhez bármilyen folyamatbefejezéskor. A SavedStateHandle adatokat ment a Bundle-be az onSaveInstanceState-n keresztül, amely túléli mind az App Freezer-t, mind a Process Death-t. Az iOS-szel ellentétben, ahol a Suspended-ből való kiürítés kivételes helyzet, Androidban a Process Death normális viselkedés, amelyet mindig várni kell.
// SavedStateHandle — menekülés a Process Death elől Androidban
class CheckoutViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Állapot, amely túléli a folyamatot még az App Freezer után is
var currentStep: MutableLiveData<Int> =
savedStateHandle.getLiveData("checkout_step", 1)
var cartItems: MutableLiveData<List<CartItem>> =
savedStateHandle.getLiveData("cart_items", emptyList())
fun proceedToNextStep() {
currentStep.value = (currentStep.value ?: 0) + 1
}
fun addToCart(item: CartItem) {
val updatedList = (cartItems.value ?: emptyList()) + item
cartItems.value = updatedList
savedStateHandle["cart_items"] = updatedList
}
}
// Mentés onStop-ban App Freezer esetére
class MainActivity : AppCompatActivity() {
override fun onStop() {
super.onStop()
// Elmentjük az adatokat, amelyeknek túl kell élniük a befagyasztást
saveDraftData()
// Felszabadítjuk az erőforrásokat, amelyek nem kellenek befagyasztott állapotban
releaseHeavyResources()
// Figyelmeztetjük, hogy az alkalmazás be lesz fagyasztva
// (Naplózás hibakereséshez)
Log.d("Lifecycle", "Activity leállítva — lehetséges App Freeze")
}
}A kód a Suspended analóg kezelésének megközelítését mutatja Androidban. SavedStateHandle a ViewModel-ben automatikusan menti és visszaállítja az adatokat Process Death esetén. onStop — az utolsó garantált esemény a lehetséges App Freezer vagy folyamatbefejezés előtt. A megrendelő űrlap állapota, a kosárban lévő termékek listája — mindezek az adatok túlélik a befagyasztást a SavedStateHandle-nek köszönhetően. Nehéz erőforrásokhoz (bitképek, adatbázis-kurzorok) az onStop a memória felszabadításának helye.
State Restoration — az iOS beépített mechanizmusa a UI-állapot mentésére és helyreállítására az alkalmazás memóriából való kiürítése után. Ha az alkalmazás Suspended-ben volt, és a rendszer kiürítette, a következő hideg indításkor a state restoration visszaállítja a navigációs vermet, a görgetési pozíciót, az űrlapok állapotát és más UI-elemeket. A felhasználó ugyanarra a képernyőre tér vissza, ahol abbahagyta.
A State Restoration a UIViewControllerRestoration és UIStateRestoring protokollokon keresztül működik. A fejlesztő restorationIdentifier-t rendel minden ViewController-hez és View-hoz, amelyet helyre szeretne állítani. A Background-ba lépéskor az iOS kódolja ezen objektumok állapotát. A kiürítésből való visszatérés után az iOS új objektumokat hoz létre, és dekódolja a mentett állapotot. State Restoration nélkül a felhasználó üres képernyőt fog látni a hideg indítás után ahelyett, ahol abbahagyta.
import UIKit
class DetailViewController: UIViewController {
var itemID: String = ""
var scrollPosition: CGPoint = .zero
override func viewDidLoad() {
super.viewDidLoad()
restorationIdentifier = "DetailViewController"
restorationClass = type(of: self)
}
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
coder.encode(itemID, forKey: "itemID")
coder.encode(scrollPosition, forKey: "scrollPosition")
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
if let savedID = coder.decodeObject(forKey: "itemID") as? String {
itemID = savedID
loadItem()
}
if let savedPosition = coder.decodeCGPoint(forKey: "scrollPosition") {
scrollPosition = savedPosition
// Helyreállítjuk a pozíciót az adatok betöltése után
}
}
}
// AppDelegate — a State Restoration aktiválása
func application(
_ application: UIApplication,
shouldSaveSecureApplicationState coder: NSCoder
) -> Bool {
return true
}
func application(
_ application: UIApplication,
shouldRestoreSecureApplicationState coder: NSCoder
) -> Bool {
return true
}A kód a State Restoration megvalósítását mutatja iOS-ben. restorationIdentifier és restorationClass szükséges minden helyreállítandó ViewController-hez. Az encodeRestorableState/decodeRestorableState az NSCoder-en keresztül menti és tölti be az adatokat. Az AppDelegate-ben a shouldSaveSecureApplicationState és a shouldRestoreSecureApplicationState bekapcsolja az állapot titkosított mentését. iOS 12+ óta ajánlott a secure encoding (NSSecureCoding) használata az adatok védelmére.
Első szabály — soha ne számíts arra, hogy az alkalmazás visszatér a Suspended-ből. A rendszer bármikor kiürítheti az alkalmazást. Az összes kritikusan fontos adatot a Suspended-be való átlépés előtt — vagyis az applicationDidEnterBackground-ben vagy onStop-ban — tartós tárhelyre kell menteni. UserDefaults, Core Data, File Manager — megfelelő tárolók. A memória (változók, tulajdonságok) — megbízhatatlan tároló azoknak az adatoknak, amelyeknek túl kell élniük a Suspended-et.
Második szabály — szabadítsd fel az erőforrásokat a Suspended előtt. Zárd be a fájlleírókat, szabadítsd fel a GPU memóriát (Metal, Core Graphics), zárd be a hálózati kapcsolatokat. Bár az alkalmazás nem fogyaszt CPU-t Suspended-ben, a lefoglalt erőforrások blokkolva vannak más alkalmazások számára. iOS-ben Suspended-ben nem lehet nyitott socketeket tartani — a Suspended-ből való visszatéréskor ezek működésképtelenek lehetnek, ami hibákat okoz.
Harmadik szabály — ne helyezz időfüggő logikát a Suspended-ből való visszatérésre várva. Az időzítők, callback-ek és hálózati tevékenység leállnak Suspended-ben. Ha az alkalmazás több órát volt Suspended-ben, a visszatéréskor az időzítő helytelenül működhet. Ellenőrizd az adatok aktualitását visszatéréskor — lehet, hogy a gyorsítótár elavult, és az engedélyezési token lejárt.
Negyedik szabály — használd a State Restoration-t az összes képernyőhöz, különösen a beviteli űrlapokhoz, görgethető listákhoz és részletképernyőkhöz. State Restoration nélkül a felhasználó a kiürített Suspended-ből való visszatérés után az alkalmazás kezdőképernyőjét fogja látni ahelyett, ahol abbahagyta. Ez rontja a felhasználói élményt és a felhasználót a műveletek megismétlésére kényszeríti.
import UIKit
// Ellenőrzés: ki lett-e ürítve az alkalmazás a memóriából?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Ellenőrizzük, hogy létezik-e mentett állapot
if UserDefaults.standard.object(forKey: "navStack") != nil {
// Az alkalmazás ki lett ürítve a Suspended-ből
// Vissza kell állítani az állapotot
restoreNavigationStack()
} else {
// Tiszta hideg indítás a Not Running-ból
showOnboardingIfNeeded()
}
return true
}
private func restoreNavigationStack() {
guard let savedStack = UserDefaults.standard.array(forKey: "navStack") as? [String],
let navController = window?.rootViewController as? UINavigationController
else { return }
for vcClassName in savedStack {
if let vcClass = NSClassFromString(vcClassName) as? UIViewController.Type {
let vc = vcClass.init()
navController.pushViewController(vc, animated: false)
}
}
}A kód azt a gyakorlatot mutatja, hogyan lehet meghatározni, hogy az alkalmazás ki lett-e ürítve a Suspended-ből. A UserDefaults ellenőrzése a mentett navigációs verem meglétére lehetővé teszi a kiürítés utáni hideg indítás megkülönböztetését a tiszta hideg indítástól. Az első esetben a navigációs verem helyreáll, a másodikban az onboarding vagy a főképernyő jelenik meg. Ez a megközelítés kiegészíti a beépített State Restoration-t azokban az esetekben, amikor az NSCoder nem elegendő.
Gyakran Ismételt Kérdések
Korlátlan — néhány másodperctől néhány napig. Az iOS-nek nincs időtúllépése a Suspended számára. Az alkalmazás addig marad a memóriában, amíg a rendszer úgy nem dönt, hogy kiüríti az erőforráshiány miatt. A gyakorlatban az alkalmazások 15 perctől néhány óráig maradnak Suspended-ben, a készülék RAM-jának méretétől és az aktív alkalmazások számától függően.
Nem. Az applicationWillTerminate nem hívódik meg az alkalmazás Suspended-ből való kiürítésekor. A rendszer egyszerűen felszabadítja a memóriát anélkül, hogy értesítené az alkalmazást. Ez egy újabb ok, amiért minden adatmentésnek az applicationDidEnterBackground-ben kell történnie. Az applicationWillTerminate csak akkor hívódik meg, amikor a felhasználó manuálisan befejezi az alkalmazást az App Switcher-ből való elhúzással.
Nincs közvetlen analóg. Android 11+ készülékeken megjelent az App Freezer, amely SIGSTOP-on keresztül függeszti fel a háttérfolyamatokat — ez funkcionálisan hasonlít a Suspended-hez. Az Android-alkalmazásokat azonban úgy kell tervezni, hogy bármikor számítsanak Process Death-re. Használd a SavedStateHandle-t a ViewModel-ben és az onSaveInstanceState-t az állapot mentésére, amely túléli mind az App Freezer-t, mind a Process Death-t.
iOS-ben nincs közvetlen API az ellenőrzésre. Közvetett módszer: ellenőrizd a UserDefaults-ot a mentett állapot meglétére a didFinishLaunchingWithOptions-ben. Ha az állapot létezik — az alkalmazás ki lett ürítve a Suspended-ből, és hideg indítást végez. Ha nincs állapot — tiszta hideg indítás. SwiftUI-ben elmenthetsz egy flag-et a scenePhase.background-ban, és ellenőrizheted a következő indításkor.
A Suspended-be való átlépéskor az iOS készít egy snapshot-ot — az alkalmazás aktuális UI-jának képernyőképét. Ez a kép az App Switcher-ben és az alkalmazásba való visszatéréskor (olvadási animációként) jelenik meg. Ha az alkalmazás bizalmas adatokat tartalmaz, a snapshot felfedheti azokat. Védelemhez használd az UIApplication.shouldSnapshotSecureApp (iOS 16+) tulajdonságot, vagy alkalmazz blur-overlay-t az applicationDidEnterBackground-ben.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is