Android Coden
Android 8 min lesen

Network Timeouts in Android

Timeouts verhindern hängende Requests. Du lernst, sie für stabile Android-Apps sinnvoll zu setzen.

Network Timeouts legen fest, wie lange deine Android-App bei einem Netzwerkzugriff warten darf, bevor sie den Versuch kontrolliert abbricht. Das klingt unspektakulär, ist aber wichtig für echte Apps: Ohne klare Grenzen kann ein Request hängen bleiben, deine UI unnötig lange im Ladezustand halten und deine Fehlerbehandlung aushebeln.

Was ist das?

Ein Network Timeout ist eine Zeitgrenze für eine bestimmte Phase eines Netzwerkaufrufs. Wenn diese Grenze erreicht wird, gilt der Vorgang als fehlgeschlagen. Deine App bekommt dann einen Fehler, den sie behandeln kann: etwa durch eine Meldung, einen Retry, einen lokalen Cache oder einen späteren Sync.

Das zentrale Problem ist nicht, dass Netzwerke langsam sein können. Das weißt du schon, sobald du Apps im Zug, im Keller oder in einem überlasteten WLAN testest. Das eigentliche Problem ist Unbestimmtheit. Wenn dein Code nicht festlegt, wann genug gewartet wurde, entsteht ein Zustand, der für Nutzer schwer verständlich und für dich schwer testbar ist. Ein Spinner dreht sich weiter, ein Button bleibt deaktiviert, ein ViewModel wartet auf ein Ergebnis, das vielleicht nie kommt.

Im Android-Kontext gehört dieses Thema in die Data Layer. Dort kapselst du normalerweise deine Repositories, Remote-Datenquellen, lokale Datenbanken und Synchronisationslogik. Die UI, egal ob Compose oder klassische Views, sollte nicht selbst entscheiden müssen, ob ein Server zu lange braucht. Sie sollte einen klaren Zustand bekommen: lädt, Daten vorhanden, Fehler, offline, Retry möglich. Timeouts helfen dir, diese Zustände zuverlässig zu erzeugen.

Du solltest Timeouts nicht als kosmetische Einstellung sehen. Sie sind ein Teil der Resilience deiner App. Resilience bedeutet hier: Die App bleibt bedienbar und nachvollziehbar, auch wenn externe Systeme langsam, instabil oder nicht erreichbar sind. Das passt direkt zu modernen Android-Architekturen, in denen Repositories Datenströme liefern, ViewModels UI-State vorbereiten und Compose diesen State darstellt. Wenn ein Request ewig blockiert, leidet diese ganze Kette.

Wichtig ist auch die Abgrenzung: Timeouts lösen nicht alle Netzwerkprobleme. Sie ersetzen keine gute Fehlerbehandlung, keinen lokalen Cache und kein Offline-First-Design. Sie sorgen aber dafür, dass ein Fehler überhaupt rechtzeitig sichtbar wird. Erst dann kann deine App sinnvoll reagieren.

Wie funktioniert es?

Bei HTTP-Clients wie OkHttp, die häufig zusammen mit Retrofit verwendet werden, unterscheidest du typischerweise mehrere Timeout-Arten. Die wichtigsten Begriffe sind connect, read und write.

Das Connect-Timeout begrenzt die Zeit, die dein Client für den Aufbau der Verbindung bekommt. Dazu gehören je nach Situation DNS-Auflösung, TCP-Verbindung und TLS-Handshake. Wenn der Server nicht erreichbar ist oder die Verbindung sehr langsam zustande kommt, greift diese Grenze. Ein zu langes Connect-Timeout führt dazu, dass die App bei schlechter Verbindung lange wartet, bevor überhaupt Daten fließen. Ein zu kurzes Connect-Timeout kann dagegen in mobilen Netzen unnötig viele Fehlversuche erzeugen.

Das Read-Timeout begrenzt, wie lange der Client beim Lesen der Antwort warten darf. Das ist relevant, wenn die Verbindung steht, der Server aber nicht rechtzeitig Daten liefert oder die Datenübertragung stockt. Dieses Timeout schützt dich vor Requests, bei denen der Server zwar erreichbar ist, aber keine brauchbare Antwort sendet.

Das Write-Timeout begrenzt die Zeit für das Senden von Request-Daten. Es spielt besonders bei Uploads, großen JSON-Bodies, Log-Dateien, Bildern oder Formularen mit vielen Daten eine Rolle. Für reine GET-Requests fällt es weniger auf, ist aber trotzdem Teil der Gesamtstrategie.

Ein Anfänger sollte sich folgendes Modell merken: Ein Netzwerkaufruf ist kein einzelner Moment, sondern eine kleine Reise mit Phasen. Erst wird verbunden, dann wird gesendet, dann wird gelesen. Jede Phase kann hängen. Darum gibt es nicht nur eine allgemeine Wartezeit, sondern mehrere Grenzen für unterschiedliche Risiken.

In der Architektur deiner App sollte diese Konfiguration nah am HTTP-Client liegen, nicht verstreut in UI-Code. Wenn du Retrofit nutzt, baust du meist einen OkHttpClient mit festen Timeout-Werten und gibst ihn an Retrofit weiter. Alle Services, die über diesen Client laufen, bekommen damit konsistente Regeln. Das ist leichter zu testen und leichter im Code-Review zu prüfen.

Timeouts sollten außerdem zur fachlichen Erwartung passen. Eine Suchanfrage sollte schnell scheitern, weil Nutzer unmittelbares Feedback erwarten. Ein geplanter Hintergrund-Sync darf oft etwas länger warten, weil er nicht direkt an eine sichtbare Interaktion gebunden ist. Ein Bild-Upload braucht eventuell andere Werte als ein kleiner Status-Request. Trotzdem solltest du nicht für jeden Endpunkt beliebige Sonderregeln einführen. Zu viele Ausnahmen machen das System schwer verständlich.

Im Zusammenspiel mit Coroutines ist noch eine zweite Ebene wichtig. HTTP-Timeouts begrenzen Netzwerkphasen auf Client-Ebene. Coroutine-Timeouts wie withTimeout begrenzen dagegen eine gesamte suspend-Funktion oder einen größeren Ablauf. Beide Werkzeuge sind nicht identisch. Wenn du nur withTimeout verwendest, weiß der HTTP-Client selbst noch nichts über connect, read oder write. Wenn du nur OkHttp-Timeouts setzt, kann ein größerer Use Case mit mehreren Schritten trotzdem zu lange dauern. In einfachen Apps reicht oft eine saubere Client-Konfiguration. In komplexeren Abläufen kann zusätzlich ein fachliches Timeout sinnvoll sein.

Für Offline-First-Apps sind Timeouts besonders wichtig, weil Remote-Zugriffe dort nicht die einzige Quelle der Wahrheit sein sollten. Eine Repository-Methode kann zuerst lokale Daten liefern und danach versuchen, frische Daten zu synchronisieren. Wenn der Remote-Versuch zu lange dauert, soll die App nicht blockieren. Sie kann lokale Daten weiter anzeigen, den Sync als fehlgeschlagen markieren und später erneut versuchen. So entsteht ein Verhalten, das Nutzer als stabil wahrnehmen.

In der Praxis

Eine typische Konfiguration liegt in einem Netzwerkmodul, etwa mit Dependency Injection über Hilt. Das folgende Beispiel zeigt bewusst nur den Kern: ein OkHttpClient mit getrennten Timeouts und ein Retrofit-Builder, der diesen Client nutzt.

import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.time.Duration

object NetworkModule {

    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .connectTimeout(Duration.ofSeconds(10))
            .readTimeout(Duration.ofSeconds(20))
            .writeTimeout(Duration.ofSeconds(20))
            .callTimeout(Duration.ofSeconds(30))
            .build()
    }

    fun provideRetrofit(client: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .client(client)
            .addConverterFactory(MoshiConverterFactory.create())
            .build()
    }
}

Die Werte sind keine allgemeingültige Wahrheit. Sie zeigen eine sinnvolle Richtung: Der Verbindungsaufbau bekommt weniger Zeit als das Lesen oder Schreiben, und ein zusätzliches Call-Timeout begrenzt den gesamten Aufruf. Für viele Apps ist das ein guter Startpunkt, den du mit echten Messungen anpassen solltest. Entscheidend ist, dass es überhaupt definierte Grenzen gibt.

Im Repository behandelst du Timeout-Fehler nicht als Sonderfall für die UI, sondern als Teil deines Datenzustands. Du kannst zum Beispiel ein Result-Modell verwenden, das zwischen Erfolg, Netzwerkfehler und unbekanntem Fehler unterscheidet. Die konkrete Exception hängt von Client und Situation ab; bei OkHttp und Java-Netzwerkklassen siehst du häufig SocketTimeoutException oder InterruptedIOException.

import java.io.InterruptedIOException
import java.net.SocketTimeoutException

class ArticleRepository(
    private val api: ArticleApi,
    private val dao: ArticleDao
) {
    suspend fun refreshArticles(): RefreshResult {
        return try {
            val remoteArticles = api.getArticles()
            dao.replaceAll(remoteArticles.map { it.toEntity() })
            RefreshResult.Success
        } catch (error: SocketTimeoutException) {
            RefreshResult.TemporaryNetworkProblem
        } catch (error: InterruptedIOException) {
            RefreshResult.TemporaryNetworkProblem
        } catch (error: Exception) {
            RefreshResult.Failed(error)
        }
    }
}

sealed interface RefreshResult {
    data object Success : RefreshResult
    data object TemporaryNetworkProblem : RefreshResult
    data class Failed(val cause: Throwable) : RefreshResult
}

In einer Compose-App würde dein ViewModel daraus einen UI-State ableiten. Die Oberfläche muss nicht wissen, ob connect, read oder write das Problem war. Für Nutzer ist meistens relevant: Die Aktualisierung hat nicht rechtzeitig funktioniert, vorhandene Daten bleiben sichtbar, und ein erneuter Versuch ist möglich. Für Logging und Debugging kannst du intern genauer unterscheiden.

Eine brauchbare Entscheidungsregel lautet: Setze Timeouts so kurz, dass Nutzer nicht unnötig warten, aber so lang, dass normale mobile Netze nicht ständig falsche Fehler produzieren. Das klingt nach Balance, und genau das ist es. Du findest diese Balance nicht durch Raten im Büro-WLAN. Teste mit schlechtem Netz, aktiviere im Emulator gedrosselte Verbindung, verwende echte Geräte und beobachte Logs. Prüfe auch, wie sich deine App verhält, wenn der Server erreichbar ist, aber langsam antwortet.

Eine häufige Stolperfalle ist ein Timeout, das nur technisch existiert, aber fachlich nicht behandelt wird. Dann bricht der Request zwar ab, aber die UI zeigt eine generische Fehlermeldung oder verliert vorhandene Daten. Besser ist: Die Data Layer liefert weiterhin Cache-Daten, markiert den Remote-Versuch als fehlgeschlagen und erlaubt einen Retry. Gerade bei Offline-First ist das der Unterschied zwischen einer fragilen und einer robusten App.

Eine zweite Stolperfalle sind extrem hohe Timeout-Werte, weil man Fehlermeldungen vermeiden möchte. Das verschiebt das Problem nur. Nutzer warten dann länger, Tests laufen langsamer, und Fehler werden schwerer reproduzierbar. Besonders kritisch ist das bei Workflows wie Login, Checkout, Formularversand oder Synchronisation beim App-Start. Dort muss klar sein, wann die App aufgibt und welchen nächsten Zustand sie einnimmt.

Eine dritte Stolperfalle ist die Vermischung von Timeout und Retry. Ein Retry kann sinnvoll sein, aber er braucht Grenzen. Drei Wiederholungen mit jeweils 30 Sekunden können aus einem kurzen Problem eine sehr lange Wartezeit machen. Für Hintergrundarbeit mit WorkManager ist ein späterer erneuter Versuch oft besser als mehrere aggressive Direkt-Retries im Vordergrund. Im Vordergrund solltest du Nutzer nicht blockieren, nur weil dein Code hofft, dass der nächste Versuch doch noch klappt.

Im Code-Review kannst du gezielt nach vier Fragen suchen. Gibt es eine zentrale Timeout-Konfiguration? Sind connect, read und write bewusst gewählt? Wird ein Timeout in der Data Layer in einen sinnvollen Zustand übersetzt? Bleiben lokale Daten sichtbar, wenn der Remote-Zugriff scheitert? Wenn du diese Fragen beantworten kannst, hast du das Thema nicht nur konfiguriert, sondern in die Architektur eingebettet.

Auch Tests sind möglich. Du kannst mit einem MockWebServer verzögerte Antworten simulieren und prüfen, ob dein Repository den erwarteten Fehlerzustand liefert. Für UI-Tests musst du nicht echte Sekunden warten; dort injizierst du besser ein Fake-Repository, das denselben Zustand liefert. So testest du die Reaktion der UI getrennt von der Netzwerkmechanik.

Fazit

Network Timeouts geben deiner Android-App klare Grenzen für Verbindungsaufbau, Senden und Lesen von Netzwerkdaten. Sie machen aus unbestimmtem Warten einen behandelbaren Fehler und passen deshalb direkt in eine saubere Data Layer mit Repository, lokalem Cache und UI-State. Prüfe das Gelernte aktiv: Konfiguriere Timeouts zentral, simuliere langsame Antworten, beobachte die Exceptions im Debugger und kontrolliere im Code-Review, ob deine App bei einem Timeout weiter bedienbar bleibt.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.