Ich mag Retrofit2 als Android-Entwickler, aber was ist mit dem Versuch, die Qualität des Ktor-HTTP-Clients zu erhalten? Meiner Meinung nach ist es für die Android-Entwicklung nicht schlechter und nicht besser, nur eine der Optionen, obwohl es sehr gut ausgehen kann, wenn Sie alles ein wenig zusammenfassen. Ich werde die grundlegenden Funktionen betrachten, mit denen Ktor als HTTP-Client verwendet werden kann: Erstellen verschiedener Arten von Anforderungen, Empfangen von Rohantworten und Antworten in Form von Text, Deserialisieren von JSON in Klassen über Konverter und Protokollieren.

Im Allgemeinen ist Ktor ein Framework, das als HTTP-Client fungieren kann. Ich werde es von der Entwicklungsseite für Android betrachten. Es ist unwahrscheinlich, dass Sie im Folgenden sehr komplexe Anwendungsfälle sehen, aber die grundlegenden Funktionen sind sicher. Der Code aus den folgenden Beispielen kann auf
GitHub angezeigt werden.
Ktor verwendet die Coroutinen aus Kotlin 1.3, eine Liste der verfügbaren Artefakte finden Sie
hier , die aktuelle Version ist
1.0.1
.
Für Anfragen werde ich
HttpBin verwenden .
Einfache Verwendung
Für den Einstieg benötigen Sie grundlegende Abhängigkeiten für den Android-Client:
implementation "io.ktor:ktor-client-core:1.0.1" implementation "io.ktor:ktor-client-android:1.0.1"
Vergessen Sie nicht, Manifest Informationen hinzuzufügen, die Sie über das Internet verwenden.
<uses-permission android:name="android.permission.INTERNET"/>
Versuchen wir, die Serverantwort als Zeichenfolge abzurufen. Was könnte einfacher sein?
private const val BASE_URL = "https://httpbin.org" private const val GET_UUID = "$BASE_URL/uuid" fun simpleCase() { val client = HttpClient() GlobalScope.launch(Dispatchers.IO) { val data = client.get<String>(GET_UUID) Log.i("$BASE_TAG Simple case ", data) } }
Sie können einen Client ohne Parameter erstellen. Erstellen Sie einfach eine Instanz von
HttpClient()
. In diesem Fall wählt Ktor die gewünschte Engine aus und verwendet sie mit den Standardeinstellungen (wir haben eine Engine angeschlossen - Android, aber es gibt andere, zum Beispiel OkHttp).
Warum Coroutinen? Weil
get()
eine
suspend
Funktion ist.
Was kann als nächstes getan werden? Sie haben bereits Daten vom Server in Form einer Zeichenfolge. Es reicht aus, diese zu analysieren und Klassen abzurufen, mit denen Sie bereits arbeiten können. In diesem Fall scheint es einfach und schnell zu sein.
Wir bekommen eine rohe Antwort
Manchmal kann es erforderlich sein, eine Reihe von Bytes anstelle einer Zeichenfolge abzurufen. Experimentieren Sie gleichzeitig mit Asynchronität.
fun performAllCases() { GlobalScope.launch(Dispatchers.IO) { simpleCase() bytesCase() } } suspend fun simpleCase() { val client = HttpClient() val data = client.get<String>(GET_UUID) Log.i("$BASE_TAG Simple case", data) } suspend fun bytesCase() { val client = HttpClient() val data = client.call(GET_UUID).response.readBytes() Log.i("$BASE_TAG Bytes case", data.joinToString(" ", "[", "]") { it.toString(16).toUpperCase() }) }
An Stellen, an denen
HttpClient
Methoden
HttpClient
, wie z. B.
call()
und
get()
, wird
await()
unter der Haube aufgerufen. In diesem Fall sind die Aufrufe von
simpleCase()
und
bytesCase()
immer sequentiell. Sie benötigen es parallel - wickeln Sie einfach jeden Anruf in eine separate Coroutine. In diesem Beispiel sind neue Methoden erschienen.
call(GET_UUID)
Sie
call(GET_UUID)
wird ein Objekt zurückgegeben, von dem wir Informationen über die Anforderung, ihre Konfiguration, Antwort und den Client erhalten können. Das Objekt enthält viele nützliche Informationen - vom Antwortcode und der Protokollversion bis zum Kanal mit denselben Bytes.
Müssen Sie es irgendwie schließen?
Die Entwickler geben an, dass Sie die Methode
close()
auf dem Client aufrufen müssen, damit die HTTP-Engine ordnungsgemäß heruntergefahren wird. Wenn Sie einen Aufruf tätigen und den Client sofort schließen müssen, können Sie die Methode
use{}
verwenden, da
HttpClient
die
Closable
Schnittstelle implementiert.
suspend fun closableSimpleCase() { HttpClient().use { val data: String = it.get(GET_UUID) Log.i("$BASE_TAG Closable case", data) } }
Beispiele neben GET
In meiner Arbeit ist
POST
die zweitbeliebteste Methode. Betrachten Sie das Beispiel zum Festlegen von Parametern, Headern und Anforderungshauptteilen.
suspend fun postHeadersCase(client: HttpClient) { val data: String = client.post(POST_TEST) { fillHeadersCaseParameters() } Log.i("$BASE_TAG Post case", data) } private fun HttpRequestBuilder.fillHeadersCaseParameters() { parameter("name", "Andrei")
Tatsächlich haben Sie im letzten Parameter der Funktion
post()
Zugriff auf den
HttpRequestBuilder
, mit dem Sie eine beliebige Anforderung bilden können.
Die
post()
-Methode analysiert einfach die Zeichenfolge, konvertiert sie in eine URL, legt den Methodentyp explizit fest und stellt eine Anforderung.
suspend fun rawPostHeadersCase(client: HttpClient) { val data: String = client.call { url.takeFrom(POST_TEST) method = HttpMethod.Post fillHeadersCaseParameters() } .response .readText() Log.i("$BASE_TAG Raw post case", data) }
Wenn Sie den Code der letzten beiden Methoden ausführen, ist das Ergebnis ähnlich. Der Unterschied ist nicht groß, aber die Verwendung von Wrappern ist bequemer. Die Situation ist für
put()
,
delete()
,
patch()
,
head()
und
options()
ähnlich, daher werden wir sie nicht berücksichtigen.
Wenn Sie jedoch genau hinschauen, können Sie feststellen, dass es einen Unterschied bei der Eingabe gibt. Wenn Sie call
call()
Sie eine Antwort auf niedriger Ebene und müssen die Daten selbst lesen. Was ist jedoch mit der automatischen Eingabe? Schließlich sind wir alle daran
Gson
einen Konverter (wie
Gson
) in Retrofit2
Gson
und den Rückgabetyp als bestimmte Klasse anzugeben. Wir werden später über die Konvertierung in Klassen sprechen, aber die
request
hilft dabei, das Ergebnis zu typisieren, ohne an eine bestimmte HTTP-Methode gebunden zu sein.
suspend fun typedRawPostHeadersCase(client: HttpClient) { val data = client.request<String>() { url.takeFrom(POST_TEST) method = HttpMethod.Post fillHeadersCaseParameters() } Log.i("$BASE_TAG Typed raw post", data) }
Senden Sie Formulardaten
Normalerweise müssen Sie Parameter entweder in der Abfragezeichenfolge oder im Text übergeben. Im obigen Beispiel haben wir bereits untersucht, wie dies mit dem
HttpRequestBuilder
. Aber es kann einfacher sein.
Die Funktion
submitForm
akzeptiert die URL als Zeichenfolge, Parameter für die Anforderung und ein boolesches Flag, das
submitForm
, wie Parameter übergeben werden sollen - in der Anforderungszeile oder als Paare im Formular.
suspend fun submitFormCase(client: HttpClient) { val params = Parameters.build { append("Star", "Sun") append("Planet", "Mercury") } val getData: String = client.submitForm(GET_TEST, params, encodeInQuery = true)
Aber was ist mit mehrteiligen / Formulardaten?
Zusätzlich zu Zeichenfolgenpaaren können Sie als Parameter POST-Anforderungsnummern, Bytearrays und verschiedene Eingabestreams übergeben. Unterschiede in Funktion und Parameterbildung. Wir sehen aus wie:
suspend fun submitFormBinaryCase(client: HttpClient) { val inputStream = ByteArrayInputStream(byteArrayOf(77, 78, 79)) val formData = formData { append("String value", "My name is")
Wie Sie vielleicht bemerkt haben, können Sie jedem Parameter auch eine Reihe von Headern hinzufügen.
Deserialisieren Sie die Antwort auf die Klasse
Sie müssen einige Daten aus der Anforderung abrufen, nicht als Zeichenfolge oder Bytes, sondern sofort in eine Klasse konvertieren. Zunächst empfehlen wir in der Dokumentation, eine Funktion für die Arbeit mit json anzuschließen. Ich möchte jedoch reservieren, dass jvm eine bestimmte Abhängigkeit benötigt und dass dies ohne Kotlinx-Serialisierung nicht funktioniert. Ich schlage vor, Gson als Konverter zu verwenden (es gibt Links zu anderen unterstützten Bibliotheken in der Dokumentation, Links zur Dokumentation finden Sie am Ende des Artikels).
build.gradle Projektebene:
buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } } allprojects { repositories { maven { url "https://kotlin.bintray.com/kotlinx" } } }
build.gradle-Anwendungsebene:
apply plugin: 'kotlinx-serialization' dependencies { implementation "io.ktor:ktor-client-json-jvm:1.0.1" implementation "io.ktor:ktor-client-gson:1.0.1" }
Führen Sie nun die Anfrage aus. Ab dem neuen gibt es nur eine Verbindung der Funktion der Arbeit mit Json beim Erstellen des Clients. Ich werde die Open-Weather-API verwenden. Der Vollständigkeit halber werde ich das Datenmodell zeigen.
data class Weather( val consolidated_weather: List<ConsolidatedWeather>, val time: String, val sun_rise: String, val sun_set: String, val timezone_name: String, val parent: Parent, val sources: List<Source>, val title: String, val location_type: String, val woeid: Int, val latt_long: String, val timezone: String ) data class Source( val title: String, val slug: String, val url: String, val crawl_rate: Int ) data class ConsolidatedWeather( val id: Long, val weather_state_name: String, val weather_state_abbr: String, val wind_direction_compass: String, val created: String, val applicable_date: String, val min_temp: Double, val max_temp: Double, val the_temp: Double, val wind_speed: Double, val wind_direction: Double, val air_pressure: Double, val humidity: Int, val visibility: Double, val predictability: Int ) data class Parent( val title: String, val location_type: String, val woeid: Int, val latt_long: String ) private const val SF_WEATHER_URL = "https://www.metaweather.com/api/location/2487956/" suspend fun getAndPrintWeather() { val client = HttpClient(Android) { install(JsonFeature) { serializer = GsonSerializer() } } val weather: Weather = client.get(SF_WEATHER_URL) Log.i("$BASE_TAG Serialization", weather.toString()) }
Und was kann noch
Beispielsweise gibt der Server einen Fehler zurück und Sie haben den Code wie im vorherigen Beispiel. In diesem Fall wird ein Serialisierungsfehler
BadResponseStatus
Sie können den Client jedoch so konfigurieren, dass ein
BadResponseStatus
Fehler ausgelöst wird, wenn der Antwortcode <300 ist. Es reicht aus,
expectSuccess
beim
expectSuccess
des Clients auf
true
zu setzen.
val client = HttpClient(Android) { install(JsonFeature) { serializer = GsonSerializer() } expectSuccess = true }
Beim Debuggen kann die Protokollierung hilfreich sein. Fügen Sie einfach eine Abhängigkeit hinzu und konfigurieren Sie den Client.
implementation "io.ktor:ktor-client-logging-jvm:1.0.1"
val client = HttpClient(Android) { install(Logging) { logger = Logger.DEFAULT level = LogLevel.ALL } }
Wir geben den DEFAULT-Logger an und alles wird an LogCat gesendet. Sie können jedoch die Benutzeroberfläche neu definieren und Ihren eigenen Logger erstellen, wenn Sie dies wünschen (obwohl ich dort keine großen Möglichkeiten gesehen habe, gibt es nur eine Nachricht am Eingang, aber keine Log-Ebene). Wir geben auch die Ebene der Protokolle an, die reflektiert werden müssen.
Referenzen:
Was nicht berücksichtigt wird:
- Arbeiten Sie mit der OkHttp-Engine
- Motoreinstellungen
- Scheinmotor und Prüfung
- Autorisierungsmodul
- Separate Funktionen wie das Speichern von Cookies zwischen Anfragen usw.
- Alles, was nicht für den HTTP-Client für Android gilt (andere Plattformen, Sockets, Serverimplementierung usw.)