Retry Strategy in Android
Retry Strategy hilft dir, fehlgeschlagene Netzwerkaktionen gezielt zu wiederholen, ohne Akku, Daten oder Server unnötig zu belasten.
Eine Retry Strategy beschreibt, wann und wie deine App eine fehlgeschlagene Operation erneut versucht. Im Android-Alltag betrifft das vor allem Netzwerkzugriffe, Synchronisation und Offline-First-Flows. Wichtig ist dabei nicht, möglichst oft zu wiederholen, sondern nur dann, wenn ein erneuter Versuch fachlich sicher ist und technisch sinnvoll bleibt.
Was ist das?
Eine Retry Strategy ist eine Regel für Wiederholungen nach Fehlern. Statt bei jedem Fehler sofort erneut dieselbe Anfrage abzuschicken, prüfst du: War der Fehler wahrscheinlich nur vorübergehend? Ist die Operation sicher wiederholbar? Gibt es eine Grenze, nach der die App aufhören muss?
Das zentrale Stichwort ist transient failure. Damit meinst du einen Fehler, der nicht dauerhaft sein muss: kurz kein Netz, ein Timeout, ein Server antwortet mit Überlastung oder eine Verbindung bricht ab. Bei solchen Fällen kann ein späterer Versuch erfolgreich sein. Anders ist es bei dauerhaften oder fachlichen Fehlern, etwa ungültigen Zugangsdaten, einer fehlerhaften Anfrage oder fehlenden Berechtigungen. Diese Fehler werden durch Wiederholen nicht besser.
Im modernen Android-Kontext gehört diese Entscheidung meist in die Daten-Schicht, also in Repository, DataSource oder Synchronisationslogik. Compose zeigt nur den Zustand an: lädt, fehlgeschlagen, wird später synchronisiert oder abgeschlossen. Die UI sollte nicht selbst in einer Schleife Netzwerkaufrufe wiederholen. Dadurch bleibt deine Architektur klarer, besser testbar und robuster.
Eine gute Retry Strategy schützt außerdem Ressourcen. Android-Geräte laufen mobil, oft mit schlechtem Netz und begrenztem Akku. Wenn deine App bei jedem Fehler zehnmal sofort nachfragt, belastest du Gerät, Datenvolumen und Server. Genau deshalb ist Retry kein reiner Komfortmechanismus, sondern Teil von Qualität und Performance.
Wie funktioniert es?
Das mentale Modell ist einfach: Nicht jeder Fehler verdient einen Retry, und nicht jeder Retry darf sofort passieren. Du brauchst drei Entscheidungen.
Erstens: Welche Fehler sind wiederholbar? Typische Kandidaten sind Timeouts, Verbindungsabbrüche und bestimmte HTTP-Statuscodes wie 503 für temporäre Nichtverfügbarkeit. Nicht geeignet sind zum Beispiel 400, 401 oder 403, weil dort meist Eingaben, Authentifizierung oder Rechte falsch sind.
Zweitens: Ist die Operation idempotent? Idempotency bedeutet, dass eine Operation bei mehrfacher Ausführung denselben fachlichen Effekt hat wie bei einmaliger Ausführung. Ein GET für eine Profilansicht ist meistens sicher. Auch ein PUT mit fester Ressourcen-ID kann idempotent sein, wenn derselbe Zustand erneut geschrieben wird. Kritischer ist ein POST, der eine Bestellung, Zahlung oder Nachricht erzeugt. Wenn du diesen Request automatisch wiederholst, kann dieselbe Aktion doppelt entstehen, falls der erste Versuch serverseitig doch verarbeitet wurde, aber die Antwort verloren ging.
Drittens: Wie wird gewartet? Backoff bedeutet, dass die App zwischen Wiederholungen zunehmende Pausen einlegt. Statt 1, 1, 1 Sekunden zu warten, nutzt du zum Beispiel 1, 2, 4, 8 Sekunden. Oft wird zusätzlich Jitter verwendet, also eine kleine zufällige Abweichung. Das verhindert, dass sehr viele Geräte exakt gleichzeitig erneut anfragen, wenn ein Dienst gerade Probleme hat.
In Android-Projekten siehst du Retry Strategy häufig an drei Stellen: in Retrofit- oder Ktor-Aufrufen, in Flows aus der Daten-Schicht und in Hintergrundjobs mit WorkManager. Für einfache, aktive Requests kann eine Coroutine mit begrenzter Retry-Logik reichen. Für zuverlässige Synchronisation, die auch nach App-Schließen weiterlaufen soll, ist WorkManager oft passender, weil Android damit Akkustand, Netzbedingungen und Systemplanung berücksichtigen kann.
Wichtig ist die Grenze. Jede Retry Strategy braucht ein Maximum: maximale Anzahl an Versuchen, maximale Wartezeit oder einen Abbruch durch Cancellation. Ohne Grenze baust du unkontrollierbare Schleifen, die schwer zu debuggen sind und unter schlechten Netzbedingungen besonders teuer werden.
In der Praxis
Stell dir vor, deine App lädt eine Artikelliste aus einem Repository. Ein Timeout darf wiederholt werden, aber eine fehlerhafte Anfrage nicht. Die UI beobachtet nur den Zustand und bietet optional einen manuellen erneuten Versuch an. Die automatische Retry-Entscheidung liegt im Repository.
class ArticleRepository(
private val api: ArticleApi
) {
suspend fun loadArticles(): List<Article> {
return retryWithBackoff(
maxAttempts = 3,
initialDelayMillis = 1_000
) {
api.getArticles()
}
}
}
suspend fun <T> retryWithBackoff(
maxAttempts: Int,
initialDelayMillis: Long,
block: suspend () -> T
): T {
var currentDelay = initialDelayMillis
var lastError: Throwable? = null
repeat(maxAttempts) { attempt ->
try {
return block()
} catch (error: IOException) {
lastError = error
if (attempt == maxAttempts - 1) {
throw error
}
delay(currentDelay)
currentDelay *= 2
}
}
throw lastError ?: IllegalStateException("Retry failed without error")
}
Dieses Beispiel wiederholt nur IOException, also typische Transportprobleme. Es wiederholt nicht pauschal jeden Fehler. Genau das ist der Kern: Deine Retry Strategy sollte konkret sein. Wenn deine API zusätzlich HTTP-Fehler als Exceptions meldet, würdest du nur ausgewählte Statuscodes zulassen, etwa 503 oder 504. Bei 401 würdest du eher einen Login- oder Token-Refresh-Flow starten. Bei 400 würdest du den Request korrigieren.
Für Compose könnte die Oberfläche dann einen Zustand anzeigen:
@Composable
fun ArticleScreen(
state: ArticleUiState,
onRetryClick: () -> Unit
) {
when (state) {
ArticleUiState.Loading -> CircularProgressIndicator()
is ArticleUiState.Content -> ArticleList(state.articles)
is ArticleUiState.Error -> Button(onClick = onRetryClick) {
Text("Erneut versuchen")
}
}
}
Der Button ist ein manueller Retry. Das ist etwas anderes als eine automatische Wiederholung. Manuelle Wiederholungen sind bei sichtbaren Fehlern sinnvoll, weil der Nutzer bewusst entscheidet. Automatische Wiederholungen sind eher für kurze, vorübergehende Störungen oder Hintergrundsync geeignet.
Eine wichtige Entscheidungsregel lautet: Wiederhole automatisch nur Operationen, die entweder lesend sind oder durch Idempotency abgesichert wurden. Wenn du Schreiboperationen wiederholst, brauchst du eine fachliche Absicherung. Das kann eine stabile Client-ID sein, ein Idempotency-Key oder eine Serverlogik, die doppelte Requests erkennt. Ohne solche Absicherung kann ein verlorenes Antwortpaket dazu führen, dass die App dieselbe Bestellung oder Nachricht mehrfach erzeugt.
Eine typische Stolperfalle ist der naive Retry direkt im ViewModel oder sogar in der Composable. Dann koppelt sich UI-Lebenszyklus mit Netzwerkverhalten: Recomposition, Navigation oder erneutes Sammeln eines Flows können plötzlich neue Requests auslösen. Besser ist eine klare Daten-Schicht, die entscheidet, ob automatisch wiederholt wird, und eine UI, die nur beobachtet und Nutzeraktionen weiterleitet.
Eine zweite Stolperfalle ist zu aggressiver Backoff. Wenn du zehn Versuche mit kurzen Pausen machst, sieht das im WLAN-Test harmlos aus. Im Zug, im Funkloch oder bei einem gestörten Backend belastet es aber genau die Systeme, die ohnehin schon instabil sind. Begrenze daher Versuche und logge klar, warum ein Retry passiert ist. In Tests kannst du künstliche IOExceptions werfen und prüfen, ob die Anzahl der Aufrufe, die Wartefolge und der Abbruch korrekt sind.
Fazit
Eine Retry Strategy macht deine Android-App widerstandsfähiger, aber nur, wenn du sie gezielt einsetzt: transient failures erkennen, Backoff verwenden, Idempotency beachten und klare Grenzen setzen. Prüfe dein Verständnis an einem echten Repository: Simuliere Timeouts, beobachte die Logs, schreibe einen Test für die maximale Anzahl an Versuchen und bespreche im Code-Review besonders Schreiboperationen. Wenn du erklären kannst, warum ein Request wiederholt wird und warum ein anderer nicht, hast du den wichtigsten Teil verstanden.