Ruckeln ist die Beschreibung einer Situation durch den Benutzer, in der eine mobile App langsam und unbeständig läuft: mal reagiert sie normal, mal friert sie plötzlich für einige Sekunden ein. Im technischen Kontext bedeutet „Ruckeln“ eine Kombination aus Lags und Mikroeinfrieren, die durch häufige GC-Pausen, Blockierung des Hauptthreads durch synchrone Operationen und suboptimale Datenstrukturen verursacht wird. Laut dem Android Performance Benchmarking Guide erhöht die Reduzierung der Reaktionszeit von 300 ms auf 100 ms die Benutzerbindung um 25%. Die Diagnose von Ruckeln erfordert eine Kombination aus CPU- und Memory-Profiling mit Analyse der Garbage-Collection-Häufigkeit.
Wichtige Punkte
Ruckeln ist ein informeller Begriff, den Benutzer verwenden, um eine subjektiv langsame App-Leistung zu beschreiben. Im Gegensatz zu einem Lag, der sich als konstante Verzögerung äußert, besteht Ruckeln aus unregelmäßigen Einfrieren: Die App kann mehrere Sekunden lang einwandfrei funktionieren und dann plötzlich 1–3 Sekunden lang „nachdenken“.
Aus Profiling-Perspektive äußert sich Ruckeln als eine Serie von verpassten Frames (Jank) mit Spitzenverzögerungen von über 100 ms. In einem FPS-Diagramm sieht dies wie scharfe Einbrüche aus: 60 → 20 → 55 → 10 Bilder pro Sekunde. Im Gegensatz zu einem Lag mit gleichmäßig niedrigem FPS hat Ruckeln eine ausgeprägte Variabilität.
Wenn eine App ruckelt, kann der Benutzer die Logik der Verlangsamungen nicht nachvollziehen: Der Bildschirm kann sanft scrollen und dann plötzlich für eine Sekunde anhalten. Dies verursacht Frustration und verringert das Vertrauen in die App. Laut Google verlassen 53% der Benutzer eine Website oder App, wenn das Laden länger als 3 Sekunden dauert.
Die intermittierende Natur des Ruckelns deutet darauf hin, dass das Problem durch ereignisgesteuerte Faktoren und nicht durch konstante Überlastung verursacht wird. Betrachten wir die typischen Szenarien.
Unter Android in der ART-Laufzeitumgebung stoppt die Garbage Collection alle App-Threads. Wenn der Code viele temporäre Objekte erstellt — zum Beispiel bei jedem onBindViewHolder-Aufruf einen neuen String durch Verkettung — läuft der GC häufiger. Eine Pause kann je nach Heap-Größe und Objektgenerierung 5–50 ms dauern. Der Benutzer nimmt dies als plötzliches „Nachdenken“ wahr.
Room unter Android und Core Data unter iOS unterstützen asynchrone Abfragen, aber Entwickler rufen oft der Einfachheit halber getValue() auf oder führen Abfragen über runBlocking aus. Ein schwerer SELECT mit Joins auf einer Tabelle mit 10.000 Zeilen kann 200–500 ms dauern und die UI während dieser Zeit vollständig blockieren.
Das Laden eines Kamerabildes (12 MP, 4000x3000 px) ohne Skalierung dauert bis zu 200 ms für die Bitmap-Decodierung. Wenn Bilder asynchron geladen werden, aber ohne einen begrenzten Thread-Pool, kann das gleichzeitige Ausführen von 5–6 Decodierungen die CPU überlasten und wandernde Verlangsamungen verursachen.
Die Diagnose intermittierender Verlangsamungen ist schwieriger als die Diagnose konstanter Lags, da das Problem nicht bei jedem Lauf reproduziert werden kann. Eine Statistiksammlung über einen langen Zeitraum ist erforderlich.
Android Studio Memory Profiler zeigt nicht nur die Speichernutzung, sondern auch GC-Ereignisse: Häufigkeit, Typ (Concurrent, Full), Dauer. Wenn GC im Leerlauf mehr als einmal alle 5 Sekunden auftritt, ist das ein Zeichen für übermäßige Allokation. Ein Heap-Dump im Moment des Ruckelns zeigt, welche Objekte Speicher belegen.
Verwenden Sie unter iOS die Vorlage Allocations in Instruments, um die Objekterstellung und -freigabe zu verfolgen. Aktivieren Sie Generations — sie ermöglichen Heap-Momentaufnahmen zwischen Aktionen und zeigen, welche Objekte im Speicher verbleiben. Persistente Objekte, die nicht freigegeben werden, sind eine Quelle für Speicheransammlung und nachfolgende Pausen.
JankStats ist eine Android-Bibliothek, die Metriken zu verpassten Frames in Echtzeit sammelt. Sie verknüpft jeden Jank mit dem aktuellen Szenario (z. B. „Listenscrollen“, „Bildschirmöffnung“), sodass nachvollzogen werden kann, welche spezifische Aktion das Ruckeln auslöst.
Beispiel der Integration von JankStats zur Verfolgung von Einfrieren unter 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")
}
}
}
}
Die Beseitigung von Ruckeln erfordert gezielte Arbeit an jeder Ursache. Es gibt keine universelle Lösung — eine Analyse spezifischer Leistungsprofile ist erforderlich.
Wenn eine Liste 1000+ Elemente enthält und alle auf einmal geladen werden, ist das garantiertes Ruckeln. Paging 3 unter Android und NSFetchedResultsController unter iOS laden Daten portionsweise, während der Benutzer scrollt. Der Benutzer sieht nur die ersten 10–20 Elemente; der Rest wird im Hintergrund geladen.
Room ermöglicht das Profiling von Abfragen über das Inspection Tool in Android Studio: Ausführungszeit, Anzahl der zurückgegebenen Zeilen und Abfrageplan sind sichtbar. Das Hinzufügen von Indizes auf WHERE- und ORDER BY-Spalten kann die Abfragezeit von 300 ms auf 5 ms reduzieren. Unter iOS führt der Core Data Profiler in Instruments eine ähnliche Prüfung durch.
Hintergrundsynchronisationen, Dateidownloads, Datenverarbeitung — all dies sollte über WorkManager (Android) oder Background Tasks (iOS) ausgeführt werden. Wenn die Synchronisation im UI-Thread läuft, ruckelt die App während der Ausführung. WorkManager garantiert die Ausführung in einem Hintergrundthread unter Berücksichtigung des Batterie- und Netzwerkzustands.
Beispiel einer Hintergrundsynchronisation über WorkManager unter Android:
class SyncWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
Log.d("Sync", "Syncing data in background thread")
syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
Ruckeln kann bereits in der Codierungsphase verhindert werden, indem man den Prinzipien eines effizienten Speicher- und Thread-Managements folgt.
Baseline Profiles sind eine Liste von Klassen und Methoden, die Android vorab (AOT) statt per JIT kompiliert. Ohne Profil wird jeder neue Bildschirm beim ersten Öffnen kompiliert, was eine Verzögerung von 100–500 ms verursacht. Erstellen Sie ein Baseline Profile für wichtige Bildschirme und aktivieren Sie die Generierung in Gradle über das baseline-profile-gradle-plugin.
Ein heißer Pfad ist Code, der bei jedem Frame ausgeführt wird: onBindViewHolder, draw, layoutSubviews. Vermeiden Sie die Objekterstellung in diesen Methoden: verwenden Sie Objektpools, StringBuilder statt Verkettung, cachen Sie formatierte Strings und Formatierer. Jede zusätzliche Allokation bringt den nächsten GC näher.
Fügen Sie Macrobenchmark zu Ihrer CI-Pipeline mit einem Szenario für Listenscrollen und Bildschirmöffnung hinzu. Legen Sie einen Schwellenwert fest: Das 99. Perzentil der Frame-Zeit darf 16 ms nicht überschreiten. Wird der Schwellenwert überschritten, wird der Build bis zur Optimierung abgelehnt.
Häufig gestellte Fragen
Ein Lag ist eine konstante Verzögerung (z. B. 200 ms bei jedem Tippen). Ruckeln ist intermittierend: Die App funktioniert normal, verlangsamt sich dann plötzlich für 1–3 Sekunden und kehrt dann zur Normalität zurück. Die Ursache sind ereignisgesteuerte Faktoren wie GC-Pausen oder synchrone Datenbankabfragen.
Verwenden Sie Memory Profiler in Android Studio: Der Memory-Tab zeigt GC-Ereignisse mit Dauer an. Für das Produktionsmonitoring integrieren Sie Firebase Performance Monitoring mit benutzerdefinierten Traces. Aktivieren Sie unter iOS Malloc Debug und markieren Sie Allokationsgenerationen in Instruments.
Indirekt — ja. Wenn die Serverantwort verzögert wird und die UI synchron darauf wartet, friert die App ein. Wenn die Anfrage asynchron ist, aber die Antwortverarbeitung im UI-Thread erfolgt, verursacht das ebenfalls Ruckeln. Die Lösung ist die asynchrone Verarbeitung mit Koroutinen und Fortschrittsindikatoren.
Bei falscher Verwendung kann KMP übermäßige Wrapper-Objekte für die Interoperabilität erzeugen. Unter iOS erhöht dies die Allokationshäufigkeit und folglich die ARC-Pausen. Verwenden Sie @ObjCName, optimieren Sie expect/actual und vermeiden Sie häufige Aufrufe von Shared-Code aus heißen UI-Pfaden.
Die Vergrößerung des Heaps über android:largeHeap=”true” verzögert den GC, beseitigt aber nicht die Ursache der Allokationen. Wenn der GC schließlich ausgeführt wird, ist die Pause länger, weil mehr Objekte durchlaufen werden müssen. Die Lösung ist, die Anzahl der Allokationen zu reduzieren, nicht den Heap zu erweitern.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch