Hapert — dit is de gebruikersbeschrijving van een situatie waarin een mobiele app traag en onstabiel werkt: soms reageert hij normaal, soms bevriest hij plotseling enkele seconden. In technische context betekent „hapert“ een combinatie van lags en micro-bevriezingen veroorzaakt door frequente GC-pauzes, blokkering van de hoofdthread door synchrone bewerkingen en niet-optimale datastructuren. Volgens de Android Performance Benchmarking Guide verhoogt het verminderen van de responstijd van 300 ms naar 100 ms het gebruikersbehoud met 25%. Diagnose van haperen vereist een combinatie van CPU- en Memory-profilering met analyse van de frequentie van garbagecollection.
Belangrijkste punten
Hapert — een informele term waarmee gebruikers subjectief trage app-prestaties beschrijven. In tegenstelling tot lag, die zich manifesteert als een constante vertraging, is haperen onregelmatig bevriezen: de app kan enkele seconden perfect werken en dan 1–3 seconden „denken“.
Vanuit profileringsoogpunt manifesteert haperen zich als een reeks overgeslagen frames (jank) met piekvertragingen van meer dan 100 ms. Op de FPS-grafiek ziet dit eruit als scherpe dalingen: 60 → 20 → 55 → 10 frames per seconde. In tegenstelling tot lag met gelijkmatig lage FPS, heeft haperen een duidelijke variabiliteit.
Wanneer de app hapert, begrijpt de gebruiker de logica van de vertragingen niet: het scherm kan vloeiend scrollen en dan plotseling een seconde stoppen. Dit veroorzaakt frustratie en vermindert het vertrouwen in de app. Volgens Google verlaat 53% van de gebruikers een site of app als het laden langer dan 3 seconden duurt.
Het onregelmatige karakter van haperen geeft aan dat het probleem wordt veroorzaakt door gebeurtenisfactoren, niet door constante overbelasting. Laten we typische scenario's bekijken.
Op Android in de ART-omgeving stopt garbagecollection alle threads van de app. Als er in de code veel tijdelijke objecten worden gemaakt — bijvoorbeeld bij elke onBindViewHolder-aanroep wordt een nieuwe String gemaakt door concatenatie — start GC vaker. De pauze kan 5–50 ms duren, afhankelijk van de heapgrootte en objectgeneratie. De gebruiker ervaart dit als plotseling „denken“.
Room op Android en Core Data op iOS ondersteunen asynchrone query's, maar ontwikkelaars roepen vaak getValue() aan of voeren query's uit via runBlocking voor de eenvoud. Een zware SELECT met joins op een tabel van 10.000 rijen kan 200–500 ms duren en de UI gedurende die tijd volledig blokkeren.
Het laden van een camerabeeld (12 Mp, 4000x3000 px) zonder schalen duurt tot 200 ms om naar Bitmap te decoderen. Als afbeeldingen asynchroon worden geladen maar zonder een beperkte threadpool, kan het gelijktijdig starten van 5–6 decoderingen de CPU overbelasten, wat migrerende vertragingen veroorzaakt.
Diagnose van onregelmatige vertragingen is moeilijker dan diagnose van constante lags, omdat het probleem mogelijk niet bij elke start optreedt. Het verzamelen van statistieken over een langere periode is vereist.
Android Studio Memory Profiler toont niet alleen geheugengebruik, maar ook GC-gebeurtenissen: frequentie, type (Concurrent, Full), duur. Als GC vaker dan 1 keer per 5 seconden optreedt in rusttoestand — dit is een teken van overmatige allocatie. Het opnemen van een heapdump op het moment van haperen laat zien welke objecten het geheugen bezetten.
Gebruik op iOS het Allocations-sjabloon in Instruments voor het volgen van objectcreatie en -vrijgave. Schakel generaties (Generations) in — ze maken momentopnames van de heap tussen acties mogelijk en laten zien welke objecten in het geheugen blijven. Aanhoudende objecten die niet worden vrijgegeven — bron van geheugenophoping en volgende pauzes.
JankStats — Android-bibliotheek die in realtime statistieken van overgeslagen frames verzamelt. Het koppelt elke jank aan het huidige scenario (bijv. „lijst scrollen“, „scherm openen“), waardoor duidelijk wordt bij welke actie het haperen optreedt.
Voorbeeld van JankStats-integratie voor het volgen van bevriezingen op Android:
class MainActivity : AppCompatActivity() {
private lateinit var jankStats: JankStats
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
jankStats = JankStats.create(this.window.decorView) { frameData ->
if (frameData.isJank()) {
Log.w("Jank", "Duration=${frameData.durationMs}ms")
}
}
}
}
Het verhelpen van haperen vereist gericht werk met elke oorzaak. Er is geen universele oplossing — analyse van specifieke prestatieprofielen is nodig.
Als de lijst 1000+ elementen bevat en ze worden allemaal tegelijk geladen — dit is gegarandeerd haperen. Paging 3 op Android en NSFetchedResultsController op iOS laden gegevens in porties tijdens het scrollen. De gebruiker ziet alleen de eerste 10–20 elementen, de rest wordt op de achtergrond geladen.
Room maakt profilering van query's mogelijk via Inspection Tool in Android Studio: uitvoeringstijd, aantal geretourneerde rijen en queryplan zijn zichtbaar. Het toevoegen van indexen op WHERE- en ORDER BY-kolommen kan de querytijd verminderen van 300 ms naar 5 ms. Op iOS wordt een vergelijkbare controle uitgevoerd door Core Data Profiler in Instruments.
Achtergrondsynchronisaties, bestandsuploads, gegevensverwerking — dit alles moet worden uitgevoerd via WorkManager (Android) of Background Tasks (iOS). Als synchronisatie in de UI-thread wordt gestart, zal de app haperen tijdens de uitvoering. WorkManager garandeert uitvoering in de achtergrondthread, rekening houdend met batterij- en netwerkstatus.
Voorbeeld van achtergrondsynchronisatie via WorkManager op Android:
class SyncWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
Log.d("Sync", "Gegevens synchroniseren in achtergrondthread")
syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
Haperen kan worden voorkomen in de coderingsfase door de principes van efficiënt werken met geheugen en threads te volgen.
Baseline Profiles — een lijst van klassen en methoden die Android vooraf (AOT) compileert, niet JIT. Zonder profiel wordt elk nieuw scherm bij de eerste opening gecompileerd, wat een vertraging van 100–500 ms veroorzaakt. Maak een Baseline Profile voor belangrijke schermen en schakel generatie in Gradle in via baseline-profile-gradle-plugin.
Hot path — code die bij elk frame wordt uitgevoerd: onBindViewHolder, draw, layoutSubviews. Vermijd het maken van objecten in deze methoden: gebruik een objectpool, StringBuilder in plaats van concatenatie, cache geformatteerde strings en formatters. Elke extra allocatie brengt de volgende GC dichterbij.
Voeg aan de CI-pipeline het uitvoeren van Macrobenchmark toe met een scenario van lijst scrollen en scherm openen. Stel een drempel in: het 99e percentiel van de frametijd mag niet hoger zijn dan 16 ms. Als de drempel wordt overschreden — wordt de build afgewezen tot optimalisatie.
Veelgestelde vragen
Lag — een constante vertraging (bijv. 200 ms per tik). Hapert — onregelmatig: de app werkt normaal, vertraagt dan plotseling 1–3 seconden en werkt dan weer normaal. Oorzaak — gebeurtenisfactoren zoals GC-pauzes of synchrone databasequery's.
Gebruik Memory Profiler in Android Studio: het tabblad Memory toont GC-gebeurtenissen met duur. Voor productiemonitoring sluit u Firebase Performance Monitoring aan met aangepaste traces. Schakel op iOS Malloc Debug in en markeer allocatiegeneraties in Instruments.
Indirect — ja. Als het serverantwoord met vertraging komt en de UI er synchroon op wacht, bevriest de app. Als het verzoek asynchroon is maar de verwerking van het antwoord in de UI-thread plaatsvindt — veroorzaakt dit ook haperen. Oplossing — asynchrone verwerking met coroutines en voortgangsindicatoren.
Bij verkeerd gebruik kan KMP overbodige wrapper-objecten genereren voor interoperabiliteit. Op iOS verhoogt dit de allocatiefrequentie en bijgevolg ARC-pauzes. Gebruik @ObjCName, optimaliseer expect/actual en vermijd frequente aanroepen van gedeelde code vanuit hot paths van de UI.
Het vergroten van de heap via android:largeHeap="true" stelt GC uit maar elimineert de oorzaak van allocaties niet. Wanneer GC uiteindelijk wordt gestart, zal de pauze langer zijn omdat er meer objecten moeten worden doorlopen. Oplossing — verminder het aantal allocaties, vergroot niet de heap.
Samenvatting
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.
Lees ook