Bevriezingen in ontwikkeling — essentie, oorzaken en preventie

Auteur: IT Sectr Gepubliceerd: 2026-07-28 Leestijd: 9 min

Bevriezen (visnet) — een toestand waarbij een mobiele app gedurende lange tijd niet reageert op gebruikersacties. In tegenstelling tot lag (vertraging) en glitches (incorrect gedrag), blokkeert bevriezing de UI volledig: aanrakingen worden niet verwerkt, animatie stopt, het scherm „bevriest". De oorzaak is blokkering van de hoofdthread door een synchrone bewerking, deadlock in multi-thread code of abnormaal lange garbage collection. Volgens Apple Main Thread Checker Documentation houdt meer dan 40% van de crashrapporten op iOS verband met blokkering van de hoofdthread. Op Android leidt een vergelijkbare situatie tot ANR — het systeemdialoogvenster „App reageert niet".

Belangrijkste punten

  • Bevriezen — volledige UI-blokkering voor langere tijd (seconden en tientallen seconden), anders dan lag en glitches
  • Belangrijkste oorzaken — blokkering van de hoofdthread door I/O, deadlock tussen threads, oneindige lus en geheugenlek met lange GC
  • Diagnostiek omvat Main Thread Checker op iOS, ANR-logboeken /data/anr/traces.txt op Android en analyse van thread-dumps
  • Oplossing — verplaatsing van alle potentieel lange bewerkingen naar achtergrondthreads, gebruik van Structured Concurrency en vermijden van synchronized in de UI-thread
  • Preventie — StrictMode, Main Thread Checker in Debug-schema, statische analyse op deadlock en periodieke testruns met meting van responstijd

Wat is bevriezen in mobiele ontwikkeling

Bevriezen (freeze, hang) in een mobiele app — een toestand waarbij de app gedurende enkele seconden of langer stopt met het verwerken van invoergebeurtenissen en het bijwerken van de interface. Technisch betekent dit dat de hoofdthread (main thread) is geblokkeerd en de volgende runner-cyclus niet kan uitvoeren.

Verschil tussen bevriezing, lag en ANR

Lag — een vertraging tot 500 ms, waarbij de gebruiker traagheid opmerkt maar de app blijft werken. Bevriezing duurt van 1 seconde tot tientallen seconden. ANR op Android — een speciaal geval van bevriezing dat langer dan 5 seconden duurde en door het systeem werd gedetecteerd. Niet elke bevriezing leidt tot ANR, maar elke ANR is een door het systeem gedocumenteerde bevriezing.

Gevolgen van bevriezing

Op Android veroorzaakt een bevriezing van meer dan 5 seconden een ANR-dialoogvenster met het voorstel de app te sluiten. Op iOS heeft het systeem een watchdog — als de app niet binnen 10–20 seconden op gebeurtenissen reageert, beëindigt Watchdog het proces met code 0x8badf00d (ate bad food). De gebruiker ziet alleen het plotseling sluiten van de app en terugkeer naar het startscherm.

Oorzaken van bevriezing op Android en iOS

Elke bewerking die langer dan 100 ms duurt en in de hoofdthread wordt gestart, kan mogelijk bevriezing veroorzaken. Laten we de belangrijkste bronnen van blokkering bekijken.

Synchrone I/O in de UI-thread

Het lezen van een groot bestand, een netwerkverzoek zonder asynchroniteit, het opslaan van gegevens in SharedPreferences via de synchrone methode apply gevolgd door commit — al deze bewerkingen blokkeren de hoofdthread. Op Android kan het synchroon lezen van een bestand van 10 MB 200–500 ms duren, afhankelijk van de snelheid van het flashgeheugen. Op iOS blokkeert synchrone URLSession-lading zonder completionHandler de UI gedurende de serverreactietijd.

Deadlock in multi-thread code

Wanneer twee threads wachten op het vrijgeven van bronnen die door elkaar worden vastgehouden, ontstaat een deadlock. In mobiele apps is het typische scenario — thread A blokkeert Lock1 en wacht op Lock2, terwijl thread B Lock2 blokkeert en wacht op Lock1. Beide threads bevriezen voor altijd. Als een van hen de hoofdthread is, bevriest de app volledig.

Oneindige lus of recursie

Een fout in de logica — bijvoorbeeld while(true) zonder uitgangsvoorwaarde of recursie zonder basisgeval — leidt tot oneindige uitvoering op de hoofdthread. Android detecteert dit via ANR na 5 seconden, iOS — via Stackshot, dat een oneindig herhalende call-stack vastlegt.

  • Android — Cursor zonder sluiting, synchrone aanvraag via execute() in plaats van enqueue(), FileInputStream.read() in de UI-thread
  • iOS — performSelectorOnMainThread:withObject:waitUntilDone:YES, starten van NSURLConnection sendSynchronousRequest, laden van afbeelding met dataWithContentsOfURL
  • Cross-platform — Flutter compute zonder dedicated isolate, React Native synchrone NativeModule

Hoe bevriezing te diagnosticeren

Diagnostiek van bevriezing vereist hulpmiddelen die de toestand van alle threads op het moment van blokkering kunnen vastleggen.

ANR-logboeken op Android

Bij elke ANR slaat het Android-systeem het bestand /data/anr/traces.txt op, met daarin een stackdump van elke thread van de app. Analyse van dit bestand — de belangrijkste diagnostische methode: zoek de main-thread en kijk op welke methode deze is gestopt. Als de stack eindigt op Thread.sleep, InputStream.read of Lock.lock — is de oorzaak gevonden.

Stackshot op iOS

Xcode kan bij het bevriezen van de app (signaal SIGSTOP) een Stackshot maken — een momentopname van de stacks van alle threads. Schakel in het schema „Logging" → „Include Stackshot Logs" in. Bij een crash met code 0x8badf00d haalt u het crashlog uit Devices & Simulators en zoekt u de thread com.apple.main-thread met de bevroren stack.

Main Thread Checker in Xcode

Main Thread Checker detecteert automatisch UIKit-aanroepen vanuit achtergrondthreads tijdens het gebruik van de app. Schakel het in het schema in (Diagnostics → Main Thread Checker). Elke waarschuwing is een mogelijke oorzaak van bevriezing, vooral als deze voorkomt in de afsluiting van een completionHandler van een netwerkverzoek.

Voorbeeld van detectie van blokkering via StrictMode op 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())
    }
}

Methoden om UI-blokkeringen op te heffen

Het oplossen van bevriezing begint met het verplaatsen van alle potentieel lange bewerkingen naar achtergrondthreads. Laten we de specifieke technieken voor elk platform bekijken.

Structured Concurrency met coroutines

Kotlin Coroutines met viewModelScope.launch(Dispatchers.IO) garanderen dat een netwerkbewerking of database-lezing in een achtergrondthread wordt uitgevoerd. Dispatchers.Main wordt alleen gebruikt voor het bijwerken van de UI. Belangrijk: alle suspend-functies moeten gestructureerd zijn — onderliggende coroutines worden geannuleerd bij annulering van de ouder, waardoor threadlekken worden voorkomen.

Asynchrone wachtrijen op iOS

Grand Central Dispatch met DispatchQueue.global(qos: .userInitiated) voor achtergrondtaken en DispatchQueue.main.async voor het bijwerken van de UI — het standaardpatroon. Vermijd sync() op de hoofdwachtrij — dit is een gegarandeerde deadlock. Gebruik async/await (Swift 5.5+) voor beter leesbare asynchrone code met automatische terugkeer naar de hoofdthread via MainActor.

Vermijden van synchronized in de UI-thread

synchronized-blokken in Kotlin en @synchronized in Swift op de hoofdthread zijn gevaarlijk: als een andere thread deze blokkering al heeft verkregen, zal de hoofdthread bevriezen in afwachting. Gebruik atomaire types (AtomicInteger, atomaire eigenschappen in Swift) of sequentiële wachtrijen in plaats van blokkeringen.

Voorbeeld van asynchroon laden van gegevens met coroutines op 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)
        }
    }
}

Preventie van bevriezing tijdens de ontwikkeling

Het systematisch voorkomen van bevriezing helpt met een combinatie van hulpmiddelen, architectuurprincipes en code-reviewprocessen.

StrictMode met penaltyDeath

Configureer StrictMode met penaltyDeath voor threadbeleid — dit leidt tot onmiddellijke crash van de app bij detectie van een netwerkoproep of schijf-I/O op de hoofdthread. De ontwikkelaar kan het probleem niet negeren. Gebruik in de productiebuild penaltyLog voor het verzamelen van statistieken zonder crashes.

Main Thread Checker in Debug-schema

Schakel op iOS Main Thread Checker in het Debug-schema in en configureer CI om tests met deze optie uit te voeren. Als de test een UIKit-aanroep vanuit een achtergrondthread bevat, moet deze mislukken. Dit is de enige betrouwbare manier om het probleem te detecteren voordat het naar TestFlight wordt gestuurd.

Code-review met controle op multi-threading

Voeg aan het code-reviewproces een verplicht punt toe: controleer of elke netwerkoproep, bestandsbewerking, database-query of zware berekening in een achtergrondthread wordt uitgevoerd. Deadlock kan worden gedetecteerd met een statische analyser: Infer van Facebook en Thread Safety Checker van Xcode vinden potentiële blokkeringen vóór uitvoering.

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

Veelgestelde vragen

Wat is het verschil tussen bevriezing en ANR?

ANR (Application Not Responding) — een systeemmelding van Android die verschijnt wanneer de hoofdthread langer dan 5 seconden bevriest. Bevriezing is een breder begrip: elke UI-blokkering van elke duur. Op iOS bestaat ANR niet, maar er is een Watchdog met een time-out van 10–20 seconden.

Hoe lees ik traces.txt op Android?

Het bestand bevindt zich in /data/anr/traces.txt. Voor toegang is root of adb shell vereist: voer adb shell cat /data/anr/traces.txt \> traces.txt uit met root-rechten. Zoek in de stack naar de thread „main" — de laatst aangeroepen methode wijst op de oorzaak van de blokkering.

Waarom bevriest de app op iOS maar crasht deze niet?

Als de bevriezing minder dan 10 seconden duurt, wordt Watchdog niet geactiveerd en blijft de app gewoon „bevroren" tot de blokkerende bewerking is voltooid. De gebruiker ziet geen crash, maar ervaart frustratie. Gebruik MetricKit met aangepaste uitvoeringstijd-tracings om dergelijke gevallen te detecteren.

Hoe test ik de app op bevriezing?

Gebruik UI-tests met controle of het scherm in minder dan 1 seconde wordt geopend. Voeg in CI meting toe van de tijd tussen aanraking en het verschijnen van het volgende scherm. Gebruik op Android Espresso met IdlingResource om te wachten op asynchrone bewerkingen. Op iOS XCTest met XCTWaiter om de laadtijd te controleren.

Kan SwiftUI bevriezing veroorzaken?

SwiftUI veroorzaakt op zichzelf geen bevriezing, maar complexe berekeningen in de body-eigenschap — wel. Als body gedurende 500 ms wordt berekend vanwege zware bewerkingen, bevriest de UI. Oplossing — verplaats de berekeningen naar Task.detached en werk @State asynchroon bij op de hoofdactor.

Samenvatting

  • Bevriezing — volledige UI-blokkering gedurende seconden en tientallen seconden, veroorzaakt door blokkering van de hoofdthread, deadlock of oneindige lus
  • Diagnostiek — /data/anr/traces.txt op Android, Stackshot en Main Thread Checker op iOS
  • Belangrijkste oorzaken — synchrone I/O, deadlock tussen threads, oneindige recursie, lange GC
  • Oplossing — coroutines met correcte dispatchers, async/await met MainActor, verplaatsing van alle I/O-bewerkingen naar achtergrondthreads
  • Preventie — StrictMode met penaltyDeath, Main Thread Checker, statische deadlock-analyse (Infer, TSAN)
  • Op Android bevriezing > 5 s = ANR; op iOS > 10–20 s = Watchdog crash (0x8badf00d)
  • Aanbeveling: schakel Thread Sanitizer in het Debug-schema in en configureer CI om tests met TSAN uit te voeren voor detectie van data races en deadlocks

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook