Lefagyás a fejlesztésben — lényege, okai és megelőzése

Szerző: IT Sectr Megjelenés: 2026-07-28 Olvasási idő: 9 perc

Lefagyás (visnet) — olyan állapot, amikor a mobilalkalmazás hosszabb időre abbahagyja a felhasználói műveletekre való reagálást. A lagektől (lassulás) és glitchektől (hibás viselkedés) eltérően a lefagyás teljesen blokkolja a UI-t: az érintések nem kerülnek feldolgozásra, az animáció megáll, a képernyő „befagy". Az ok — a fő szál blokkolása szinkron művelettel, deadlock a többszálú kódban vagy abnormálisan hosszú szemétgyűjtés. A Apple Main Thread Checker Documentation szerint az iOS crash jelentések több mint 40%-a a fő szál blokkolásához kapcsolódik. Androidon hasonló helyzet vezet az ANR-hez — „Az alkalmazás nem válaszol" rendszerpárbeszédablakhoz.

Főbb pontok

  • Lefagyás — a UI teljes blokkolása hosszabb időre (másodpercek és több tíz másodperc), eltér a lagektől és glitchektől
  • Fő okok — a fő szál blokkolása bemenet-kimenet által, deadlock a szálak között, végtelen ciklus és memóriaszivárgás hosszú GC-vel
  • Diagnosztika magában foglalja a Main Thread Checkert iOS-en, ANR naplókat /data/anr/traces.txt Androidon és szál dumpok elemzését
  • Megszüntetés — az összes potenciálisan hosszú művelet áthelyezése háttérszálakra, Structured Concurrency használata és a synchronized kerülése a UI szálban
  • Megelőzés — StrictMode, Main Thread Checker a Debug sémában, statikus elemzés deadlockra és időszakos tesztek futtatása válaszidő méréssel

Mi a lefagyás a mobilfejlesztésben

Lefagyás (freeze, hang) egy mobilalkalmazásban — olyan állapot, amikor az alkalmazás több másodpercre vagy tovább abbahagyja a bemeneti események feldolgozását és a felület frissítését. Technikailag ez azt jelenti, hogy a fő szál (main thread) blokkolva van, és nem tudja végrehajtani a következő runner ciklust.

A lefagyás, a lag és az ANR közötti különbség

A lag — legfeljebb 500 ms késleltetés, amikor a felhasználó lassulást észlel, de az alkalmazás tovább működik. Lefagyás 1 másodperctől több tíz másodpercig tart. Az ANR Androidon — a lefagyás speciális esete, amely több mint 5 másodpercig tartott és a rendszer észlelte. Nem minden lefagyás vezet ANR-hez, de minden ANR egy dokumentált lefagyás a rendszer által.

A lefagyás következményei

Androidon az 5 másodpercnél hosszabb lefagyás ANR párbeszédablakot vált ki az alkalmazás bezárására vonatkozó javaslattal. iOS-en a rendszer watchdoggal rendelkezik — ha az alkalmazás 10–20 másodpercen belül nem reagál az eseményekre, a Watchdog 0x8badf00d (ate bad food) kóddal befejezi a folyamatot. A felhasználó csak az alkalmazás hirtelen bezáródását és a kezdőképernyőre való visszatérést látja.

A lefagyás okai Androidon és iOS-en

Minden olyan művelet, amely 100 ms-nál tovább tart és a fő szálban fut, potenciálisan lefagyást okoz. Vizsgáljuk meg a blokkolások fő forrásait.

Szinkron bemenet-kimenet a UI szálban

Nagyméretű fájl olvasása, hálózati kérés aszinkronitás nélkül, adatok mentése SharedPreferences-be szinkron apply metódussal, majd commit — mindezen műveletek blokkolják a fő szálat. Androidon egy 10 MB-os fájl szinkron olvasása 200–500 ms-ig tarthat a flash memória sebességétől függően. iOS-en a URLSession szinkron betöltése completionHandler nélkül blokkolja a UI-t a szerver válaszának idejére.

Deadlock a többszálú kódban

Amikor két szál várja az egymás által tartott erőforrások felszabadítását, deadlock keletkezik. Mobilalkalmazásokban tipikus forgatókönyv — az A szál blokkolja a Lock1-et és várja a Lock2-t, míg a B szál blokkolja a Lock2-t és várja a Lock1-et. Mindkét szál örökre lefagy. Ha az egyik a fő szál, az alkalmazás teljesen lefagy.

Végtelen ciklus vagy rekurzió

Logikai hiba — például while(true) kilépési feltétel nélkül vagy rekurzió alapeset nélkül — végtelen végrehajtáshoz vezet a fő szálban. Android ezt ANR-en keresztül érzékeli 5 másodperc után, iOS — Stackshot-on keresztül, amely rögzíti a végtelenül ismétlődő hívási vermet.

  • Android — le nem zárt Cursor, szinkron kérés execute()-on keresztül enqueue() helyett, FileInputStream.read() a UI szálban
  • iOS — performSelectorOnMainThread:withObject:waitUntilDone:YES, NSURLConnection sendSynchronousRequest indítása, kép betöltése dataWithContentsOfURL-lel
  • Cross-platform — Flutter compute dedikált isolate nélkül, React Native szinkron NativeModule

Hogyan diagnosztizáljuk a lefagyást

A lefagyás diagnosztizálásához olyan eszközökre van szükség, amelyek képesek rögzíteni az összes szál állapotát a blokkolás pillanatában.

ANR naplók Androidon

Minden ANR-nél az Android rendszer elmenti a /data/anr/traces.txt fájlt, amely az alkalmazás minden szálának veremdumpját tartalmazza. E fájl elemzése — a diagnosztika fő módszere: meg kell találni a main szálat, és megnézni, melyik metódusnál állt meg. Ha a verem Thread.sleep, InputStream.read vagy Lock.lock végződéssel zárul — az ok megtalálható.

Stackshot iOS-en

Xcode az alkalmazás lefagyásakor (SIGSTOP jelzés) készíthet Stackshot-ot — az összes szál vermének pillanatképét. Kapcsolja be a sémában a „Logging" → „Include Stackshot Logs" lehetőséget. 0x8badf00d kóddal történő összeomláskor bontsa ki a crash naplót a Devices & Simulators menüből, és keresse meg a com.apple.main-thread szálat a lefagyott veremmel.

Main Thread Checker az Xcode-ban

Main Thread Checker automatikusan észleli a UIKit hívásokat háttérszálakból az alkalmazás futása során. Kapcsolja be a sémában (Diagnostics → Main Thread Checker). Minden figyelmeztetés potenciális lefagyási ok, különösen, ha egy hálózati kérés completionHandler lezárásában fordul elő.

Példa a blokkolás észlelésére StrictMode-on keresztül Androidon:

kotlin
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
            .detectLeakedSqlLiteObjects()
            .detectLeakedClosableObjects()
            .penaltyLog()
            .build())
        StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
            .detectAll()
            .penaltyDeath()
            .build())
    }
}

A UI blokkolások megszüntetésének módszerei

A lefagyás megszüntetése az összes potenciálisan hosszú művelet háttérszálakra való áthelyezésével kezdődik. Vizsgáljuk meg a platform-specifikus technikákat.

Structured Concurrency korutinokkal

Kotlin Coroutines a viewModelScope.launch(Dispatchers.IO) segítségével garantálja, hogy a hálózati művelet vagy adatbázis olvasás háttérszálban történjen. A Dispatchers.Main csak a UI frissítésére szolgál. Fontos: minden suspend függvénynek strukturáltnak kell lennie — a gyermek korutinok a szülő lemondásakor lemondásra kerülnek, megelőzve a szálszivárgást.

Aszinkron sorok iOS-en

Grand Central Dispatch a DispatchQueue.global(qos: .userInitiated) segítségével háttérfeladatokhoz és a DispatchQueue.main.async segítségével UI frissítésekhez — a szabványos minta. Kerülje a sync() használatát a fő soron — ez garantált deadlock. Használja az async/await (Swift 5.5+) funkciót az olvashatóbb aszinkron kódhoz, automatikus visszatéréssel a fő szálra MainActor segítségével.

A synchronized kerülése a UI szálban

A synchronized blokkok Kotlinben és a @synchronized Swiftben a fő szálon veszélyesek: ha egy másik szál már megszerezte ezt a zárolást, a fő szál lefagy a várakozásban. Használjon atomikus típusokat (AtomicInteger, atomikus tulajdonságok Swiftben) vagy szekvenciális sorokat a zárolások helyett.

Példa aszinkron adatbetöltésre korutinokkal Androidon:

kotlin
class DataViewModel : ViewModel() {
    private val _data = MutableStateFlow<List<Item>>(emptyList())
    val data: StateFlow<List<Item>> = _data.asStateFlow()

    fun loadData() {
        viewModelScope.launch(Dispatchers.IO) {
            val result = fetchFromNetwork()
            _data.emit(result)
        }
    }
}

A lefagyás megelőzése a fejlesztés során

A lefagyás szisztematikus megelőzését eszközök, architektúraelvek és kódáttekintési folyamatok kombinációja segíti.

StrictMode penaltyDeath-tel

Állítsa be a StrictMode-ot penaltyDeath-tel a szálpolitikákhoz — ez az alkalmazás azonnali összeomlásához vezet, ha hálózati hívást vagy lemez I/O-t észlel a fő szálban. A fejlesztő nem hagyhatja figyelmen kívül a problémát. A production buildben használja a penaltyLog-ot a statisztikák összegyűjtéséhez összeomlás nélkül.

Main Thread Checker a Debug sémában

iOS-en kapcsolja be a Main Thread Checker-t a Debug sémában, és konfigurálja a CI-t, hogy ezzel az opcióval futtassa a teszteket. Ha a teszt UIKit hívást tartalmaz háttérszálból — meg kell hiúsulnia. Ez az egyetlen megbízható mód a probléma észlelésére a TestFlight-ba küldés előtt.

Kódáttekintés többszálúság ellenőrzéssel

Adjon hozzá a kódáttekintési folyamathoz egy kötelező pontot: ellenőrizze, hogy minden hálózati hívás, fájlművelet, adatbázis-lekérdezés vagy nehéz számítás háttérszálban történik. A deadlock statikus elemzővel észlelhető: a Facebook Infer-e és az Xcode Thread Safety Checker-je potenciális blokkolásokat talál a futtatás előtt.

  • Android — StrictMode, Infer, Android Lint Multithread, Kotlin Coroutines viewModelScope-pal
  • iOS — Main Thread Checker, TSAN (Thread Sanitizer), Xcode Analyze, Swift async/await MainActor-ral
  • Cross-platform — Flutter compute isolate, React Native interaction manager requestAnimationFrame-dzsel

Gyakran ismételt kérdések

Mi a különbség a lefagyás és az ANR között?

ANR (Application Not Responding) — egy Android rendszerértesítés, amely akkor jelenik meg, ha a fő szál több mint 5 másodpercre lefagy. A lefagyás tágabb fogalom: bármilyen UI blokkolás bármilyen időtartammal. iOS-en nincs ANR, de van Watchdog 10–20 másodperces időtúllépéssel.

Hogyan olvassuk a traces.txt fájlt Androidon?

A fájl a /data/anr/traces.txt útvonalon található. A hozzáféréshez root vagy adb shell szükséges: hajtsa végre a adb shell cat /data/anr/traces.txt \> traces.txt parancsot root jogosultságokkal. A veremben keresse meg a „main" szálat — az utoljára meghívott metódus jelzi a blokkolás okát.

Miért fagy le az alkalmazás iOS-en, de nem omlik össze?

Ha a lefagyás kevesebb mint 10 másodpercig tart, a Watchdog nem aktiválódik, és az alkalmazás egyszerűen „lefagy" a blokkoló művelet befejezéséig. A felhasználó nem lát összeomlást, de frusztrációt él át. Az ilyen esetek észleléséhez használja a MetricKit-et egyéni végrehajtási idő nyomkövetéssel.

Hogyan teszteljük az alkalmazást lefagyásra?

Használjon UI teszteket annak ellenőrzésével, hogy a képernyő 1 másodpercen belül megnyílik. Adjon hozzá a CI-hez időmérést az érintés és a következő képernyő megjelenése között. Androidon használja az Espresso-t IdlingResource-szel az aszinkron műveletek várakozásához. iOS-en XCTest-et XCTWaiter-rel a betöltési idő ellenőrzéséhez.

Okozhat-e SwiftUI lefagyást?

A SwiftUI önmagában nem okoz lefagyást, de a body tulajdonságban végzett komplex számítások — igen. Ha a body 500 ms-ig számolódik nehéz műveletek miatt, a UI lefagy. Megoldás — helyezze át a számításokat a Task.detached-be, és frissítse a @State-et aszinkron módon a fő aktoron.

Összefoglalás

  • Lefagyás — a UI teljes blokkolása másodpercekig és több tíz másodpercig, amelyet a fő szál blokkolása, deadlock vagy végtelen ciklus okoz
  • Diagnosztika — /data/anr/traces.txt Androidon, Stackshot és Main Thread Checker iOS-en
  • Fő okok — szinkron I/O, deadlock szálak között, végtelen rekurzió, hosszú GC
  • Megszüntetés — korutinok megfelelő dispatcherekkel, async/await MainActor-ral, az összes IO művelet áthelyezése háttérszálakra
  • Megelőzés — StrictMode penaltyDeath-tel, Main Thread Checker, deadlock statikus elemzés (Infer, TSAN)
  • Androidon lefagyás > 5 mp = ANR; iOS-en > 10–20 mp = Watchdog crash (0x8badf00d)
  • Javaslat: kapcsolja be a Thread Sanitizer-t a Debug sémában, és konfigurálja a CI-t TSAN tesztfuttatásra az adatverseny és deadlock észleléséhez

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.

Projekt megbeszélése

Olvassa el is