第36章:网络请求(Retrofit)——配置、注解、拦截器与转换器
网络请求,几乎是每个App的标配。在Kotlin世界里,Retrofit就是那个最趁手的工具。我个人用了好几年,从Java时代一路跟到Kotlin,可以说它是我最信赖的HTTP客户端库之一。
这一章,我会带你完整走一遍Retrofit的配置流程。从基础搭建到高级玩法,咱们一次说透。
36.1 Retrofit是什么?为什么选它?
Retrofit是Square公司出品的一个类型安全的HTTP客户端。说白了,它把网络请求变成了接口调用。你只需要定义一个接口,加几个注解,就能发起请求。
它的核心优势有三个:
- 注解驱动:用注解描述请求方式、路径、参数
- 自动序列化:配合Gson或Moshi,JSON直接转对象
- 与OkHttp深度集成:拦截器、缓存、日志,全都能用
我记得刚接触Retrofit时,最让我惊艳的是它的简洁性。以前写网络请求,要手动拼URL、处理JSON、管理线程。用了Retrofit后,代码量直接砍半。
36.2 基础配置:三步搞定
先看一个最基础的配置示例。我习惯把Retrofit实例做成单例,避免重复创建。
// 第一步:定义接口
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): User
}
// 第二步:构建Retrofit实例
object RetrofitClient {
private const val BASE_URL = "https://api.example.com/"
private val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService: ApiService = retrofit.create(ApiService::class.java)
}
// 第三步:发起请求
suspend fun loadUser() {
val user = RetrofitClient.apiService.getUser(123)
println("用户名:${user.name}")
}
这里有几个关键点:
baseUrl必须以/结尾,否则会报错。我踩过这个坑,后来就养成了习惯。GsonConverterFactory负责把JSON转成Kotlin对象。如果你用Moshi,就换成MoshiConverterFactory。- OkHttpClient可以单独配置,拦截器、超时时间都在这里设置。
36.3 注解使用:GET、POST、PUT、DELETE
Retrofit的注解很直观。常用的有这些:
| 注解 | 用途 | 示例 |
|---|---|---|
| @GET | 查询数据 | @GET("users") |
| @POST | 新增数据 | @POST("users") |
| @PUT | 更新数据 | @PUT("users/{id}") |
| @DELETE | 删除数据 | @DELETE("users/{id}") |
| @Path | 替换路径参数 | @Path("id") |
| @Query | 添加查询参数 | @Query("page") |
| @Body | 发送请求体 | @Body user: User |
| @Header | 设置请求头 | @Header("Authorization") |
来看一个完整的例子:
interface ApiService {
// GET请求,带路径参数和查询参数
@GET("users/{id}/posts")
suspend fun getUserPosts(
@Path("id") userId: Int,
@Query("page") page: Int = 1,
@Query("limit") limit: Int = 10
): List<Post>
// POST请求,发送JSON body
@POST("users")
suspend fun createUser(@Body user: User): User
// 动态URL,用@Url注解
@GET
suspend fun fetchFromUrl(@Url url: String): ResponseBody
}
我个人比较喜欢用@Query加默认值的方式。这样调用时可以不传参,代码更简洁。
36.4 OkHttp拦截器:日志、重试、Token刷新
拦截器是OkHttp的灵魂。Retrofit底层用的就是OkHttp,所以拦截器完全通用。
我项目中常用的拦截器有三种:
日志拦截器
调试时必备。能看到请求和响应的完整内容。
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}
Token自动刷新拦截器
我曾经被Token过期问题折磨过。后来写了个拦截器,自动检测401并刷新Token。
class AuthInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val request = originalRequest.newBuilder()
.header("Authorization", "Bearer ${TokenManager.getToken()}")
.build()
val response = chain.proceed(request)
// 如果返回401,尝试刷新Token
if (response.code == 401) {
// 同步刷新Token
val newToken = refreshTokenSync()
if (newToken != null) {
TokenManager.saveToken(newToken)
// 重试原始请求
val retryRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer $newToken")
.build()
response.close()
return chain.proceed(retryRequest)
}
}
return response
}
private fun refreshTokenSync(): String? {
// 这里实现Token刷新逻辑
return null
}
}
synchronized或协程的Mutex来保证只有一个刷新请求在执行。
36.5 Gson vs Moshi:选哪个?
这两个都是JSON转换库。Gson更老牌,Moshi是后来者。我两个都用过,说说我的感受。
| 特性 | Gson | Moshi |
|---|---|---|
| 性能 | 中等 | 更快(特别是大JSON) |
| 空安全 | 默认不检查 | 原生支持Kotlin空安全 |
| 自定义适配器 | TypeAdapter | JsonAdapter(更简洁) |
| 社区生态 | 非常成熟 | 正在成长 |
如果你用Kotlin,我个人更推荐Moshi。它对空安全的支持更好,不会出现Gson那种把null转成空字符串的诡异问题。
Moshi的配置也很简单:
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
注意要加KotlinJsonAdapterFactory,否则Moshi不认识Kotlin的数据类。
36.6 核心知识体系
下面这张图,帮你理清Retrofit的整体架构:
36.7 避坑指南
最后,分享几个我踩过的坑:
坑1:baseUrl忘记加斜杠
Retrofit要求baseUrl必须以/结尾。否则会抛出IllegalArgumentException。我刚开始用的时候,排查了半天才发现是这个问题。
坑2:协程与回调混用
Retrofit从2.6.0开始支持suspend函数。如果你还在用Call回调,建议尽快迁移。协程的写法更简洁,而且能避免回调地狱。
坑3:Moshi的Kotlin适配器
用Moshi时,别忘了加KotlinJsonAdapterFactory。否则Moshi会把Kotlin数据类当成Java类处理,导致空安全失效。我曾经因为这个,线上出了好几个空指针异常。
小技巧:调试时把日志拦截器级别设为BODY,能看到完整的请求和响应。上线前记得改成NONE,避免敏感信息泄露。
好了,这一章的内容就到这里。Retrofit的配置、注解、拦截器、转换器,咱们都过了一遍。你可以在自己的项目里试试看,从最简单的GET请求开始,慢慢加上拦截器和自定义转换器。实践出真知,多写几次就熟练了。
公众号:蓝海资料掘金营,微信deep3321