OkHttp 和 Retrofit 的用法

一、OkHttp 是什么?

OkHttp 是 Square 公司开源的一个高性能 HTTP 客户端库,常用于 Android 网络请求。

它主要负责:

  • 发起 HTTP / HTTPS 请求
  • 连接池复用
  • 请求 / 响应拦截
  • 缓存
  • 超时控制
  • 文件上传下载
  • WebSocket
  • HTTP/2 支持

二、Retrofit 是什么?

Retrofit 也是 Square 公司开源的网络请求框架。

它本质上是对 OkHttp 的封装,主要作用是:

  • 用接口注解描述 HTTP 请求
  • 自动完成 URL 拼接
  • 自动解析 JSON 数据
  • 支持协程、RxJava、LiveData 等
  • 让网络请求代码更简洁、更易维护

通常关系是:

Retrofit 负责接口封装和数据转换
OkHttp 负责真正的网络请求

三、OkHttp 基本用法

1. 添加依赖

implementation 'com.squareup.okhttp3:okhttp:4.12.0'

2. GET 请求

val client = OkHttpClient()

val request = Request.Builder()
    .url("https://www.example.com/api/user")
    .get()
    .build()

client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        e.printStackTrace()
    }

    override fun onResponse(call: Call, response: Response) {
        val result = response.body?.string()
        println(result)
    }
})

注意:

response.body?.string()

只能调用一次,因为它内部是流,读取后就关闭了。


3. POST 表单请求

val client = OkHttpClient()

val formBody = FormBody.Builder()
    .add("username", "admin")
    .add("password", "123456")
    .build()

val request = Request.Builder()
    .url("https://www.example.com/login")
    .post(formBody)
    .build()

client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {}

    override fun onResponse(call: Call, response: Response) {
        val result = response.body?.string()
        println(result)
    }
})

4. POST JSON 请求

val client = OkHttpClient()

val json = """
    {
        "username": "admin",
        "password": "123456"
    }
""".trimIndent()

val body = json.toRequestBody("application/json; charset=utf-8".toMediaType())

val request = Request.Builder()
    .url("https://www.example.com/login")
    .post(body)
    .build()

client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {}

    override fun onResponse(call: Call, response: Response) {
        val result = response.body?.string()
        println(result)
    }
})

需要导入:

import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody

5. OkHttpClient 配置

val client = OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)
    .writeTimeout(10, TimeUnit.SECONDS)
    .retryOnConnectionFailure(true)
    .build()

常见超时:

类型 含义
connectTimeout 连接超时
readTimeout 读取超时
writeTimeout 写入超时
callTimeout 整个请求超时

6. OkHttp 拦截器

普通拦截器

class HeaderInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val oldRequest = chain.request()

        val newRequest = oldRequest.newBuilder()
            .addHeader("Authorization", "Bearer token")
            .addHeader("platform", "android")
            .build()

        return chain.proceed(newRequest)
    }
}

使用:

val client = OkHttpClient.Builder()
    .addInterceptor(HeaderInterceptor())
    .build()

日志拦截器

添加依赖:

implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'

使用:

val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY

val client = OkHttpClient.Builder()
    .addInterceptor(loggingInterceptor)
    .build()

日志级别:

HttpLoggingInterceptor.Level.NONE
HttpLoggingInterceptor.Level.BASIC
HttpLoggingInterceptor.Level.HEADERS
HttpLoggingInterceptor.Level.BODY

四、Retrofit 基本用法

1. 添加依赖

implementation 'com.squareup.retrofit2:retrofit:2.11.0'
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'

如果使用协程:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'

2. 定义数据类

data class User(
    val id: Int,
    val name: String,
    val age: Int
)

3. 定义接口

interface ApiService {

    @GET("user/info")
    suspend fun getUserInfo(
        @Query("userId") userId: String
    ): User

    @POST("user/login")
    suspend fun login(
        @Body body: LoginRequest
    ): LoginResponse
}

请求参数类:

data class LoginRequest(
    val username: String,
    val password: String
)

data class LoginResponse(
    val token: String,
    val userId: String
)

4. 创建 Retrofit 实例

val retrofit = Retrofit.Builder()
    .baseUrl("https://www.example.com/api/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val apiService = retrofit.create(ApiService::class.java)

注意:

baseUrl 必须以 / 结尾

例如:

正确:https://www.example.com/api/
错误:https://www.example.com/api

5. 发起请求

在协程中调用:

lifecycleScope.launch {
    try {
        val user = apiService.getUserInfo("1001")
        println(user.name)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

如果在 ViewModel 中:

viewModelScope.launch {
    try {
        val result = apiService.login(LoginRequest("admin", "123456"))
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

五、Retrofit 常见注解

1. 请求方法注解

@GET
@POST
@PUT
@DELETE
@PATCH
@HEAD
@OPTIONS

示例:

@GET("users")
suspend fun getUsers(): List<User>

2. @Query

用于 GET 请求参数。

@GET("user")
suspend fun getUser(
    @Query("id") id: String
)

最终请求:

/user?id=1001

3. @QueryMap

多个 Query 参数。

@GET("search")
suspend fun search(
    @QueryMap params: Map<String, String>
)

4. @Path

用于替换 URL 路径中的参数。

@GET("user/{id}")
suspend fun getUserById(
    @Path("id") id: String
)

最终请求:

/user/1001

5. @Body

请求体,常用于 JSON 提交。

@POST("login")
suspend fun login(
    @Body request: LoginRequest
): LoginResponse

6. @FormUrlEncoded + @Field

表单提交。

@FormUrlEncoded
@POST("login")
suspend fun login(
    @Field("username") username: String,
    @Field("password") password: String
): LoginResponse

7. @Multipart + @Part

文件上传。

@Multipart
@POST("upload")
suspend fun uploadFile(
    @Part file: MultipartBody.Part
): UploadResponse

文件构造:

val file = File("path")
val requestBody = file.asRequestBody("image/png".toMediaType())
val part = MultipartBody.Part.createFormData(
    "file",
    file.name,
    requestBody
)

8. @Header / @HeaderMap

动态添加请求头。

@GET("user/info")
suspend fun getUser(
    @Header("Authorization") token: String
): User

9. @Headers

静态请求头。

@Headers("Cache-Control: max-age=60")
@GET("news")
suspend fun getNews(): List<News>

六、OkHttp + Retrofit 结合使用

实际开发中,一般会给 Retrofit 配置 OkHttpClient。

val loggingInterceptor = HttpLoggingInterceptor().apply {
    level = HttpLoggingInterceptor.Level.BODY
}

val headerInterceptor = Interceptor { chain ->
    val oldRequest = chain.request()

    val newRequest = oldRequest.newBuilder()
        .addHeader("Authorization", "Bearer token")
        .build()

    chain.proceed(newRequest)
}

val okHttpClient = OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)
    .writeTimeout(10, TimeUnit.SECONDS)
    .addInterceptor(headerInterceptor)
    .addInterceptor(loggingInterceptor)
    .build()

val retrofit = Retrofit.Builder()
    .baseUrl("https://www.example.com/api/")
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val apiService = retrofit.create(ApiService::class.java)

七、一个简单的封装示例

object NetworkManager {

    private const val BASE_URL = "https://www.example.com/api/"

    private val okHttpClient: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .addInterceptor { chain ->
                val request = chain.request().newBuilder()
                    .addHeader("Authorization", "Bearer token")
                    .build()
                chain.proceed(request)
            }
            .build()
    }

    private val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    val apiService: ApiService by lazy {
        retrofit.create(ApiService::class.java)
    }
}

使用:

viewModelScope.launch {
    try {
        val user = NetworkManager.apiService.getUserInfo("1001")
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

八、面试常见问题

1. OkHttp 和 Retrofit 的区别?

答:

OkHttp 是底层 HTTP 客户端,负责真正的网络请求、连接管理、缓存、拦截器等。

Retrofit 是对 OkHttp 的上层封装,使用接口和注解描述请求,负责请求参数组织、结果转换、适配协程或 RxJava。

简单来说:

OkHttp 负责请求
Retrofit 负责封装接口和解析数据

2. Retrofit 底层是怎么工作的?

答:

Retrofit 使用动态代理创建接口实现类。

例如:

val api = retrofit.create(ApiService::class.java)

内部通过 Java 动态代理拦截接口方法调用,解析方法上的注解,例如 @GET@POST@Query@Body,然后构建 OkHttp 的 Request,最后交给 OkHttp 执行网络请求。

流程:

接口方法调用
     ↓
动态代理拦截
     ↓
解析注解
     ↓
构建 Request
     ↓
OkHttp 执行请求
     ↓
Converter 转换响应数据
     ↓
返回结果

3. OkHttp 拦截器有哪些类型?

答:

OkHttp 有两类拦截器:

应用拦截器

addInterceptor()

特点:

  • 不关心重定向、重试等中间过程
  • 通常用于添加公共 Header、日志、统一参数、Token
  • 只会调用一次

网络拦截器

addNetworkInterceptor()

特点:

  • 能观察真实网络请求
  • 可以看到重定向、重试过程
  • 可访问网络层数据
  • 更适合处理网络缓存、底层网络信息

4. OkHttp 的拦截器执行顺序?

答:

按照添加顺序执行。

例如:

OkHttpClient.Builder()
    .addInterceptor(A)
    .addInterceptor(B)
    .addInterceptor(C)

执行顺序大致是:

A request
  B request
    C request
      网络请求
    C response
  B response
A response

类似洋葱模型。


5. OkHttp 内部有哪些核心拦截器?

答:

OkHttp 内部请求链中有几个核心拦截器:

RetryAndFollowUpInterceptor
BridgeInterceptor
CacheInterceptor
ConnectInterceptor
CallServerInterceptor

作用:

拦截器 作用
RetryAndFollowUpInterceptor 失败重试、重定向
BridgeInterceptor 添加必要请求头,处理 Cookie、gzip
CacheInterceptor 处理缓存
ConnectInterceptor 建立连接
CallServerInterceptor 真正读写网络数据

6. OkHttp 如何实现连接复用?

答:

OkHttp 内部通过连接池 ConnectionPool 管理连接。

默认情况下:

最多保留 5 个空闲连接
空闲连接最多存活 5 分钟

如果同一个主机、端口、协议相同,并且连接仍然可用,就可以复用已有连接,减少 TCP 握手和 TLS 握手开销。


7. OkHttp 支持哪些缓存?

答:

OkHttp 支持 HTTP 缓存,依赖响应头:

Cache-Control
Expires
ETag
Last-Modified

配置缓存:

val cache = Cache(File(context.cacheDir, "http_cache"), 10L * 1024 * 1024)

val client = OkHttpClient.Builder()
    .cache(cache)
    .build()

8. Retrofit 的 baseUrl 有什么要求?

答:

baseUrl 必须以 / 结尾。

正确:

.baseUrl("https://api.example.com/")

错误:

.baseUrl("https://api.example.com")

如果接口注解中的 URL 以 / 开头,会覆盖 baseUrl 的 path 部分。

例如:

baseUrl = "https://api.example.com/v1/"
@GET("/user")

最终是:

https://api.example.com/user

而不是:

https://api.example.com/v1/user

9. @Query 和 @Path 的区别?

答:

@Query 是拼接在 URL 后面的查询参数。

@GET("user")
suspend fun getUser(@Query("id") id: String)

结果:

/user?id=1001

@Path 是替换 URL 路径中的占位符。

@GET("user/{id}")
suspend fun getUser(@Path("id") id: String)

结果:

/user/1001

10. @Field 和 @Body 的区别?

答:

@Field 用于表单请求,必须配合 @FormUrlEncoded 使用。

@FormUrlEncoded
@POST("login")
suspend fun login(
    @Field("username") username: String,
    @Field("password") password: String
)

请求类型:

application/x-www-form-urlencoded

@Body 用于提交完整请求体,常用于 JSON。

@POST("login")
suspend fun login(@Body request: LoginRequest)

请求类型通常是:

application/json

11. Retrofit 如何上传文件?

答:

使用 @Multipart@Part

@Multipart
@POST("upload")
suspend fun upload(
    @Part file: MultipartBody.Part
): UploadResponse

构造文件参数:

val file = File(path)
val body = file.asRequestBody("image/jpeg".toMediaType())
val part = MultipartBody.Part.createFormData("file", file.name, body)

apiService.upload(part)

12. Retrofit 如何下载文件?

答:

可以返回 ResponseBody,并使用 @Streaming 避免一次性加载到内存。

@Streaming
@GET
suspend fun downloadFile(
    @Url url: String
): ResponseBody

保存文件:

val responseBody = apiService.downloadFile(url)
val inputStream = responseBody.byteStream()

@Streaming 很重要,否则大文件可能造成 OOM。


13. response.body.string() 为什么只能调用一次?

答:

因为 ResponseBody 底层是输入流,string() 会读取并关闭流。

第一次读取后,流已经被消费,第二次再读就没有数据了。

如果要多次使用,应先保存为变量:

val result = response.body?.string()

14. OkHttp 同步请求和异步请求区别?

答:

同步请求:

val response = client.newCall(request).execute()

特点:

  • 阻塞当前线程
  • 不能在主线程执行
  • 需要自己切线程

异步请求:

client.newCall(request).enqueue(callback)

特点:

  • 不阻塞当前线程
  • 回调在子线程
  • 更新 UI 需要切回主线程

15. Retrofit 的 suspend 方法回调在哪个线程?

答:

使用 suspend 时,Retrofit 会挂起协程,不会阻塞线程。

网络请求实际在 OkHttp 线程池中执行。

恢复后的线程由当前协程的 Dispatcher 决定。

例如:

viewModelScope.launch {
    val result = api.getData()
}

这里默认在主线程恢复,所以可以直接更新 LiveData 或 StateFlow。


16. 如何统一添加 Token?

答:

可以使用 OkHttp 拦截器。

val tokenInterceptor = Interceptor { chain ->
    val token = "your_token"

    val request = chain.request().newBuilder()
        .addHeader("Authorization", "Bearer $token")
        .build()

    chain.proceed(request)
}

配置:

val client = OkHttpClient.Builder()
    .addInterceptor(tokenInterceptor)
    .build()

17. Token 过期如何自动刷新?

答:

常见方案有两种:

方案一:Interceptor

在拦截器中判断响应码是否为 401,如果是则刷新 Token,然后重新请求。

方案二:Authenticator

OkHttp 提供 Authenticator 专门处理认证失败。

val client = OkHttpClient.Builder()
    .authenticator { route, response ->
        val newToken = refreshToken()

        response.request.newBuilder()
            .header("Authorization", "Bearer $newToken")
            .build()
    }
    .build()

面试中推荐回答:

Token 过期更推荐使用 Authenticator,因为它是 OkHttp 专门为认证重试设计的机制。

需要注意避免无限重试。


18. 如何避免 Token 刷新时多个请求同时刷新?

答:

可以使用锁或同步机制,保证同一时间只有一个请求刷新 Token。

常见做法:

synchronized(this) {
    // 判断当前 token 是否已经被其他请求刷新
    // 如果已刷新,直接使用新 token
    // 如果未刷新,再执行刷新 token 请求
}

核心思想:

多个请求同时 401,只允许第一个请求刷新 Token,其他请求等待并复用新 Token。

19. Retrofit 如何统一处理返回结果?

假设后端格式:

{
  "code": 0,
  "message": "success",
  "data": {}
}

定义:

data class ApiResponse<T>(
    val code: Int,
    val message: String,
    val data: T?
)

接口:

@GET("user/info")
suspend fun getUserInfo(): ApiResponse<User>

业务层处理:

val response = api.getUserInfo()
if (response.code == 0) {
    val data = response.data
} else {
    throw RuntimeException(response.message)
}

也可以使用自定义 CallAdapter 或 Repository 层统一封装。


20. Retrofit 中 ConverterFactory 和 CallAdapterFactory 分别是什么?

答:

ConverterFactory

负责数据转换。

比如:

.addConverterFactory(GsonConverterFactory.create())

作用:

JSON <-> Kotlin/Java 对象

常见 Converter:

GsonConverterFactory
MoshiConverterFactory
ScalarsConverterFactory

CallAdapterFactory

负责返回值适配。

比如:

Call<T>
RxJava Observable<T>
Coroutine suspend
LiveData<T>

以前使用 RxJava 时常见:

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

九、面试高频简答总结

OkHttp 高频点

  1. OkHttp 是什么?
  2. OkHttp 和 Retrofit 的区别?
  3. OkHttp 拦截器原理?
  4. 应用拦截器和网络拦截器区别?
  5. OkHttp 内部拦截器有哪些?
  6. OkHttp 如何实现缓存?
  7. OkHttp 如何实现连接复用?
  8. response.body.string() 为什么只能调用一次?
  9. 同步请求和异步请求区别?
  10. 如何统一添加 Header?
  11. 如何处理 Token 过期?
  12. OkHttp 是否支持 WebSocket?
  13. OkHttp 超时有哪些?
  14. OkHttp 如何取消请求?

Retrofit 高频点

  1. Retrofit 是什么?
  2. Retrofit 底层原理?
  3. Retrofit 为什么要用动态代理?
  4. Retrofit 常见注解有哪些?
  5. @Query、@Path、@Field、@Body 区别?
  6. baseUrl 为什么必须以 / 结尾?
  7. Retrofit 如何上传文件?
  8. Retrofit 如何下载大文件?
  9. ConverterFactory 作用?
  10. CallAdapterFactory 作用?
  11. Retrofit 如何结合协程?
  12. Retrofit 如何统一处理错误?
  13. Retrofit 如何配置 OkHttpClient?
  14. Retrofit 如何添加公共参数?
  15. Retrofit 如何处理多个 BaseUrl?

十、简短面试回答模板

如果面试官问:

“你项目中是怎么使用 Retrofit + OkHttp 的?”

可以这样回答:

我们项目中使用 Retrofit 负责接口声明和数据解析,OkHttp 负责底层网络请求。
Retrofit 中通过注解定义 GET、POST、Body、Query 等请求参数,通过 GsonConverterFactory 将 JSON 转成实体类。
OkHttpClient 中配置了连接超时、读写超时、日志拦截器、Header 拦截器和 Token 认证处理。
对于 Token 过期,一般通过 Authenticator 或 Interceptor 监听 401,刷新 Token 后重新发起请求。
网络结果会在 Repository 层统一封装,转换成业务层使用的 Result 或 Resource 类型。


“Retrofit 的原理是什么?”

可以这样回答:

Retrofit 使用动态代理创建接口实现类。当调用接口方法时,动态代理会拦截方法调用,解析方法上的注解和参数注解,生成对应的 HTTP 请求,然后交给 OkHttp 执行。拿到响应后,再通过 ConverterFactory 将 JSON 转换成对应的数据对象,最后通过 CallAdapterFactory 适配成 Call、协程或者 RxJava 类型返回。


“OkHttp 拦截器原理是什么?”

可以这样回答:

OkHttp 的拦截器采用责任链模式。每个拦截器都可以在请求发送前修改 Request,也可以在响应返回后修改 Response。调用 chain.proceed(request) 后会进入下一个拦截器,最终由 CallServerInterceptor 真正执行网络请求。整个过程类似洋葱模型。


十一、建议掌握的源码重点

如果面试偏源码,可以重点看:

Retrofit

Retrofit.create()
ServiceMethod
RequestFactory
HttpServiceMethod
Converter.Factory
CallAdapter.Factory

OkHttp

OkHttpClient
RealCall
Dispatcher
Interceptor
RealInterceptorChain
ConnectionPool
Exchange
RetryAndFollowUpInterceptor
BridgeInterceptor
CacheInterceptor
ConnectInterceptor
CallServerInterceptor

总结:

OkHttp:底层网络请求框架,重点是拦截器、连接池、缓存、调度器。
Retrofit:上层接口封装框架,重点是动态代理、注解解析、Converter、CallAdapter。
实际项目:Retrofit + OkHttp + 协程 + Repository 是主流组合。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇