Android Coden
Android 11 min lesen

HTTP-Grundlagen für Android

HTTP entscheidet, wie deine App Web-APIs korrekt anspricht. Du lernst Methoden, Header und Statuscodes einzuordnen.

Wenn deine Android-App Daten aus dem Netz lädt, spricht sie fast immer HTTP. Du musst dafür nicht jedes Detail des Protokolls auswendig kennen, aber du brauchst ein stabiles Modell: Eine Anfrage hat eine Absicht, Metadaten und einen Körper; eine Antwort hat Metadaten, einen Körper und einen Status. Wer Methoden, Header und Statuscodes sauber versteht, schreibt robustere Repositorys, bessere Fehlermeldungen und weniger überraschende Apps.

Was ist das?

HTTP ist ein Protokoll, mit dem ein Client und ein Server strukturierte Nachrichten austauschen. In deinem Fall ist der Client meist deine Android-App, genauer gesagt eine Netzwerkbibliothek wie Retrofit, Ktor oder OkHttp. Der Server stellt eine Web-API bereit, zum Beispiel für Login, Produktlisten, Nutzerprofile oder Synchronisation. HTTP legt nicht fest, wie deine App intern aufgebaut ist. Es legt fest, wie Anfrage und Antwort über die Leitung aussehen.

Für Android-Entwicklung ist das wichtig, weil Netzwerkcode selten isoliert bleibt. Ein Button in Jetpack Compose löst vielleicht eine Aktion aus, ein ViewModel startet eine Coroutine, ein Repository ruft eine API auf, und die Daten landen im Cache oder in der UI. Wenn du HTTP nur als „URL aufrufen und JSON bekommen“ verstehst, fehlen dir wichtige Signale. Du erkennst dann zum Beispiel nicht, ob ein Fehler am Login liegt, ob der Server gerade nicht erreichbar ist, ob du den falschen Request gesendet hast oder ob die Daten aus einem Cache kommen sollten.

Die drei Grundbegriffe sind Methoden, Header und Statuscodes. Methoden beschreiben die Absicht einer Anfrage. Header beschreiben zusätzliche Informationen zur Anfrage oder Antwort. Statuscodes beschreiben das Ergebnis aus Sicht des Servers. Zusammen ergeben sie die Sprache, in der deine App mit einer API verhandelt.

Ein gutes Einsteiger-Modell lautet: HTTP ist wie ein streng formatierter Auftrag. Du sagst mit der Methode, was du tun willst. Du legst mit Headern Kontext bei. Du sendest optional einen Body mit Nutzdaten. Der Server antwortet mit einem Statuscode, Headern und optional einem Body. Dieses Modell hilft dir später bei Architekturfragen, Offline-first-Konzepten, Tests und Release-Qualität, ohne dass du dich in Spezialfällen verlierst.

In der Android-Architektur gehört HTTP typischerweise in die Data Layer. Die UI sollte nicht wissen müssen, ob ein Profil per GET, aus Room oder aus einem Memory-Cache kommt. Sie sollte einen Zustand sehen: lädt, Daten vorhanden, leer, Fehler, erneuter Versuch möglich. Das Repository übersetzt HTTP-Antworten in fachliche Ergebnisse. Genau dort entscheidet sich, ob deine App stabil wirkt oder ob jeder kleine Netzwerkfehler direkt als kryptische Meldung in der Oberfläche landet.

Wie funktioniert es?

Eine HTTP-Anfrage beginnt mit einer Methode. Die häufigsten Methoden sind GET, POST, PUT, PATCH und DELETE. GET wird zum Lesen verwendet. Ein Produktkatalog, eine Liste von Nachrichten oder ein Nutzerprofil werden häufig per GET geladen. POST sendet neue Daten oder löst eine Aktion aus, zum Beispiel Login, Registrierung oder das Erstellen eines Kommentars. PUT ersetzt eine Ressource oft vollständig. PATCH ändert meist einzelne Felder. DELETE fordert das Löschen einer Ressource an.

Diese Methoden sind mehr als Namen. Sie transportieren Erwartungen. Ein GET sollte keine Daten verändern. Wenn deine App durch bloßes Laden einer Detailseite eine Bestellung auslöst, ist die API schlecht geschnitten oder falsch verwendet. Für Android ist diese Erwartung praktisch: Ein Repository kann Lesezugriffe anders behandeln als Schreibzugriffe. Lesezugriffe lassen sich eher cachen, wiederholen oder für Offline-first Strategien planen. Schreibzugriffe brauchen meist genauere Fehlerbehandlung, weil der Nutzer wissen muss, ob eine Änderung gespeichert wurde.

Header sind Schlüssel-Wert-Paare mit Metadaten. Typische Request-Header sind Authorization, Accept, Content-Type und Accept-Language. Authorization enthält zum Beispiel ein Bearer-Token. Accept sagt, welches Antwortformat deine App erwartet, etwa application/json. Content-Type beschreibt das Format des Request-Bodys, ebenfalls oft application/json. Accept-Language kann dem Server helfen, lokalisierte Inhalte zu liefern.

Response-Header sind genauso wichtig. Sie können Cache-Regeln, Content-Typen, Rate-Limit-Informationen oder Serverhinweise enthalten. In vielen Projekten beachtest du sie anfangs kaum, aber bei realen Apps werden sie schnell relevant. Wenn eine API zum Beispiel Cache-Control sinnvoll setzt, kann dein Netzwerkclient oder deine Architektur davon profitieren. Wenn ein Server einen Retry-After-Header sendet, solltest du nicht sofort denselben Request in einer Schleife wiederholen.

Statuscodes sind dreistellige Zahlen. Du musst nicht alle kennen, aber du solltest die Klassen verstehen. 2xx bedeutet Erfolg. 200 OK ist der bekannte Standardfall, 201 Created passt zu erfolgreich erstellten Ressourcen, und 204 No Content bedeutet Erfolg ohne Antwortkörper. 3xx steht für Weiterleitungen. Diese werden von Netzwerkbibliotheken oft automatisch behandelt, können aber bei Authentifizierung oder falscher Basis-URL sichtbar werden.

4xx bedeutet: Die Anfrage ist aus Sicht des Servers problematisch. 400 Bad Request weist oft auf ungültige Daten hin. 401 Unauthorized bedeutet, dass Authentifizierung fehlt oder ungültig ist. 403 Forbidden bedeutet, dass der Client zwar erkannt wurde, aber keine Berechtigung hat. 404 Not Found heißt, dass die Ressource nicht gefunden wurde. 409 Conflict ist bei Synchronisation und parallelen Änderungen wichtig. 429 Too Many Requests zeigt, dass du zu viele Anfragen sendest oder ein Limit erreichst.

5xx bedeutet: Der Server konnte eine grundsätzlich verständliche Anfrage nicht korrekt verarbeiten. 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable und 504 Gateway Timeout gehören in diese Gruppe. Für deine App ist die Unterscheidung wichtig: Bei 4xx hilft oft eine Korrektur durch Nutzer, App oder Token-Handling. Bei 5xx hilft meistens ein späterer Versuch, ein Fallback oder ein sauberer Offline-Zustand.

Ein weiterer zentraler Begriff ist der Body. Bei POST, PUT und PATCH sendest du oft JSON im Request-Body. Bei GET liegt die Information meist in Pfadparametern oder Query-Parametern. Eine Suche könnte also GET /articles?tag=compose sein. Ein neuer Kommentar könnte POST /comments mit JSON-Body sein. Verwechsle Query-Parameter nicht mit Headern. Query-Parameter gehören zur Ressource oder Abfrage. Header beschreiben Kontext zur Übertragung oder Verarbeitung.

Im Alltag sieht HTTP in Android selten als Rohtext aus. Du definierst Interfaces, Datenklassen und Mapper. Trotzdem bleibt das Protokoll darunter wirksam. Retrofit macht aus einer Annotation wie @GET("users/{id}") eine HTTP-Anfrage. OkHttp hängt Header an, verarbeitet Redirects und kann Interceptors nutzen. Coroutines sorgen dafür, dass der Aufruf nicht den Main Thread blockiert. Dein Repository entscheidet, ob eine Antwort in ein Domain-Modell, einen Fehlerzustand oder eine Cache-Aktion übersetzt wird.

Gerade im Umfeld Data Layer und Offline-first brauchst du eine klare Trennung. HTTP ist die Kommunikation mit der Remote-Datenquelle. Die fachliche Entscheidung, was die App bei Erfolg, Fehler oder fehlendem Netz tut, gehört nicht in die Composable-Funktion. Eine Composable sollte keinen Statuscode interpretieren. Ein ViewModel sollte höchstens einen fachlichen Zustand erhalten. Das Repository oder ein Use Case ist der richtige Ort, um 401 in „Sitzung abgelaufen“, 404 in „Eintrag nicht gefunden“ oder 503 in „später erneut versuchen“ zu übersetzen.

In der Praxis

Stell dir eine App vor, die Artikel aus einer API lädt und neue Favoriten speichert. Du arbeitest mit Retrofit, Kotlin Coroutines und einem Repository. Ein kleines Interface könnte so aussehen:

interface ArticleApi {
    @GET("articles")
    suspend fun getArticles(
        @Header("Authorization") token: String,
        @Query("tag") tag: String? = null
    ): Response<List<ArticleDto>>

    @POST("favorites")
    suspend fun addFavorite(
        @Header("Authorization") token: String,
        @Body request: FavoriteRequest
    ): Response<FavoriteDto>
}

data class FavoriteRequest(
    val articleId: String
)

data class ArticleDto(
    val id: String,
    val title: String,
    val summary: String
)

data class FavoriteDto(
    val articleId: String,
    val createdAt: String
)

Der wichtige Punkt ist nicht Retrofit selbst, sondern die Abbildung der HTTP-Idee. getArticles nutzt GET, weil Daten gelesen werden. Das Tag ist ein Query-Parameter, weil es die Abfrage einschränkt. Der Token wird als Header gesendet, weil er Kontext zur Authentifizierung liefert. addFavorite nutzt POST, weil eine neue serverseitige Zuordnung entsteht. Die Nutzdaten stehen im Body.

Im Repository solltest du die Antwort nicht ungeprüft weiterreichen. Ein häufiger Anfängerfehler ist, bei jedem Request direkt response.body()!! zu verwenden. Das funktioniert im Erfolgsfall, bricht aber bei 204, 401, 404 oder einem leeren Body schnell. Besser ist eine kleine Übersetzung in fachliche Ergebnisse:

sealed interface NetworkResult<out T> {
    data class Success<T>(val value: T) : NetworkResult<T>
    data class ClientError(val code: Int, val message: String?) : NetworkResult<Nothing>
    data class ServerError(val code: Int) : NetworkResult<Nothing>
    data object Unauthorized : NetworkResult<Nothing>
    data object NetworkUnavailable : NetworkResult<Nothing>
}

class ArticleRepository(
    private val api: ArticleApi,
    private val tokenProvider: TokenProvider
) {
    suspend fun loadArticles(tag: String?): NetworkResult<List<ArticleDto>> {
        return try {
            val response = api.getArticles(
                token = "Bearer ${tokenProvider.currentToken()}",
                tag = tag
            )

            when {
                response.isSuccessful -> {
                    val body = response.body().orEmpty()
                    NetworkResult.Success(body)
                }
                response.code() == 401 -> NetworkResult.Unauthorized
                response.code() in 400..499 -> {
                    NetworkResult.ClientError(response.code(), response.message())
                }
                response.code() in 500..599 -> {
                    NetworkResult.ServerError(response.code())
                }
                else -> {
                    NetworkResult.ClientError(response.code(), response.message())
                }
            }
        } catch (exception: IOException) {
            NetworkResult.NetworkUnavailable
        }
    }
}

Dieses Beispiel ist bewusst kompakt. In einer echten App würdest du DTOs in Domain-Modelle mappen, Fehlertexte vorsichtig behandeln und vielleicht eine eigene Fehlerstruktur aus dem Error-Body lesen. Entscheidend ist die Regel: Interpretiere HTTP an einer kontrollierten Stelle und gib der restlichen App keine rohen Protokolldetails, wenn sie fachlich nicht gebraucht werden.

Entscheidungsregeln für Methoden

Nutze GET, wenn du Daten liest und der Aufruf keine Änderung auslösen soll. Nutze POST, wenn du etwas erstellst oder eine Aktion startest, deren Ergebnis nicht sauber als vollständiger Ersatz einer Ressource beschrieben werden kann. Nutze PUT, wenn du eine Ressource vollständig ersetzt. Nutze PATCH, wenn du einzelne Felder änderst. Nutze DELETE, wenn eine Ressource gelöscht werden soll.

Diese Regeln klingen trocken, schützen dich aber vor echten Problemen. Wenn du etwa einen „Als gelesen markieren“-Aufruf per GET modellierst, kann ein Cache, ein Prefetch oder ein erneuter Request unerwartet Daten verändern. Das ist besonders kritisch, wenn Compose durch State-Änderungen erneut lädt oder wenn dein Repository bei Verbindungsproblemen Requests wiederholt. Eine Methode sollte zur Absicht passen, damit Infrastruktur und andere Entwickler dein Verhalten korrekt einschätzen können.

Header bewusst behandeln

Header sollten nicht verstreut in UI-Code entstehen. Ein Authentifizierungs-Header gehört meist in einen OkHttp-Interceptor oder in eine zentrale Datenquelle. So vermeidest du, dass manche Endpunkte versehentlich ohne Token aufgerufen werden. Gleichzeitig musst du vorsichtig bleiben: Nicht jeder Request braucht denselben Header. Ein Login-Request hat noch kein Bearer-Token. Ein öffentlicher Feed braucht vielleicht keine Authentifizierung. Eine Datei-Upload-API braucht eventuell einen anderen Content-Type als ein JSON-Endpunkt.

Eine typische Stolperfalle ist die Verwechslung von Accept und Content-Type. Accept sagt: „Dieses Antwortformat kann meine App verarbeiten.“ Content-Type sagt: „Dieses Format hat mein gesendeter Body.“ Bei einem GET ohne Body ist Content-Type oft nicht relevant. Bei einem POST mit JSON-Body ist er wichtig. Viele Bibliotheken setzen ihn automatisch, wenn du passende Converter nutzt. Trotzdem solltest du im Debugger oder mit einem Logging-Interceptor prüfen können, was tatsächlich gesendet wird.

Statuscodes in UI-Zustände übersetzen

Für die Nutzeroberfläche zählt nicht der rohe Code, sondern die Bedeutung. 401 kann bedeuten, dass du zur Anmeldung zurückführen musst. 403 kann bedeuten, dass eine Funktion für dieses Konto nicht verfügbar ist. 404 kann in einer Detailansicht eine leere oder entfernte Ressource bedeuten. 409 kann bei Offline-first Apps wichtig sein, wenn lokale Änderungen mit Serveränderungen kollidieren. 429 sollte nicht als normaler Fehler behandelt werden, sondern als Hinweis auf Begrenzung.

Bei 5xx solltest du meist keine Schuld beim Nutzer suchen. Eine gute App zeigt einen wiederholbaren Zustand, nutzt vorhandene lokale Daten oder stellt die Änderung später erneut zu. Genau hier verbindet sich HTTP mit Offline-first Architektur. Wenn ein GET fehlschlägt, kannst du eventuell gecachte Daten anzeigen. Wenn ein POST fehlschlägt, musst du entscheiden, ob du die Aktion lokal vormerkst, den Nutzer informierst oder einen erneuten Versuch anbietest. Diese Entscheidung ist fachlich, aber sie basiert auf dem HTTP-Ergebnis.

Testen und Debuggen

Du kannst dein Verständnis gut prüfen, indem du Netzwerkfälle explizit testest. Schreibe Unit-Tests für die Repository-Übersetzung: 200 mit Body ergibt Erfolg, 401 ergibt einen Auth-Zustand, 500 ergibt einen Serverfehler, eine IOException ergibt fehlendes Netzwerk. Mit MockWebServer kannst du Antworten mit bestimmten Statuscodes und Headern simulieren, ohne eine echte API zu brauchen. In Code-Reviews solltest du gezielt fragen: Passt die Methode zur Absicht? Werden Header zentral und nachvollziehbar gesetzt? Wird jeder relevante Statuscode sinnvoll behandelt?

Beim Debuggen hilft ein Logging-Interceptor in Debug-Builds. Er zeigt dir Methode, URL, Header und Statuscode. Sensible Werte wie Tokens dürfen dabei nicht unkontrolliert in Logs landen, besonders nicht in Release-Builds. Achte außerdem darauf, dass du nicht jede Fehlermeldung des Servers direkt in der UI anzeigst. Servertexte sind oft technisch, nicht lokalisiert oder für Entwickler gedacht. Übersetze sie in klare App-Zustände und verständliche Hinweise.

Ein weiteres Risiko liegt in zu pauschaler Fehlerbehandlung. „Alles außer 2xx ist Netzwerkfehler“ ist falsch. Ein 400 bei ungültigen Formulardaten ist etwas anderes als ein 503 bei Wartung. Ein 401 braucht andere Logik als ein Timeout. Wenn du diese Fälle zusammenwirfst, wird die App schwer zu warten. Du verlierst außerdem wichtige Signale für Telemetrie, Support und spätere Verbesserungen.

Fazit

HTTP-Grundlagen sind kein Nebenthema, sondern ein praktisches Werkzeug für jede Android-App mit Web-API. Du solltest Methoden als Absicht, Header als Kontext und Statuscodes als Ergebnis lesen können. Prüfe das aktiv: Öffne einen bestehenden API-Call, benenne Methode, Header und erwartete Statuscodes, schreibe einen kleinen Repository-Test für Erfolg und Fehler, und kontrolliere im Debug-Log, ob die echte Anfrage zu deiner Annahme passt. So baust du ein Netzwerkverständnis auf, das dir bei Compose-UI, Repository-Design, Offline-first Verhalten und Code-Reviews direkt hilft.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

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