【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"
}

稍微解释一下这两个依赖

  1. Ktor的客户端内核

  2. 由于本APP是部署在Android上的,因此需要引入一个Android依赖,Android平台和其他平台的不同点在于Android具有主线程的概念,Android不允许在主线程发送网络请求,而在Kotlin协程中就是主调度器的概念,其内部是post任务到主线程Handler中,这里就不展开太多。当然如果要使用OkHttp也是可以的!

    implementation "io.ktor:ktor-client-okhttp:$ktor_version"
    

    如果想应用到其他客户端平台可以使用CIO

  3. 第三个简单来说就是数据转换的插件,例如将远端发送来的数据(可以是CBOR、Json、Protobuf)转换成一个个数据类。

  4. 而第四个就是第三个的衍生插件,相信用过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官方包装类,具有successfailure两个方法,可以包装成功和失败两种数据,可以简单使用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/创建。

  1. 工程名字。

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的坑先开到这啦!

参考

ktor.io/

作者:米奇律师
链接:https://juejin.cn/post/7136829279903416333
更多Android学习资料可点击下方卡片~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值