Einfrieren (Hängenbleiben) ist ein Zustand, in dem eine mobile App für längere Zeit auf keine Benutzeraktionen mehr reagiert. Im Gegensatz zu Lags (Verlangsamung) und Glitches (fehlerhaftem Verhalten) blockiert das Einfrieren die UI vollständig: Berührungen werden nicht verarbeitet, Animationen stoppen, der Bildschirm „gefriert“. Die Ursache ist die Blockierung des Hauptthreads durch eine synchrone Operation, ein Deadlock in Multithread-Code oder eine ungewöhnlich lange Garbage Collection. Laut Apple Main Thread Checker Dokumentation stehen mehr als 40 % der iOS-Crashberichte im Zusammenhang mit der Blockierung des Hauptthreads. Unter Android führt eine ähnliche Situation zu ANR — dem Systemdialog „App antwortet nicht“.
Wichtige Punkte
Einfrieren (Hängenbleiben) in einer mobilen App ist ein Zustand, in dem die App für mehrere Sekunden oder länger keine Eingabeereignisse mehr verarbeitet und die Oberfläche nicht aktualisiert. Technisch bedeutet dies, dass der Hauptthread blockiert ist und die nächste Run-Loop- Iteration nicht ausführen kann.
Ein Lag ist eine Verzögerung von bis zu 500 ms, bei der der Benutzer eine Verlangsamung bemerkt, die App aber weiterläuft. Einfrieren dauert von 1 Sekunde bis zu mehreren Sekunden. ANR auf Android ist ein Sonderfall eines Einfrierens, das länger als 5 Sekunden dauerte und vom System erkannt wurde. Nicht jedes Einfrieren führt zu ANR, aber jedes ANR ist ein systemdokumentiertes Einfrieren.
Unter Android löst ein Einfrieren von mehr als 5 Sekunden einen ANR-Dialog aus, der zum Schließen der App auffordert. Unter iOS verfügt das System über einen Watchdog — wenn die App 10–20 Sekunden lang nicht auf Ereignisse reagiert, beendet der Watchdog den Prozess mit dem Code 0x8badf00d (ate bad food). Der Benutzer sieht nur, wie die App plötzlich zum Startbildschirm geschlossen wird.
Jeder Vorgang, der länger als 100 ms dauert und im Hauptthread ausgeführt wird, kann potenziell ein Einfrieren verursachen. Sehen wir uns die Hauptquellen von Blockaden an.
Lesen einer großen Datei, eine Netzwerkanfrage ohne Asynchronität, Speichern von Daten in SharedPreferences mit der synchronen Methode apply gefolgt von commit — all diese Operationen blockieren den Hauptthread. Unter Android kann das synchrone Lesen einer 10 MB großen Datei je nach Flash-Speichergeschwindigkeit 200–500 ms dauern. Unter iOS blockiert ein synchroner URLSession-Ladevorgang ohne completionHandler die UI für die Dauer der Serverantwort.
Wenn zwei Threads auf Ressourcen warten, die vom jeweils anderen gehalten werden, entsteht ein Deadlock. In mobilen Apps ist ein typisches Szenario: Thread A sperrt Lock1 und wartet auf Lock2, während Thread B Lock2 sperrt und auf Lock1 wartet. Beide Threads frieren für immer ein. Wenn einer davon der Hauptthread ist, friert die App vollständig ein.
Ein Logikfehler — zum Beispiel while(true) ohne Ausstiegsbedingung oder Rekursion ohne Basisfall — führt zu einer unendlichen Ausführung im Hauptthread. Android erkennt dies nach 5 Sekunden über ANR, iOS — über Stackshot, das einen sich unendlich wiederholenden Aufrufstapel erfasst.
Die Diagnose von Einfrierungen erfordert Werkzeuge, die den Zustand aller Threads im Moment der Blockierung erfassen können.
Bei jedem ANR speichert das Android-System eine Datei /data/anr/traces.txt mit einem Stapeldump jedes App-Threads. Die Analyse dieser Datei ist die wichtigste Diagnosemethode: Finden Sie den main-Thread und sehen Sie, bei welcher Methode er gestoppt hat. Wenn der Stack mit Thread.sleep, InputStream.read oder Lock.lock endet — ist die Ursache gefunden.
Xcode kann beim Einfrieren der App (SIGSTOP-Signal) einen Stackshot — eine Momentaufnahme aller Thread-Stacks — erstellen. Aktivieren Sie „Logging“ → „Include Stackshot Logs“ im Schema. Bei einem Absturz mit Code 0x8badf00d extrahieren Sie das Crash-Protokoll aus Devices & Simulators und suchen Sie den com.apple.main-thread mit dem feststeckenden Stack.
Main Thread Checker erkennt automatisch UIKit-Aufrufe aus Hintergrundthreads während der Ausführung der App. Aktivieren Sie es im Schema (Diagnostics → Main Thread Checker). Jede Warnung ist eine potenzielle Ursache für ein Einfrieren, insbesondere wenn sie in einem completionHandler-Closure einer Netzwerkanfrage auftritt.
Beispiel für die Erkennung von Blockaden via StrictMode unter 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())
}
}
Die Beseitigung von Einfrierungen beginnt damit, alle potenziell langen Operationen in Hintergrundthreads zu verschieben. Sehen wir uns spezifische Techniken für jede Plattform an.
Kotlin Coroutines mit viewModelScope.launch(Dispatchers.IO) stellen sicher, dass Netzwerkoperationen oder Datenbanklesevorgänge in einem Hintergrundthread ausgeführt werden. Dispatchers.Main wird nur für UI-Updates verwendet. Wichtig: Alle suspend-Funktionen müssen strukturiert sein — untergeordnete Coroutinen werden bei Abbruch der übergeordneten abgebrochen, wodurch Thread-Leaks verhindert werden.
Grand Central Dispatch mit DispatchQueue.global(qos: .userInitiated) für Hintergrundaufgaben und DispatchQueue.main.async für UI-Updates ist das Standardmuster. Vermeiden Sie sync() in der Hauptwarteschlange — dies ist ein garantierter Deadlock. Verwenden Sie async/await (Swift 5.5+) für besser lesbaren asynchronen Code mit automatischer Rückkehr zum Hauptthread über MainActor.
Synchronized-Blöcke in Kotlin und @synchronized in Swift im Hauptthread sind gefährlich: Wenn ein anderer Thread diese Sperre bereits erworben hat, friert der Hauptthread wartend ein. Verwenden Sie atomare Typen (AtomicInteger, atomare Eigenschaften in Swift) oder serielle Warteschlangen anstelle von Sperren.
Beispiel für asynchrones Laden von Daten mit Coroutinen unter 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)
}
}
}
Eine Kombination aus Werkzeugen, Architekturprinzipien und Code-Review-Prozessen hilft, Einfrierungen systematisch zu verhindern.
Konfigurieren Sie StrictMode mit penaltyDeath für Thread-Richtlinien — dies führt zu einem sofortigen App-Absturz, wenn ein Netzwerkaufruf oder Festplatten-E/A im Hauptthread erkannt wird. Der Entwickler kann das Problem nicht ignorieren. Verwenden Sie in Produktions-Builds penaltyLog, um Statistiken ohne Abstürze zu sammeln.
Aktivieren Sie unter iOS Main Thread Checker im Debug-Schema und konfigurieren Sie CI, um Tests mit dieser Option auszuführen. Wenn ein Test einen UIKit-Aufruf aus einem Hintergrundthread enthält — sollte er fehlschlagen. Dies ist der einzige zuverlässige Weg, das Problem vor dem Senden an TestFlight zu identifizieren.
Fügen Sie dem Code-Review-Prozess einen obligatorischen Punkt hinzu: Überprüfen Sie, ob jeder Netzwerkaufruf, Dateivorgang, Datenbankzugriff oder schwere Berechnung in einem Hintergrundthread ausgeführt wird. Deadlock kann mit statischen Analysewerkzeugen erkannt werden: Infer von Facebook und Thread Safety Checker von Xcode finden potenzielle Sperren vor der Laufzeit.
Häufig gestellte Fragen
ANR (Application Not Responding) ist eine Android-Systembenachrichtigung, die erscheint, wenn der Hauptthread länger als 5 Sekunden eingefroren ist. Einfrieren ist ein breiterer Begriff: jede UI-Blockierung beliebiger Dauer. iOS hat kein ANR, aber einen Watchdog mit einem Timeout von 10–20 Sekunden.
Die Datei befindet sich unter /data/anr/traces.txt. Der Zugriff erfordert Root-Zugriff oder adb shell: Führen Sie adb shell cat /data/anr/traces.txt \> traces.txt mit Root-Rechten aus. Suchen Sie im Stack den Thread „main“ — die zuletzt aufgerufene Methode gibt die Ursache der Blockierung an.
Wenn das Einfrieren weniger als 10 Sekunden dauert, löst der Watchdog nicht aus, und die App „hängt“ einfach, bis die blockierende Operation abgeschlossen ist. Der Benutzer sieht keinen Absturz, erlebt aber Frustration. Verwenden Sie MetricKit mit benutzerdefinierten Ausführungszeit-Traces, um solche Fälle zu erkennen.
Verwenden Sie UI-Tests mit der Überprüfung, dass der Bildschirm in < 1 Sekunde geöffnet wird. Fügen Sie die Zeitmessung zwischen Tippen und Erscheinen des nächsten Bildschirms zum CI hinzu. Verwenden Sie unter Android Espresso mit IdlingResource, um auf asynchrone Operationen zu warten. Verwenden Sie unter iOS XCTest mit XCTWaiter, um die Ladezeit zu überprüfen.
SwiftUI selbst verursacht keine Einfrierungen, aber komplexe Berechnungen in der body-Eigenschaft schon. Wenn die Berechnung von body aufgrund schwerer Operationen 500 ms dauert, friert die UI ein. Die Lösung besteht darin, Berechnungen in Task.detached auszulagern und @State asynchron auf dem Hauptakteur zu aktualisieren.
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