网络请求:Retrofit集成、OkHttp配置、协程与网络请求结合、JSON解析
网络请求,说白了就是App跟服务器打交道的那扇门。我做了这么多年Android,见过太多因为网络层写得不好而翻车的项目。今天咱们就把这扇门彻底整明白。
一、Retrofit集成——最优雅的HTTP客户端
Retrofit这东西,我刚开始用的时候就觉得——嗯,这才是人该写的代码。它把网络请求抽象成了接口调用,你只需要定义接口,剩下的它帮你搞定。
1.1 添加依赖
在build.gradle.kts里加上:
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
// 配合OkHttp
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// JSON转换器(这里用Moshi举例)
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
我个人习惯把版本号抽到libs.versions.toml里统一管理,项目大了你就知道这有多香了。
1.2 定义API接口
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
@POST("users")
suspend fun createUser(@Body user: User): User
}
注意看,这里用了suspend关键字。为什么?因为我们要跟协程配合,这个后面细说。
二、OkHttp配置——网络层的守门员
Retrofit底层跑的是OkHttp。你想想看,如果OkHttp没配好,Retrofit再优雅也没用。
2.1 基础配置
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
我在项目中遇到过一个问题:线上环境忘了关日志拦截器,结果用户敏感信息全打出来了……嗯,从那以后我养成了用BuildConfig.DEBUG控制日志级别的习惯。
if (BuildConfig.DEBUG) addInterceptor(...)来条件编译。
2.2 拦截器实战
拦截器是OkHttp的灵魂。我常用的几个:
- 日志拦截器:调试必备,但记得只在debug模式开启
- Token刷新拦截器:自动处理401,用户无感知
- 缓存拦截器:离线也能看数据
class TokenInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val newRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer ${getToken()}")
.build()
return chain.proceed(newRequest)
}
}
三、协程与网络请求结合——告别回调地狱
以前写网络请求,回调嵌套回调,代码丑得自己都不想看。协程来了之后,整个世界清静了。
3.1 为什么用协程?
说白了,协程让异步代码写起来像同步代码。你不需要手动切线程,不需要回调,一个suspend搞定。
// 以前:回调地狱
api.getUser(1, object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
// 处理成功
}
override fun onFailure(call: Call<User>, t: Throwable) {
// 处理失败
}
})
// 现在:协程
viewModelScope.launch {
try {
val user = api.getUser(1)
// 直接使用user
} catch (e: Exception) {
// 统一处理异常
}
}
你想想看,哪个更清爽?
3.2 协程的异常处理
我建议统一封装一个网络请求的扩展函数:
suspend fun <T> safeApiCall(call: suspend () -> T): Result<T> {
return try {
Result.success(call())
} catch (e: Exception) {
when (e) {
is HttpException -> {
// 根据状态码处理
Result.failure(e)
}
is IOException -> {
// 网络问题
Result.failure(e)
}
else -> {
// 其他异常
Result.failure(e)
}
}
}
}
Result类型包装返回值,调用方可以很优雅地处理成功/失败两种情况,不用到处写try-catch。
四、JSON解析——数据格式化的最后一公里
网络请求拿回来的数据是JSON字符串,得转成Kotlin对象才能用。这里有两个主流选择:kotlinx.serialization和Moshi。
4.1 kotlinx.serialization——Kotlin亲儿子
这是JetBrains官方出的,跟Kotlin语言深度绑定。用起来很简单:
@Serializable
data class User(
val id: Int,
val name: String,
@SerialName("avatar_url")
val avatarUrl: String
)
配合Retrofit使用时,需要加一个转换器:
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
4.2 Moshi——Square家的老牌选手
Moshi是Square出的,跟Retrofit同门,配合起来天衣无缝。我个人更习惯用Moshi,因为它的自定义适配器写起来很顺手。
@JsonClass(generateAdapter = true)
data class User(
@Json(name = "id")
val id: Int,
@Json(name = "name")
val name: String,
@Json(name = "avatar_url")
val avatarUrl: String
)
4.3 怎么选?
| 对比项 | kotlinx.serialization | Moshi |
|---|---|---|
| 编译速度 | 快(编译期处理) | 中等(运行时反射) |
| Kotlin特性支持 | 完美(默认参数、data class) | 良好(需额外配置) |
| 自定义解析 | 需要写Serializer | 写JsonAdapter,更灵活 |
| 社区生态 | 较新,但官方支持 | 成熟,文档丰富 |
我的建议是:新项目用kotlinx.serialization,老项目迁移成本高就继续用Moshi。两个都能用,别纠结。
五、完整集成示例
把上面所有东西串起来,就是一个完整的网络层:
// 1. 创建OkHttpClient
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.addInterceptor(TokenInterceptor())
.addInterceptor(if (BuildConfig.DEBUG) HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
} else Interceptor { chain -> chain.proceed(chain.request()) })
.build()
// 2. 创建Retrofit实例
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
// 3. 创建API服务
val api = retrofit.create(ApiService::class.java)
// 4. 在ViewModel中使用
class UserViewModel : ViewModel() {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user.asStateFlow()
fun loadUser(id: Int) {
viewModelScope.launch {
val result = safeApiCall { api.getUser(id) }
result.onSuccess { _user.value = it }
.onFailure { /* 处理错误 */ }
}
}
}
六、知识体系总览
下面这张图把本章的核心脉络画出来了,你可以对照着回顾:
嗯,到这里网络请求这块就讲完了。记住一个原则:网络层要稳定,接口要清晰,异常要兜底。你在项目里按这个思路走,基本不会出大问题。
公众号:蓝海资料掘金营,微信deep3321