Saya suka Retrofit2 sebagai pengembang Android, tetapi bagaimana dengan mencoba mendapatkan kualitas klien HTTP Ktor? Menurut pendapat saya, untuk pengembangan Android itu tidak lebih buruk dan tidak lebih baik, hanya salah satu opsi, meskipun jika Anda membungkus semuanya sedikit, itu bisa berubah dengan sangat baik. Saya akan mempertimbangkan fitur-fitur dasar yang memungkinkan untuk mulai menggunakan Ktor sebagai klien HTTP - membuat berbagai jenis permintaan, menerima tanggapan dan jawaban mentah dalam bentuk teks, menghapus json ke dalam kelas melalui konverter, log.

Secara umum, Ktor adalah kerangka kerja yang dapat bertindak sebagai klien HTTP. Saya akan mempertimbangkannya dari sisi pengembangan untuk Android. Sepertinya Anda tidak akan melihat kasus penggunaan yang sangat rumit di bawah ini, tetapi fitur dasarnya pasti. Kode dari contoh di bawah ini dapat dilihat di
GitHub .
Ktor menggunakan coroutine dari Kotlin 1.3, daftar artefak yang tersedia dapat ditemukan di
sini , versi saat ini adalah
1.0.1
.
Untuk pertanyaan, saya akan menggunakan
HttpBin .
Penggunaan sederhana
Untuk memulai, Anda akan memerlukan dependensi dasar untuk klien Android:
implementation "io.ktor:ktor-client-core:1.0.1" implementation "io.ktor:ktor-client-android:1.0.1"
Jangan lupa menambahkan informasi ke Manifest bahwa Anda menggunakan Internet.
<uses-permission android:name="android.permission.INTERNET"/>
Mari kita coba untuk mendapatkan respons server sebagai string, apa yang bisa lebih mudah?
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) } }
Anda dapat membuat klien tanpa parameter, cukup buat instance
HttpClient()
. Dalam hal ini, Ktor akan memilih mesin yang diinginkan dan menggunakannya dengan pengaturan default (kami memiliki satu mesin yang terhubung - Android, tetapi ada yang lain, misalnya, OkHttp).
Mengapa coroutine? Karena
get()
adalah fungsi
suspend
.
Apa yang bisa dilakukan selanjutnya? Anda sudah memiliki data dari server dalam bentuk string, itu sudah cukup untuk menguraikannya dan mendapatkan kelas yang sudah bisa Anda gunakan. Tampaknya sederhana dan cepat dalam hal penggunaan ini.
Kami mendapat jawaban mentah
Kadang-kadang mungkin perlu untuk mendapatkan satu set byte daripada string. Pada saat yang sama, bereksperimenlah dengan asinkron.
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() }) }
Di tempat-tempat di mana metode
HttpClient
, seperti
call()
dan
get()
,
await()
akan dipanggil di bawah tenda. Jadi dalam hal ini panggilan ke
simpleCase()
dan
bytesCase()
akan selalu berurutan. Anda membutuhkannya secara paralel - cukup bungkus setiap panggilan dalam coroutine terpisah. Dalam contoh ini, metode baru telah muncul. Panggilan
call(GET_UUID)
akan mengembalikan objek tempat kami dapat memperoleh informasi tentang permintaan, konfigurasi, respons, dan klien. Objek berisi banyak informasi berguna - dari kode respons dan versi protokol ke saluran dengan byte yang sama.
Apakah Anda perlu menutupnya?
Pengembang menunjukkan bahwa agar mesin HTTP untuk dimatikan dengan benar, Anda perlu memanggil metode
close()
pada klien. Jika Anda perlu melakukan satu panggilan dan segera menutup klien, Anda dapat menggunakan metode
use{}
, karena
HttpClient
mengimplementasikan antarmuka
Closable
.
suspend fun closableSimpleCase() { HttpClient().use { val data: String = it.get(GET_UUID) Log.i("$BASE_TAG Closable case", data) } }
Contoh selain GET
Dalam pekerjaan saya, metode kedua yang paling populer adalah
POST
. Pertimbangkan contoh pengaturan parameter, tajuk dan badan permintaan.
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")
Bahkan, dalam parameter terakhir dari fungsi
post()
, Anda memiliki akses ke
HttpRequestBuilder
, yang dengannya Anda dapat membentuk permintaan apa pun.
Metode
post()
mem-parsing string, mengkonversinya menjadi URL, secara eksplisit menetapkan jenis metode, dan membuat permintaan.
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) }
Jika Anda menjalankan kode dari dua metode terakhir, hasilnya akan serupa. Perbedaannya tidak besar, tetapi menggunakan pembungkus lebih nyaman. Situasinya mirip dengan
put()
,
delete()
,
patch()
,
head()
dan
options()
, jadi kami tidak akan mempertimbangkannya.
Namun, jika Anda perhatikan dengan seksama, Anda dapat melihat bahwa ada perbedaan dalam pengetikan. Saat Anda menelepon
call()
Anda mendapatkan jawaban tingkat rendah dan Anda harus membaca sendiri datanya, tetapi bagaimana dengan mengetik otomatis? Bagaimanapun, kita semua terbiasa menghubungkan konverter (seperti
Gson
) di Retrofit2 dan menunjukkan tipe pengembalian sebagai kelas tertentu. Kita akan berbicara tentang mengonversi ke kelas nanti, tetapi metode
request
akan membantu menentukan hasil tanpa mengikat ke metode HTTP tertentu.
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) }
Kirim data formulir
Biasanya Anda perlu meneruskan parameter baik di string kueri atau di badan. Dalam contoh di atas, kami telah memeriksa cara melakukan ini menggunakan
HttpRequestBuilder
. Tapi itu bisa lebih mudah.
Fungsi
submitForm
menerima url sebagai string, parameter untuk permintaan dan bendera boolean yang memberi tahu cara meneruskan parameter - di baris permintaan atau sebagai pasangan dalam formulir.
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)
Tapi bagaimana dengan multipart / form-data?
Selain pasangan string, Anda dapat lulus sebagai parameter nomor permintaan POST, array byte dan berbagai aliran Input. Perbedaan dalam fungsi dan pembentukan parameter. Kami terlihat seperti:
suspend fun submitFormBinaryCase(client: HttpClient) { val inputStream = ByteArrayInputStream(byteArrayOf(77, 78, 79)) val formData = formData { append("String value", "My name is")
Seperti yang mungkin Anda perhatikan - Anda juga dapat melampirkan satu set header ke setiap parameter.
Deserialize jawaban ke kelas
Anda perlu mendapatkan beberapa data dari permintaan, bukan sebagai string atau byte, tetapi segera dikonversi ke kelas. Untuk memulainya, dalam dokumentasi kami sarankan untuk menghubungkan fitur untuk bekerja dengan json, tetapi saya ingin membuat reservasi bahwa jvm membutuhkan ketergantungan tertentu dan tanpa kotlinx-serialisasi semua ini tidak akan berjalan. Saya sarankan menggunakan Gson sebagai konverter (ada tautan ke perpustakaan lain yang didukung dalam dokumentasi, tautan ke dokumentasi akan ada di akhir artikel).
tingkat proyek build.gradle:
buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } } allprojects { repositories { maven { url "https://kotlin.bintray.com/kotlinx" } } }
tingkat aplikasi build.gradle:
apply plugin: 'kotlinx-serialization' dependencies { implementation "io.ktor:ktor-client-json-jvm:1.0.1" implementation "io.ktor:ktor-client-gson:1.0.1" }
Sekarang jalankan permintaan. Dari yang baru, hanya akan ada koneksi fitur bekerja dengan Json saat membuat klien. Saya akan menggunakan API cuaca terbuka. Untuk kelengkapan, saya akan menunjukkan model data.
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()) }
Dan apa lagi yang bisa
Misalnya, server mengembalikan kesalahan, dan Anda memiliki kode seperti pada contoh sebelumnya. Dalam hal ini, Anda akan menerima kesalahan serialisasi, tetapi Anda dapat mengonfigurasi klien sehingga kesalahan
BadResponseStatus
dilemparkan ketika kode respons <300. Sudah cukup untuk mengatur
expectSuccess
menjadi
true
ketika membangun klien.
val client = HttpClient(Android) { install(JsonFeature) { serializer = GsonSerializer() } expectSuccess = true }
Saat debugging, logging mungkin berguna. Cukup tambahkan satu ketergantungan dan konfigurasikan klien.
implementation "io.ktor:ktor-client-logging-jvm:1.0.1"
val client = HttpClient(Android) { install(Logging) { logger = Logger.DEFAULT level = LogLevel.ALL } }
Kami menentukan DEFAULT logger dan semuanya akan masuk ke LogCat, tetapi Anda dapat mendefinisikan ulang antarmuka dan membuat logger Anda sendiri jika Anda mau (walaupun saya tidak melihat peluang besar di sana, hanya ada pesan di input, tetapi tidak ada level log). Kami juga menunjukkan tingkat log yang perlu direfleksikan.
Referensi:
Apa yang tidak dipertimbangkan:
- Bekerja dengan mesin OkHttp
- Pengaturan Mesin
- Mesin tiruan dan pengujian
- Modul otorisasi
- Fitur terpisah seperti menyimpan cookie di antara permintaan, dll.
- Segala sesuatu yang tidak berlaku untuk klien HTTP untuk Android (platform lain, bekerja melalui soket, implementasi server, dll.