Î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 (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.
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.
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.
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.
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.
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.
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.
Diagnosticarea înghețării necesită instrumente capabile să înregistreze starea tuturor firelor în momentul blocării.
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ă.
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 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:
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())
}
}
Eliminarea înghețării începe cu mutarea tuturor operațiilor potențial lungi în firele de fundal. Să analizăm tehnicile specifice pentru fiecare platformă.
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.
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.
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:
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 sistematică a înghețării ajută prin combinarea instrumentelor, principiilor arhitecturale și proceselor de revizuire a codului.
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.
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.
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.
Întrebări frecvente
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.
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.
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.
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.
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
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.
Citiți și