Înghețarea în dezvoltare — esență, cauze și prevenire

Autor: IT Sectr Publicat: 2026-07-28 Timp de citire: 9 min

Înghețarea (visnet) — este starea în care aplicația mobilă încetează să mai reacționeze la orice acțiune a utilizatorului pentru o perioadă îndelungată. Spre deosebire de laguri (încetinire) și glitchuri (comportament incorect), înghețarea blochează complet interfața UI: atingerile nu sunt procesate, animația se oprește, ecranul „îngheață". Cauza — blocarea firului principal de o operație sincronă, un deadlock în codul multi-thread sau o colectare a gunoiului anormal de lungă. Conform Apple Main Thread Checker Documentation, peste 40% din rapoartele de crash pe iOS sunt legate de blocarea firului principal. Pe Android, o situație similară duce la ANR — dialogul de sistem „Aplicația nu răspunde".

Principalele puncte

  • Înghețarea — blocarea completă a interfeței UI pentru o perioadă îndelungată (secunde și zeci de secunde), diferită de laguri și glitchuri
  • Cauze principale — blocarea firului principal de operații de intrare-ieșire, deadlock între fire de execuție, buclă infinită și scurgere de memorie cu GC lung
  • Diagnosticarea include Main Thread Checker pe iOS, loguri ANR /data/anr/traces.txt pe Android și analiza dump-urilor de fire
  • Remedierea — mutarea tuturor operațiilor potențial lungi în fire de fundal, utilizarea Structured Concurrency și evitarea synchronized în firul UI
  • Prevenirea — StrictMode, Main Thread Checker în schema Debug, analiză statică pentru deadlock și rularea periodică a testelor cu măsurarea timpului de răspuns

Ce este înghețarea în dezvoltarea mobilă

Înghețarea (freeze, hang) într-o aplicație mobilă — este starea în care aplicația încetează să proceseze evenimentele de intrare și să actualizeze interfața timp de câteva secunde sau mai mult. Tehnic, aceasta înseamnă că firul principal (main thread) este blocat și nu poate executa următorul ciclu al runner-ului.

Diferența dintre înghețare, lag și ANR

Lag — este o întârziere de până la 500 ms, în care utilizatorul observă încetinirea, dar aplicația continuă să funcționeze. Înghețarea durează de la 1 secundă la zeci de secunde. ANR pe Android — este un caz particular de înghețare care a durat mai mult de 5 secunde și a fost detectat de sistem. Nu orice înghețare duce la ANR, dar fiecare ANR este o înghețare documentată de sistem.

Consecințele înghețării

Pe Android, înghețarea mai lungă de 5 secunde declanșează dialogul ANR cu propunerea de a închide aplicația. Pe iOS, sistemul are un watchdog — dacă aplicația nu reacționează la evenimente timp de 10–20 de secunde, Watchdog termină procesul cu codul 0x8badf00d (ate bad food). Utilizatorul vede doar închiderea bruscă a aplicației și revenirea la ecranul principal.

Cauzele înghețării pe Android și iOS

Orice operație care durează mai mult de 100 ms și este lansată în firul principal poate provoca înghețarea. Să analizăm principalele surse de blocări.

Intrare-ieșire sincronă în firul UI

Citirea unui fișier mare, o cerere de rețea fără asincronizare, salvarea datelor în SharedPreferences prin metoda sincronă apply urmată de commit — toate aceste operații blochează firul principal. Pe Android, citirea sincronă a unui fișier de 10 MB poate dura 200–500 ms în funcție de viteza memoriei flash. Pe iOS, încărcarea sincronă URLSession fără completionHandler blochează UI pe durata răspunsului serverului.

Deadlock în codul multi-thread

Când două fire de execuție așteaptă eliberarea resurselor deținute reciproc, apare deadlock-ul. În aplicațiile mobile, scenariul tipic — firul A blochează Lock1 și așteaptă Lock2, iar firul B blochează Lock2 și așteaptă Lock1. Ambele fire îngheață pentru totdeauna. Dacă unul dintre ele este firul principal, aplicația îngheață complet.

Buclă infinită sau recursiune

O eroare de logică — de exemplu, while(true) fără condiție de ieșire sau recursiune fără caz de bază — duce la execuția infinită pe firul principal. Android detectează acest lucru prin ANR după 5 secunde, iOS — prin Stackshot, care înregistrează stiva de apeluri care se repetă la infinit.

  • Android — Cursor neînchis, cerere sincronă prin execute() în loc de enqueue(), FileInputStream.read() în firul UI
  • iOS — performSelectorOnMainThread:withObject:waitUntilDone:YES, lansarea NSURLConnection sendSynchronousRequest, încărcarea imaginii cu dataWithContentsOfURL
  • Cross-platform — Flutter compute fără isolate dedicat, React Native NativeModule sincron

Cum să diagnosticați înghețarea

Diagnosticarea înghețării necesită instrumente capabile să înregistreze starea tuturor firelor în momentul blocării.

Loguri ANR pe Android

La fiecare ANR, sistemul Android salvează fișierul /data/anr/traces.txt, care conține dump-ul stivei fiecărui fir al aplicației. Analiza acestui fișier — principala metodă de diagnosticare: trebuie găsit firul main și văzut pe ce metodă s-a oprit. Dacă stiva se termină cu Thread.sleep, InputStream.read sau Lock.lock — cauza a fost găsită.

Stackshot pe iOS

Xcode, la înghețarea aplicației (semnal SIGSTOP), poate face un Stackshot — o imagine a stivelor tuturor firelor. Activați în schemă „Logging" → „Include Stackshot Logs". La căderea cu codul 0x8badf00d, extrageți logul de crash din Devices & Simulators și găsiți firul com.apple.main-thread cu stiva înghețată.

Main Thread Checker în Xcode

Main Thread Checker detectează automat apelurile UIKit din firele de fundal în timpul funcționării aplicației. Activați-l în schemă (Diagnostics → Main Thread Checker). Fiecare avertisment este o cauză potențială de înghețare, mai ales dacă apare în închiderea completionHandler a unei cereri de rețea.

Exemplu de detectare a blocării prin StrictMode pe Android:

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())
    }
}

Metode de eliminare a blocărilor UI

Eliminarea înghețării începe cu mutarea tuturor operațiilor potențial lungi în firele de fundal. Să analizăm tehnicile specifice pentru fiecare platformă.

Structured Concurrency cu corutine

Kotlin Coroutines cu viewModelScope.launch(Dispatchers.IO) garantează că operația de rețea sau citirea din baza de date se execută într-un fir de fundal. Dispatchers.Main este folosit doar pentru actualizarea UI. Important: toate funcțiile suspend trebuie să fie structurate — corutinele copil sunt anulate la anularea părintelui, prevenind scurgerea de fire.

Coadă asincronă pe iOS

Grand Central Dispatch cu DispatchQueue.global(qos: .userInitiated) pentru sarcini de fundal și DispatchQueue.main.async pentru actualizarea UI — modelul standard. Evitați sync() pe coada principală — acesta este un deadlock garantat. Folosiți async/await (Swift 5.5+) pentru un cod asincron mai lizibil, cu revenire automată la firul principal prin MainActor.

Evitarea synchronized în firul UI

Blocările synchronized în Kotlin și @synchronized în Swift pe firul principal sunt periculoase: dacă un alt fir a preluat deja această blocare, firul principal va îngheța în așteptare. Folosiți tipuri atomice (AtomicInteger, proprietăți atomice în Swift) sau cozi secvențiale în loc de blocări.

Exemplu de încărcare asincronă a datelor cu corutine pe Android:

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)
        }
    }
}

Prevenirea înghețării în faza de dezvoltare

Prevenirea sistematică a înghețării ajută prin combinarea instrumentelor, principiilor arhitecturale și proceselor de revizuire a codului.

StrictMode cu penaltyDeath

Configurați StrictMode cu penaltyDeath pentru politicile de fire — aceasta va duce la căderea imediată a aplicației la detectarea unui apel de rețea sau a unei operații de intrare-ieșire pe disc în firul principal. Dezvoltatorul nu va putea ignora problema. În build-ul de producție, folosiți penaltyLog pentru colectarea statisticilor fără căderi.

Main Thread Checker în schema Debug

Pe iOS, activați Main Thread Checker în schema Debug și configurați CI să ruleze testele cu această opțiune. Dacă testul conține un apel UIKit dintr-un fir de fundal — trebuie să eșueze. Aceasta este singura modalitate sigură de a detecta problema înainte de trimiterea în TestFlight.

Revizuirea codului cu verificarea multi-threading-ului

Adăugați în procesul de revizuire a codului un punct obligatoriu: verificarea că orice apel de rețea, lucru cu fișiere, baza de date sau calcule grele se execută într-un fir de fundal. Deadlock-ul poate fi detectat cu un analizator static: Infer de la Facebook și Thread Safety Checker de la Xcode găsesc blocări potențiale înainte de execuție.

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

Întrebări frecvente

Care este diferența dintre înghețare și ANR?

ANR (Application Not Responding) — este o notificare de sistem Android care apare la înghețarea firului principal mai mult de 5 secunde. Înghețarea este un concept mai larg: orice blocare a UI de orice durată. Pe iOS nu există ANR, dar există Watchdog cu un timeout de 10–20 de secunde.

Cum se citește traces.txt pe Android?

Fișierul se află în /data/anr/traces.txt. Pentru acces este necesar root sau adb shell: executați adb shell cat /data/anr/traces.txt \> traces.txt cu drepturi de root. În stivă, găsiți firul „main" — ultima metodă apelată indică cauza blocării.

De ce aplicația îngheață pe iOS, dar nu se prăbușește?

Dacă înghețarea durează mai puțin de 10 secunde, Watchdog nu se activează, iar aplicația pur și simplu „îngheață" până la finalizarea operației de blocare. Utilizatorul nu vede un crash, dar experimentează frustrare. Pentru detectarea unor astfel de cazuri, utilizați MetricKit cu urmăriri personalizate ale timpului de execuție.

Cum se testează aplicația pentru înghețare?

Folosiți teste UI cu verificarea că ecranul se deschide în mai puțin de 1 secundă. Adăugați în CI măsurarea timpului între atingere și apariția ecranului următor. Pe Android, utilizați Espresso cu IdlingResource pentru a aștepta operațiile asincrone. Pe iOS, XCTest cu XCTWaiter pentru verificarea timpului de încărcare.

Poate SwiftUI să cauzeze înghețare?

SwiftUI în sine nu cauzează înghețare, dar calculele complexe în proprietatea body — da. Dacă body se calculează timp de 500 ms din cauza operațiilor grele, UI îngheață. Soluția — mutați calculele în Task.detached și actualizați @State asincron pe actorul principal.

Rezumat

  • Înghețarea — blocarea completă a UI pentru secunde și zeci de secunde, cauzată de blocarea firului principal, deadlock sau buclă infinită
  • Diagnosticarea — /data/anr/traces.txt pe Android, Stackshot și Main Thread Checker pe iOS
  • Cauze principale — intrare-ieșire sincronă, deadlock între fire, recursiune infinită, GC lung
  • Remedierea — corutine cu dispatcheri corecți, async/await cu MainActor, mutarea tuturor operațiilor IO în fire de fundal
  • Prevenirea — StrictMode cu penaltyDeath, Main Thread Checker, analiză statică de deadlock (Infer, TSAN)
  • Pe Android înghețare > 5 s = ANR; pe iOS > 10–20 s = Watchdog crash (0x8badf00d)
  • Recomandare: activați Thread Sanitizer în schema Debug și configurați CI să ruleze teste cu TSAN pentru detectarea data race și deadlock

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și