背景
与 Server 的数据交互已经成为 App 必不可少的一个重要部分,常用的方式即 HTTP(S),当然也有 WebSocket、TCP、UDP 等等
在 KMM 模块中,为保证双端逻辑一致,且对 JVM、Native 进行统一兼容,可以使用官方推荐的 Ktor 进行网络通信,Kotlinx.Serialization 来进行数据解析
这篇文章就来介绍在 KMM 中如何发起并处理网络请求,后面的文章再详细介绍 kotlinx.serialization 的使用
Ktor 是什么?
Ktor 是由 JetBrains 开发的一套用于解决各类应用中网络连接的框架,不仅可以用在发起请求的各类客户端(不是所谓的 App),还可以构建微服务
针对客户端能力,通过一系列插件,可以支持 HTTP 的各类特性,如:Cookies、重定向、代理、UA、WebSocket 等,在一定程度上,还可以支持一些简单的 TCP 或 UDP 通信
另外,Ktor 还支持为不同的平台配置不同的 HTTP 引擎,如:为 Android 配置 OkHttp 或 HttpURLConnection,为 iOS 配置 NSURLSession,或者为 JVM 配置 Apache HttpClient、为 JavaScript (Node.js) 配置 node-fetch,以便使用同一套代码逻辑处理网络请求
由于现在的 RESTful API 通常会以 JSON 作为通信数据格式,在 JVM 平台上,Ktor 还支持与 Gson、Jackson 协同工作,而对于 Kotlin Multiplatform(当然包括 KMM)可以与 kotlinx.serialization 进行协作
由于 Ktor 适用的平台广泛,本文只对 KMM 平台上的使用进行说明
为 KMM 模块配置 Ktor
如果你使用的 IDE 是 IntelliJ IDEA Ultimate 版本,可以考虑安装 Ktor 插件,但基于 Community 版本的 Android Studio 等 IDE 并不支持该插件,当然它对实际使用影响不大
对于 KMM 模块,首先需要在 Common 的依赖中加入 Ktor 的核心依赖
由于 Ktor 底层依赖协程一些核心功能,同时 Ktor 需要使用基于 Kotlin Native 且实现多线程版本的协程库,所以还需要加入对协程的依赖
// build.gradle.kts
// 2022 年 4 月,Ktor 正式发布了 2.0.0 版本
val ktor_version = "2.0.2"
// ...
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:$ktor_version")
}
}
Android 模块中加入 Ktor Android 端默认引擎(使用 HttpURLConnection)的依赖
// build.gradle.kts
androidMain {
dependencies {
implementation("io.ktor:ktor-client-android:$ktor_version")
}
}
如果需要使用 OkHttp 来作为 HTTP 能力的引擎,可以使用如下的依赖
// build.gradle.kts
androidMain {
dependencies {
implementation("io.ktor:ktor-client-okhttp:$ktor_version")
}
}
另外,Android 端也可以使用 CIO(Coroutine(协程) based I/O 实现)引擎,但 CIO 目前还不支持 HTTP/2
对于 iOS,则加入 iOS 的引擎依赖,由于 iOS 的 HTTP 网络请求都是使用 NSURLSession(包括著名的 AFNetworking,NSURLConnection 早已经不用了),所以也就不像 Android 上有多种选择
// build.gradle.kts
iosMain {
dependencies {
implementation("io.ktor:ktor-client-darwin:$ktor_version")
}
}
由于 Ktor 是 Kotlin 团队主要负责开发和维护,所以对 Kotlin 相关技术栈支持的比较友好,且部分技术应用的也比较激进,比如 Kotlin Native 的 New Memory Management,所以官方建议大家使用 Kotlin 协程,这就要求在宿主 App(Android 端)中添加协程相关的依赖
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1")
}
Ktor 已经适配了 New Memory 技术,如果还需要开启 New Memory,则需要根据 New Memory 官方的文档要求,在 gradle.properties 文件中,添加以下的配置项
kotlin.native.binary.memoryModel=experimental
创建 Ktor 的 HttpClient
Ktor 中的 HttpClient 与其他 HTTP 框架类似,都是对发送和接收网络请求的一系列资源、配置的封装,请求与响应的操作方法,以 Extension 的形式表现,调用也非常简洁
在 Common 代码中,首先需要创建一个 HttpClient 的实例
val httpClient by lazy {
HttpClient() }
如果不需要对 HttpClient 默认的引擎(根据 Gradle 中的依赖自动设置)进行特殊配置,以上代码足矣
为保障多平台的一致,在 Common 中的 HttpClient,对 engine 的可配置项非常有限,只有下面的 Proxy 和线程数量可配,同时可以支持一些公共的请求配置,写在 defaultRequest
闭包中即可,具体内容见下面一节
HttpClient {
engine {
proxy = ProxyBuilder.http("http://127.0.0.1:8888")
threadsCount = 4
}
defaultRequest {
// 可配置公共的 Cookies、Headers、Params
}
}
如果需要针对不同的平台和不同的引擎的特性,进行一些自定义配置,则需要用到 expect/actual 的方式来实现 HttpClient
比如在 Android 代码中,针对 OkHttp 进行一些定制
actual val httpClient by lazy {
HttpClient(OkHttp) {
engine {
config {
// 禁止重定向
followRedirects(false)
}
// 加入 Stetho 方便 Debug
addNetworkInterceptor(StethoInterceptor())
}
}
}
或者对 iOS 的 NSURLSession 进行一些配置
actual val httpClient by lazy {
HttpClient(Ios) {
engine {
configureRequest {
// 如果 HttpClient 需要在后台进行上传、下载
NSURLSessionConfiguration.backgroundSessionConfiguration("xxx").apply {
// 添加统一的 Headers
HTTPAdditionalHeaders = mapOf("a" to