在 Kotlin 中,实现网络请求和响应分析有多种方式,比如如下三种方式:
使用 OkHttp:OkHttp 是一个高效的 HTTP 客户端,它支持 HTTP/2 和 SPDY 协议,并且在网络请求方面表现出色,同时还能很好地与 Kotlin 结合使用。
使用 Retrofit:Retrofit 是一个基于 OkHttp 封装的类型安全的 HTTP 客户端,它可以将 HTTP API 转换为 Kotlin 接口。
使用 Ktor:Ktor 是一个基于 Kotlin 语言的异步框架,提供了简单易用的 HTTP 客户端功能。
1,定义数据类
假设请求的API返回的数据是一个简单的用户对象。
package com.eric.kotlin
// 导入 kotlinx.serialization 库中的 Serializable 注解。
// kotlinx.serialization 是 Kotlin 官方提供的一个用于序列化和反序列化 Kotlin 对象的库,
// Serializable 注解则是该库的核心注解之一,用于标记一个类可以被序列化和反序列化。
import kotlinx.serialization.Serializable
// @Serializable 注解被应用于 EricUser 类,表明这个类的实例可以被序列化为某种格式(如 JSON、XML 等), 也可以从相应的格式反序列化为 EricUser 对象。
// 在使用 kotlinx.serialization 库进行序列化和反序列化操作时,只有被 @Serializable 注解标记的类才能被处理。
// data class 是 Kotlin 中的一种特殊类,它会自动为类生成一些常用的方法,
// 包括 equals()、hashCode()、toString() 和 copy() 方法。
// 这些方法的实现基于类的属性,使得数据类在处理数据时更加方便和简洁。
@Serializable
data class EricUser(val name: String, val email: String)
可以对上面定义的数据类进行序列化和反序列化,如下所示:
package com.eric.kotlin
// encodeToString是一个扩展函数,用于将一个可序列化的对象编码为字符串,通常用于将对象序列化为 JSON 格式。
import kotlinx.serialization.encodeToString
// Json是 kotlinx.serialization 库中用于处理 JSON 序列化和反序列化的核心类,它提供了一系列方法来完成 JSON 数据的编码和解码操作。
import kotlinx.serialization.json.Json
fun main() {
val user = EricUser("Eric", "eric@163.com")
// 调用了 Json 类的 encodeToString 方法,将 user 对象序列化为 JSON 格式的字符串,并将结果赋值给 jsonUser 变量。
val jsonUser = Json.encodeToString(user)
println("json serialized:$jsonUser") //输出:json serialized:{"name":"Eric","email":"eric@163.com"}
// 调用了 Json 类的 decodeFromString 方法,将之前序列化得到的 JSON 字符串 jsonUser 反序列化为 EricUser 类型的对象。
val deJsonUser = Json.decodeFromString<EricUser>(jsonUser)
println("deJsonUser:$deJsonUser") //输出:deJsonUser:EricUser(name=Eric, email=eric@163.com)
}
在上面的代码中使用到了序列化和反序列化,需要首先引入相关依赖,在build.gradle中可以加入如下serialization相关依赖。
plugins {
kotlin("jvm") version "1.9.23"
kotlin("plugin.serialization") version "1.9.20"
}
dependencies {
testImplementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
2,状态封装
package com.eric.kotlin
// 定义了一个密封类 Result,它用于封装网络请求或其他异步操作的结果状态。
// 密封类是 Kotlin 中一种特殊的类,它的子类是固定的,并且必须在与密封类相同的文件中声明。
// sealed class 关键字用于定义密封类。
// 密封类的主要作用是限制继承关系,所有的子类必须在密封类的同一文件中定义,
// 这样在进行 when 表达式匹配时,编译器可以确保所有可能的情况都被处理,避免出现未处理的分支。
// <out T> 是 Kotlin 的协变类型参数声明。
// out 关键字表示类型参数 T 是协变的,即如果 A 是 B 的子类型,那么 Result<A> 也是 Result<B> 的子类型。这使得 Result 类在处理不同类型的数据时更加灵活。
// EricResult 类有三个子类:Loading、Success 和 Error,分别表示操作正在进行、操作成功和操作失败三种状态。
sealed class EricResult<out T> {
// Nothing 是 Kotlin 中的一个特殊类型,表示没有任何值,在这里使用 Nothing 表示 Loading 状态不携带任何具体的数据。
data object Loading : EricResult<Nothing>()
// <out T> 表示 Success 类也是协变的。val data: T 是一个只读属性,用于存储操作成功后返回的数据。
// 当异步操作成功完成时,可以使用 Result.Success(data) 来表示操作成功,并携带具体的数据。
data class Success<out T>(val data: T) : EricResult<T>()
// Error 类用于表示操作失败的状态。val exception: Throwable 是一个只读属性,用于存储操作失败时抛出的异常信息。
// EricResult<Nothing> 表示 Error 状态不携带其他额外的数据,只关注异常信息。
data class Error(val exception: Throwable) : EricResult<Nothing>()
}
3,定义API接口
使用Retrofit定义API接口。
package com.eric.kotlin
// 导入了 Retrofit 库中的 GET 注解。
// Retrofit 是一个类型安全的 HTTP 客户端,用于简化与 RESTful API 的交互。
// GET 注解是 Retrofit 提供的,用于标记一个方法为 HTTP GET 请求。
import retrofit2.http.GET
interface EricApiService {
// @GET 是 Retrofit 的注解,用于指定该方法对应的 HTTP 请求类型为 GET。
// "users" 是请求的相对路径。当使用 Retrofit 构建 API 调用时,
// 这个路径会与 Retrofit 实例中设置的基础 URL 组合成完整的请求 URL。
// 例如,如果基础 URL 是 https://example.com/api/,
// 那么这个请求的完整 URL 就是 https://example.com/api/users。
// suspend 是 Kotlin 协程中的关键字,用于标记一个挂起函数。
// 挂起函数可以在不阻塞当前线程的情况下暂停执行,等待某个操作完成后再继续执行。
// 在 Android 开发或其他异步编程场景中,使用 suspend 函数可以更方便地处理异步操作,避免回调地狱。
@GET("users")
suspend fun getUsers(): List<EricUser>
}
4,创建 Retrofit 实例
package com.eric.kotlin
// Retrofit 是一个用于构建类型安全的 HTTP 客户端的核心类,通过它可以方便地与 RESTful API 进行交互。
import retrofit2.Retrofit
// GsonConverterFactory 是 Retrofit 的一个数据转换器工厂,
// 用于将服务器返回的 JSON 数据转换为 Kotlin 对象,以及将 Kotlin 对象转换为 JSON 数据发送给服务器。
import retrofit2.converter.gson.GsonConverterFactory
// object 关键字在 Kotlin 中用于定义单例对象。单例模式确保一个类只有一个实例,并提供一个全局访问点。
object EricNetworkClient {
// https://jsonplaceholder.typicode.com/ 是一个免费的、公开的 RESTful API 服务,专门为开发者提供模拟数据,在开发和测试过程中具有广泛的用途。
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
// by lazy 是 Kotlin 提供的一种延迟初始化方式。
// 使用 lazy 委托的属性会在第一次被访问时进行初始化,而不是在对象创建时就初始化。这样可以避免不必要的资源开销,提高性能。
val ericApi: EricApiService by lazy {
// Retrofit.Builder()用于创建一个 Retrofit 构建器实例,用于配置 Retrofit 客户端的各项参数。
Retrofit.Builder()
// 设置 Retrofit 客户端的基础 URL,使用前面定义的 BASE_URL 常量。
.baseUrl(BASE_URL)
// 添加 GsonConverterFactory 作为数据转换器工厂,使得 Retrofit 能够自动处理 JSON 数据的序列化和反序列化。
.addConverterFactory(GsonConverterFactory.create())
// 调用 build() 方法构建 Retrofit 客户端实例。
.build()
// 通过 create() 方法创建 EricApiService 接口的实现对象。
// Retrofit 会根据接口中定义的注解和方法,动态生成一个实现类,用于处理具体的网络请求。
.create(EricApiService::class.java)
}
}
5,使用flow处理网络请求
package com.eric.kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow // Kotlin 协程库中用于表示异步数据流的类型
import kotlinx.coroutines.flow.flow // 是一个构建 Flow 的工厂函数,用于创建一个冷流。
class EricDataRepository {
// 创建了一个私有属性 apiService,它是 EricApiService 接口的实例,通过 EricNetworkClient 单例对象获取。
private val apiService = EricNetworkClient.ericApi
// 使用 Flow 包装网络请求
fun getUsers() = flow { // 使用 flow 构建一个冷流,冷流只有在被收集时才会开始执行。
emit(EricResult.Loading)
try {
val users = apiService.getUsers() // 调用挂起函数
// 发射一个 EricResult.Success 状态,并携带获取到的用户数据。
emit(EricResult.Success(users))
} catch (e: Exception) {
// 发射一个 EricResult.Error 状态,并携带捕获到的异常。
emit(EricResult.Error(e))
}
}
// 带重试的版本
fun getUsersWithRetry(retries: Int = 3) = flow {
var retryCount: Long = 0
while (true) {
emit(EricResult.Loading)
try {
val users = apiService.getUsers()
emit(EricResult.Success(users))
break
} catch (e: Exception) {
if (retryCount++ >= retries) {
emit(EricResult.Error(e))
break
}
delay(1000 * retryCount)
}
}
}
}
6,使用协程收集flow中的数据
fun main() {
runBlocking {
val repository = EricDataRepository()
// 获取 Flow:调用 repository.getUsers() 方法,
// 该方法返回一个 Flow<EricResult<List<EricUser>>> 类型的数据流。
val flow = repository.getUsers()
// 收集 Flow:使用 collect 方法对 Flow 进行收集。
// collect 是一个挂起函数,它会依次处理 Flow 中发射的每个元素。
try {
flow.collect {
result ->
when(result) {
is EricResult.Loading -> println("loading")
is EricResult.Error -> println("error:${result.exception.message}")
is EricResult.Success -> println("success:${result.data}")
}
}
} catch (e: Exception) {
println("Exception occurred while collecting flow: ${e.message}")
}
val flow1 = repository.getUsersWithRetry(3)
try {
flow1.collect {
result ->
when(result) {
is EricResult.Loading -> println("loading1")
is EricResult.Error -> println("error1:${result.exception.message}")
is EricResult.Success -> println("success1:${result.data}")
}
}
} catch (e: Exception) {
println("Exception occurred while collecting flow: ${e.message}")
}
}
}
上面的代码输出如下:
loading
success:[EricUser(name=Leanne Graham, email=Sincere@april.biz), ...]
loading1
success1:[EricUser(name=Leanne Graham, email=Sincere@april.biz), ...]