OkHttp 是实际执行网络请求、处理连接和响应的“发动机”。其设计精髓在于其灵活的拦截器机制和高效的连接管理。
组件 | 作用 |
---|---|
OkHttpClient |
配置中心与客户端入口。它是线程安全的,用于配置和创建所有请求。应全局单例使用,因为它管理着内部的连接池和线程池,重复创建会浪费资源。 |
Request |
HTTP 请求的抽象。包含了 URL、请求方法 (GET/POST)、请求头 (Headers)、请求体 (Body) 等所有请求信息。这是一个不可变对象。 |
Response |
HTTP 响应的抽象。包含了状态码、响应头、响应体等信息。 |
Call |
一个待执行的请求的抽象。它代表了一个准备好被执行的 Request ,可以被同步 (execute() ) 或异步 (enqueue() ) 执行。每个 Request 都会被包装成一个 RealCall 。 |
Dispatcher |
请求调度器。内部管理着一个线程池,负责执行异步请求 (enqueue ),并维护了等待执行和正在执行的请求队列。通过 maxRequests 和 maxRequestsPerHost 限制最大并发请求数和每个域名最大并发请求数,避免资源过度竞争。 |
ConnectionPool |
连接池。负责复用底层的 TCP 和 HTTP/2 连接,极大地减少了重复建立连接的延迟和开销,提升了性能。 |
拦截器是 OkHttp 最强大的机制,它通过责任链 (Chain of Responsibility) 模式将一个 HTTP 请求的完整生命周期分解为一系列可插拔的处理单元。每个拦截器都能对请求(Request
)和响应(Response
)进行检查、修改、增强,甚至短路处理。
OkHttp 主要有两类拦截器:
应用拦截器 (Application Interceptors)
OkHttpClient.Builder.addInterceptor(interceptor)
网络拦截器 (Network Interceptors)
OkHttpClient.Builder.addNetworkInterceptor(interceptor)
请求流程示意:
应用拦截器
-> OkHttp 核心拦截器 (重试、缓存、桥接)
-> 网络拦截器
-> OkHttp 核心拦截器 (连接)
-> 服务器通信
示例:一个添加通用请求头的应用拦截器
class AuthInterceptor(private val authToken: String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
// 1. 获取原始请求
val originalRequest = chain.request()
// 2. 构建新的请求,添加 Authorization 头部
val newRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer $authToken")
.method(originalRequest.method, originalRequest.body)
.build()
// 3. 将新请求交由责任链中的下一个拦截器处理
return chain.proceed(newRequest)
}
}
// 使用时添加到 OkHttpClient
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(AuthInterceptor("YOUR_STATIC_TOKEN"))
.build()
Retrofit 的作用是让你能用一个简单的 Kotlin/Java 接口来定义和描述 RESTful API,将复杂的 HTTP 请求细节(如 URL 构建、参数序列化、线程切换)完全隐藏。
Retrofit 的魔法主要基于以下几个技术点:
动态代理 (Dynamic Proxy)
当你创建一个 Retrofit
实例并调用 retrofit.create(ApiService::class.java)
时,Retrofit 并不会去实现你的 ApiService
接口。相反,它使用 Java 的 Proxy.newProxyInstance
在运行时为你创建了一个该接口的动态代理对象。
注解处理 (Annotation Processing)
当你调用代理对象的某个方法时(如 apiService.getUser("123")
),代理对象的 InvocationHandler
会被触发。这个处理器会:
@GET
, @POST
, @Path
, @Body
, @Header
等)。Request
对象。适配器 (Adapter) 模式
CallAdapter: 负责将 OkHttp 的 Call
对象适配成你的接口方法所声明的返回类型。例如:
suspend
函数: Retrofit 内置了对协程的支持。当你定义一个 suspend
函数时,编译器会为其添加一个 Continuation
参数。Retrofit 的 DefaultCallAdapter
会识别这种情况,并自动使用 suspendCancellableCoroutine
包装 OkHttp 的 call.enqueue()
,从而将回调式的异步调用无缝转换为挂起函数。Call<T>
: 这是默认的适配器,返回一个可同步/异步执行的 Call
对象。CompletableFuture<T>
(Java 8+): 将 Call
适配为 Java 8 的 CompletableFuture
。Observable<T>
(RxJava): 如果你添加了 retrofit2:adapter-rxjava3
依赖,RxJava3CallAdapterFactory
会被启用,它能将 Call
适配成 RxJava 的 Observable
、Single
等数据流。Converter: 负责对象的序列化和反序列化。例如:
@Body
的对象(如一个数据类 User
)序列化成 JSON 字符串(如果使用 GsonConverterFactory
或 MoshiConverterFactory
)。User
或 List<User>
)。调用接口方法
-> 动态代理拦截
-> 解析注解与参数
-> Converter 序列化请求体 (如有)
-> 构建 OkHttp Request
-> CallAdapter 包装 Call
-> 返回适配后的对象 (suspend/Observable等)
当最终执行时,这个被层层包装的请求会交由底层的 OkHttpClient
,通过其拦截器链和连接池,最终完成与服务器的通信。
直接使用 OkHttp 或 Retrofit 进行大文件(如视频、安装包)的上传和下载会面临严峻的挑战,但借助其强大的流式处理和请求定制能力,我们可以有效地解决这些问题。
核心思想是避免将整个文件一次性加载到内存,而是以数据流(Stream)的形式分块读写。
@Multipart
对于文件上传,尤其是文件和表单字段混合的场景,通常使用 multipart/form-data
格式。Retrofit 的 @Multipart
注解配合 @Part
可以轻松实现。
关键在于,将文件包装成 MultipartBody.Part
时,应使用 RequestBody.create(MediaType, File)
或其流式变体,OkHttp 会以流的方式读取文件内容,而不是一次性加载。
示例:使用 Retrofit 上传文件
interface FileUploadService {
@Multipart
@POST("upload")
suspend fun uploadFile(
@Part("description") description: RequestBody,
@Part file: MultipartBody.Part
): Response<Unit>
}
// 在调用处创建 MultipartBody.Part
val file = File("path/to/your/large/file.zip")
val requestFile = file.asRequestBody("application/octet-stream".toMediaTypeOrNull())
val body = MultipartBody.Part.createFormData("file", file.name, requestFile)
// 创建描述文本
val description = "This is a large file".toRequestBody("text/plain".toMediaTypeOrNull())
// 发起调用
// retrofitService.uploadFile(description, body)
@Streaming
对于文件下载,Retrofit 提供了 @Streaming
注解。
Response<ResponseBody>
中的 body()
将提供一个可以实时读取数据的输入流 (ResponseBody.byteStream()
)。示例:使用 Retrofit 下载文件
interface FileDownloadService {
@Streaming // 关键注解
@GET
suspend fun downloadFile(@Url fileUrl: String): Response<ResponseBody>
}
// 在调用处处理数据流
suspend fun saveFile(responseBody: ResponseBody, destination: File) {
withContext(Dispatchers.IO) {
responseBody.byteStream().use { inputStream ->
destination.outputStream().use { outputStream ->
val buffer = ByteArray(8 * 1024) // 8K 缓冲区
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
}
}
}
}
断点续传是处理超大文件和不稳定网络环境的必备功能,其核心是能够从上次中断的位置继续传输。
Range: bytes=start-
来告知服务器:“请从第 start
个字节开始向我发送数据,直到文件结束。”服务器如果支持该功能,会返回 206 Partial Content
状态码以及相应的数据。RandomAccessFile
java.io.RandomAccessFile
类是实现断点下载的利器。
OutputStream
那样只能顺序写入。downloadedBytes
),然后在 HTTP 请求头中设置 Range: bytes=downloadedBytes-
。获取到输入流后,使用 RandomAccessFile.seek(downloadedBytes)
将文件写入指针移动到末尾,然后继续将新下载的数据块写入文件。断点下载伪代码流程:
suspend fun downloadWithBreakpoint(url: String, localFile: File) {
val downloadedBytes = if (localFile.exists()) localFile.length() else 0L
// 1. 设置 Range 头部
val response = fileService.downloadFileWithRange(url, "bytes=$downloadedBytes-")
if (response.isSuccessful && response.body() != null) {
// 2. 使用 RandomAccessFile
RandomAccessFile(localFile, "rw").use { randomAccessFile ->
// 3. 移动文件指针到断点位置
randomAccessFile.seek(downloadedBytes)
// 4. 从流中读取并写入文件 (同 @Streaming 示例)
response.body()!!.byteStream().use { input ->
// ... (省略分块读写逻辑)
}
}
}
}