我喜欢Retrofit2作为Android开发人员,但是如何尝试获得Ktor HTTP客户端的质量呢? 以我的观点,对于Android开发来说,这并不坏也没有更好,只是选择之一,尽管如果将所有内容都包装一遍,效果可能会很好。 我将考虑可以开始将Ktor用作HTTP客户端的基本功能-创建各种类型的请求,接收文本形式的原始响应和答案,通过转换器将json反序列化为类以及进行日志记录。

通常,Ktor是一个可以充当HTTP客户端的框架。 我将从Android的开发方面考虑。 下面不太可能看到非常复杂的用例,但是基本功能是准确的。 以下示例中的代码可以在
GitHub上查看。
Ktor使用Kotlin 1.3中的协程,可以在
此处找到可用工件的列表,当前版本是
1.0.1
。
对于查询,我将使用
HttpBin 。
使用简单
首先,您需要Android客户端的基本依赖项:
implementation "io.ktor:ktor-client-core:1.0.1" implementation "io.ktor:ktor-client-android:1.0.1"
不要忘了向使用Internet的清单添加信息。
<uses-permission android:name="android.permission.INTERNET"/>
让我们尝试以字符串形式获取服务器响应,这会更容易些吗?
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) } }
您可以创建不带参数的客户端,只需创建
HttpClient()
的实例即可。 在这种情况下,Ktor将选择所需的引擎,并以默认设置使用它(我们连接了一个引擎-Android,但还有其他引擎,例如OkHttp)。
为什么要协程? 因为
get()
是一个
suspend
函数。
接下来可以做什么? 您已经从服务器获得了字符串形式的数据,足以解析它并获取可以使用的类。 在这种使用情况下,它看起来既简单又快速。
我们得到一个原始答案
有时可能需要获取一组字节而不是字符串。 同时,尝试异步。
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() }) }
在
HttpClient
方法被
HttpClient
,例如
call()
和
get()
,
await()
将在
await()
被调用。 因此,在这种情况下,对
simpleCase()
和
bytesCase()
的调用将始终是顺序的。 您需要并行处理-只需将每个调用包装在单独的协程中即可。 在此示例中,出现了新方法。 调用
call(GET_UUID)
将返回一个对象,我们可以从该对象获取有关请求,其配置,响应和客户端的信息。 该对象包含许多有用的信息-从响应代码和协议版本到具有相同字节的通道。
您是否需要以某种方式关闭它?
开发人员指出,要使HTTP引擎正确关闭,您需要在客户端上调用
close()
方法。 如果需要拨打电话并立即关闭客户端,则可以使用
use{}
方法,因为
HttpClient
实现了
Closable
接口。
suspend fun closableSimpleCase() { HttpClient().use { val data: String = it.get(GET_UUID) Log.i("$BASE_TAG Closable case", data) } }
GET以外的例子
在我的工作中,第二受欢迎的方法是
POST
。 考虑设置参数,标头和请求正文的示例。
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")
实际上,在
post()
函数的最后一个参数中,您可以访问
HttpRequestBuilder
,您可以使用它来形成任何请求。
post()
方法仅分析字符串,将其转换为URL,显式设置方法的类型,然后发出请求。
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) }
如果从后两种方法执行代码,结果将相似。 差别不是很大,但是使用包装器更为方便。 对于
put()
,
delete()
,
patch()
,
head()
和
options()
,情况类似,因此我们将不考虑它们。
但是,如果仔细观察,您会发现打字有所不同。 当您致电
call()
您会得到一个低级答案,您必须自己读取数据,但是自动键入又如何呢? 毕竟,我们都习惯于在Retrofit2中连接转换器(例如
Gson
)并将返回类型指示为特定类。 稍后我们将讨论转换为类,但是
request
方法将有助于在不绑定到特定HTTP方法的情况下代表结果。
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) }
提交表格数据
通常,您需要在查询字符串或正文中传递参数。 在上面的示例中,我们已经研究了如何使用
HttpRequestBuilder
做到这一点。 但这可能更容易。
submitForm
函数接受url作为字符串,请求的参数以及一个布尔标志,该标志指示如何传递参数-在请求行中还是在表单中成对传递。
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)
但是多部分/表单数据呢?
除了字符串对,您还可以传递POST请求编号,字节数组和各种Input流作为参数。 功能和参数形成方面的差异。 我们看起来像:
suspend fun submitFormBinaryCase(client: HttpClient) { val inputStream = ByteArrayInputStream(byteArrayOf(77, 78, 79)) val formData = formData { append("String value", "My name is")
您可能已经注意到-您还可以将一组标题附加到每个参数。
反序列化课程答案
您需要从请求中获取一些数据,而不是字符串或字节,而是立即将其转换为类。 首先,在文档中,我们建议您连接一个用于json的功能,但是我想保留一点,即jvm需要特定的依赖关系,并且如果没有kotlinx序列化,那么所有这些都不会成功。 我建议使用Gson作为转换器(文档中有指向其他受支持库的链接,该文档的链接将在本文结尾)。
build.gradle项目级别:
buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } } allprojects { repositories { maven { url "https://kotlin.bintray.com/kotlinx" } } }
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" }
现在执行请求。 从新版本开始,只有在创建客户端时才能使用Json的功能。 我将使用开放天气API。 为了完整起见,我将显示数据模型。
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()) }
还有什么可以
例如,服务器返回一个错误,并且您具有前面示例中的代码。 在这种情况下,您将收到序列化错误,但是您可以配置客户端,以便当响应代码小于300时引发
BadResponseStatus
错误。 在构建客户端时,将
expectSuccess
设置为
true
就足够了。
val client = HttpClient(Android) { install(JsonFeature) { serializer = GsonSerializer() } expectSuccess = true }
调试时,日志记录可能很有用。 只需添加一个依赖项并配置客户端。
implementation "io.ktor:ktor-client-logging-jvm:1.0.1"
val client = HttpClient(Android) { install(Logging) { logger = Logger.DEFAULT level = LogLevel.ALL } }
我们指定了DEFAULT记录器,一切都将进入LogCat,但是您可以根据需要重新定义界面并创建自己的记录器(尽管我在那儿看不到任何大的机会,但是输入中只有一条消息,但是没有日志级别)。 我们还指出了需要反映的日志级别。
参考文献:
什么不考虑:
- 使用OkHttp引擎
- 引擎设定
- 模拟引擎和测试
- 授权模块
- 独立的功能,例如在请求之间存储cookie等。
- 不适用于Android的HTTP客户端的所有内容(其他平台,通过套接字工作,服务器实现等)。