一、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 高频点
- OkHttp 是什么?
- OkHttp 和 Retrofit 的区别?
- OkHttp 拦截器原理?
- 应用拦截器和网络拦截器区别?
- OkHttp 内部拦截器有哪些?
- OkHttp 如何实现缓存?
- OkHttp 如何实现连接复用?
- response.body.string() 为什么只能调用一次?
- 同步请求和异步请求区别?
- 如何统一添加 Header?
- 如何处理 Token 过期?
- OkHttp 是否支持 WebSocket?
- OkHttp 超时有哪些?
- OkHttp 如何取消请求?
Retrofit 高频点
- Retrofit 是什么?
- Retrofit 底层原理?
- Retrofit 为什么要用动态代理?
- Retrofit 常见注解有哪些?
- @Query、@Path、@Field、@Body 区别?
- baseUrl 为什么必须以
/结尾? - Retrofit 如何上传文件?
- Retrofit 如何下载大文件?
- ConverterFactory 作用?
- CallAdapterFactory 作用?
- Retrofit 如何结合协程?
- Retrofit 如何统一处理错误?
- Retrofit 如何配置 OkHttpClient?
- Retrofit 如何添加公共参数?
- 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 是主流组合。