Ktor挖坑日记还在用Retrofit网络请求吗?试试Ktor吧
Posted 上马定江山
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ktor挖坑日记还在用Retrofit网络请求吗?试试Ktor吧相关的知识,希望对你有一定的参考价值。
Ktor官方对Ktor的描述是:
Create asynchronous client and server applications. Anything from microservices to multiplatform HTTP client apps in a simple way. Open Source, free, and fun!
创建异步客户端和和服务器应用,从微服务到多平台HTTP客户端应用程序都可以用一种简单的方式完成。开源、免费、有趣!
它具有轻量级+可扩展性强+多平台+异步的特性。
-
轻量级和可扩展性是因为它的内核比较简单,并且当需要一些功能的时候可以加入别的插件到项目中,并不会造成功能冗余。并且Ktor的扩展是使用插拔的方式,使用起来非常简单!
-
异步,Ktor内部是使用Kotlin协程来实现异步,这对于熟悉Kotlin的android开发非常友好。
看到这里可能一头雾水,下面将用一个比较简单的例子来带大家入坑Ktor!等看完这篇文章之后就会对Ktor的这些特性有进一步的了解。
小例子 —— 看猫咪
引入依赖
在app模块的gradle中引入依赖
plugins
...
id 'org.jetbrains.kotlin.plugin.serialization' version "1.7.10" // 跟Kotlin版本一致
dependencies
...
// Ktor
def ktor_version = "2.1.0"
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-android:$ktor_version"
implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
稍微解释一下这两个依赖
-
Ktor的客户端内核
-
由于本APP是部署在Android上的,因此需要引入一个Android依赖,Android平台和其他平台的不同点在于Android具有主线程的概念,Android不允许在主线程发送网络请求,而在Kotlin协程中就是主调度器的概念,其内部是post任务到主线程Handler中,这里就不展开太多。当然如果要使用OkHttp也是可以的!
implementation "io.ktor:ktor-client-okhttp:$ktor_version"
如果想应用到其他客户端平台可以使用CIO
-
第三个简单来说就是数据转换的插件,例如将远端发送来的数据(可以是CBOR、Json、Protobuf)转换成一个个数据类。
-
而第四个就是第三个的衍生插件,相信用过
kotlin-serialization
的人会比较熟悉,是Kotlin序列化插件,本次引用的是json,类似于Gson,可以将json字符串转换成数据类。
当然,如果需要其他插件可以到官网上看看,例如打印日志Logging
implementation "ch.qos.logback:logback-classic:$logback_version"
implementation "io.ktor:ktor-client-logging:$ktor_version"
创建HttpClient
首先创建一个HttpClient实例
val httpClient = HttpClient(Android)
defaultRequest
url
protocol = URLProtocol.HTTP
host = 你的host
port = 你的端口
install(ContentNegotiation)
json()
创建的时候是使用DSL语法的,这里解释一下其中使用的两个配置
-
defaultRequest
:给每个HTTP请求加上BaseUrl例如请求
"/get-cat"
就会向"http://$你的host:$你的端口/get-cat"
发起HTTP请求。 -
ContentNegotiation
:引入数据转换插件。 -
json
:引入自动将json转换数据类的插件。
定义数据类
@Serializable
data class Cat(
val name: String,
val description: String,
val imageUrl: String
)
此处给猫咪定义名字、描述和图片url,需要注意的是需要加上@Serializable
注解,这是使用kotlin-serialization
的前提条件,而需要正常使用kotlin-serialization
,需要在app模块的build.gradle
加上以下plugin
plugins
...
id 'org.jetbrains.kotlin.plugin.serialization' version "1.7.10" // 跟Kotlin版本一致
创建API
interface CatSource
suspend fun getRandomCat(): Result<Cat>
companion object
val instance = CatSourceImpl(httpClient)
class CatSourceImpl(
private val client: HttpClient
) : CatSource
override suspend fun getRandomCat(): Result<Cat> = runCatching
client.get("random-cat").body()
此处声明一个CatSource
接口,接口中声明一个获取随机小猫咪的函数,并且对该接口进行实现。
-
suspend
:HttpClient的方法大多数为suspend函数,例如例子中的get为suspend函数,因此接口也要定义成suspend函数。 -
Result
:Result为Kotlin官方包装类,具有success
和failure
两个方法,可以包装成功和失败两种数据,可以简单使用runCatching
来返回Result@InlineOnly @SinceKotlin("1.3") public inline fun <T, R> T.runCatching(block: T.() -> R): Result<R> return try Result.success(block()) catch (e: Throwable) Result.failure(e)
-
body
:获取返回结果,由于内部协程实现,因此不用担心阻塞主线程的问题,由于引入了ContentNegotiation
,因此获取到结果之后可以对其进行转换,转换成实际数据类。
展示
ViewModel
class MainViewModel : ViewModel()
private val catSource = CatSource.instance
private val _catState = MutableStateFlow<UiState<Cat>>(UiState.Loading)
val catState = _catState.asStateFlow()
init
getRandomCat()
fun getRandomCat()
viewModelScope.launch
_catState.value = UiState.Loading
// fold 方法可以用来对 Result 的结果分情况处理
catSource.getRandomCat().fold(
onSuccess =
_catState.value = UiState.Success(it)
, onFailure =
_catState.value = UiState.Failure(it)
)
sealed class UiState<out T>
object Loading: UiState<Nothing>()
data class Success<T>(val value: T): UiState<T>()
data class Failure(val exc: Throwable): UiState<Nothing>()
inline fun <T> UiState<T>.onState(
onSuccess: (T) -> Unit,
onFailure: (Throwable) -> Unit = ,
onLoading: () -> Unit =
)
when(this)
is UiState.Failure -> onFailure(this.exc)
UiState.Loading -> onLoading()
is UiState.Success -> onSuccess(this.value)
Activity
界面比较简单,因此用Compose实现
class MainActivity : ComponentActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContent
KittyTheme
Column(
modifier = Modifier
.fillMaxWidth()
.padding(32.dp)
)
val viewModel: MainViewModel = viewModel()
val catState by viewModel.catState.collectAsState()
catState.onState(
onSuccess = cat ->
AsyncImage(model = cat.imageUrl, contentDescription = cat.name)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = cat.name,
fontWeight = FontWeight.SemiBold,
fontSize = 20.sp
)
Spacer(modifier = Modifier.height(8.dp))
Text(text = cat.description)
,
onFailure =
Text(text = "Loading Failure!")
,
onLoading =
CircularProgressIndicator()
)
Button(
onClick = viewModel::getRandomCat,
modifier = Modifier.align(Alignment.End)
) Text(text = "Next Cat!")
Spacer(modifier = Modifier.height(8.dp))
-
对state分情况展示
-
加载中就展示转圈圈。
-
成功就展示猫咪图片、猫咪名字、猫咪描述。
-
失败就展示加载失败。
-
-
展示图片的
AsyncImage
来自于Coil展示库,传入imageUrl就好啦,使用Kotlin编写,内部使用协程实现异步。
我们运行一下吧!
总结一下
是不是很简单捏!看起来好像很多,其实核心用法就三个
-
实例HttpClient
-
在HttpClient中配置插件
-
调用
get
或者post
方法
由于内部使用了协程来进行异步,因此不用担心主线程阻塞!令我觉得比较香的是数据转换插件,可以再也不用担心数据转换了。并且支持例如XML、CBOR、Json等等,也不会担心后端会给我们发来什么数据格式了。
还有一个文中没有用到的是Logging插件,可以在logcat打印给服务端发了什么,服务端给客户端发了什么,调试API起来也很方便,跟后端拉扯起来也很有底气!
另外,Android插件不支持WebSocket,但是Okhttp和CIO支持!实际使用中可以用后者创建httpClient!
服务端
创建项目
服务端不是重点就简单提一下,贴一下代码,使用IntelliJ IDEA Ultimate可以直接创建Ktor工程,要是用社区版就去ktor.io/create/创建。
- 工程名字。
2. 配置插件,官方很多插件,不用想着一下子就添加完,需要用的时候再像客户端一样引入依赖就好。
3. 创建项目,下载打开。
编写代码
到Application.kt看一下主函数
fun main()
embeddedServer(Netty, port = 你的端口, host = "0.0.0.0")
configureRouting()
configureSerialization()
.start(wait = true)
-
配置Routing插件
fun Application.configureRouting() routing randomCat() static resources("static") fun Route.randomCat() get("/random-cat") // 随便回一直猫咪给客户端 call.respond(cats.random()) //本地IPV4地址 private const val BASE_URL = "http://$你的host:$你的端口" private val cats = listOf( Cat("夺宝1号", "这是一只可爱的小猫咪", "$BASE_URL/cats/cat1.jpg"), Cat("夺宝2号", "这是一只可爱的小猫咪", "$BASE_URL/cats/cat2.jpg"), Cat("夺宝3号", "这是一只可爱的小猫咪", "$BASE_URL/cats/cat3.jpg"), Cat("夺宝4号", "这是一只可爱的小猫咪", "$BASE_URL/cats/cat4.jpg"), Cat("夺宝5号", "这是一只可爱的小猫咪", "$BASE_URL/cats/cat5.jpg"), Cat("夺宝6号", "这是一只可爱的小猫咪", "$BASE_URL/cats/cat6.jpg"), Cat("夺宝7号", "这是一只可爱的小猫咪", "$BASE_URL/cats/cat7.jpg"), ) @Serializable data class Cat( val name: String, val description: String, val imageUrl: String )
-
配置Serialization插件
fun Application.configureSerialization() install(ContentNegotiation) json()
-
放入图片资源,我放了七只猫咪图片。
然后跑起来就好啦!去手机上看看效果吧!
又总结一次
客户端和服务端使用方式是比较相似的,这也非常友好,由于也是使用Kotlin作为后端,那很多代码都可以拷贝了,例如文中的数据类Cat甚至可以直接拷贝过来。Ktor用起来非常方便,由于其Okhttp插件的存在,在全Kotlin的Android项目中甚至可以考虑Ktor而不是Retrofit(当然Retrofit也是非常优秀的网络请求库)。关于Ktor的坑先开到这啦!
参考
作者:米奇律师
链接:https://juejin.cn/post/7136829279903416333
更多Android学习资料可点击下方卡片~
以上是关于Ktor挖坑日记还在用Retrofit网络请求吗?试试Ktor吧的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Ktor 中设置类似于 Retrofit 的`Retrofit.Builder().baseUrl(baseUrl) 的 basePath?