网络底层库,基于 HTTP 协议封装的一套请求客户端。
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
OkHttpClient client = new OkHttpClient
.Builder()
.build();
Request request = new Request
.Builder()
.url("https://api.github.com/users/octocat/repos")
.build();
Call call = client.newCall(request);
// enqueue:异步,execute:同步
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
Log.d("TAG", "response: " + response.body().string());
}
});
OkHttpClient 是 OkHttp 的配置类,通过构建者模式生成。
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
...
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()// 调度器,用于调度后台发起的网络请求,有后台总请求数(64)和单主机总计数(5)的控制
internal var connectionPool: ConnectionPool = ConnectionPool()// 连接池,复用连接
internal val interceptors: MutableList = mutableListOf()// 拦截器,大多数的拦截器都应该配置到这里
internal val networkInterceptors: MutableList = mutableListOf()// 网络拦截器,直接和网络请求交互的拦截器配置到这里,如查看返回的301报文,或未解压的Response Body
internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()// 流程监听器
internal var retryOnConnectionFailure = true// 在请求失败的时候是否自动重试。注意 大多数的请求失败并不属于 OkHttp 所定义的 需要重试,这种情况只适用于 同一个域名的多个 IP 切换重试、Socket 失效重试 等情况
internal var authenticator: Authenticator = Authenticator.NONE// 用于自动重新认证,配置之后,在请求收到 401 状态码的响应时,会直接调用 authenticator,手动加入 Authorization header 之后自动重新发起请求
internal var followRedirects = true// 是否支持重定向
internal var followSslRedirects = true// 是否支持从 http到https 或https到http 的重定向
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES// 管理 Cookie 的控制器,OkHttp 提供了 Cookie 存取的判断支持(即什么时候需要存 Coookie,什么时候需要读取Cookie,但没有给出具体的实现),如果需要存取 Cookie ,需要自己实现,如保存在内存或本地缓存
internal var cache: Cache? = null// 缓存设置,默认没有,如果需要用,需要自己配置出 Cache 存储的文件位置以及存储空间上限
internal var dns: Dns = Dns.SYSTEM/// DNS
internal var proxy: Proxy? = null// 代理
internal var proxySelector: ProxySelector? = null// 代理选择器
internal var proxyAuthenticator: Authenticator = Authenticator.NONE// 代理服务器认证
internal var socketFactory: SocketFactory = SocketFactory.getDefault()// socket配置
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null// https scoket配置
internal var x509TrustManagerOrNull: X509TrustManager? = null// 证书
internal var connectionSpecs: List = DEFAULT_CONNECTION_SPECS// 应用层支持的 Socket 设置,即使用明文传输(用于HTTP),还是某个版本的 TLS (用于 HTTPS)
internal var protocols: List = DEFAULT_PROTOCOLS// 支持应用层协议,即 HTTP/1.1 HTTP/2 等
internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier// 域名校验,用于验证 HTTPS 握手过程中,下载到的证书所属者是否和自己要访问的主机名一致
internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT// 用于设置 HTTPS 握手过程中针对某个 Host 额外的 Certificate Public Key Pinner,即把网站证书链中的某一个证书公钥直接拿来提前配置到 OkHttpClient 中,做为正常的证书验证机制之外的一次额外验证
internal var certificateChainCleaner: CertificateChainCleaner? = null
internal var callTimeout = 0// 请求超时
internal var connectTimeout = 10_000// 建立 TCP 或 TLS 连接的超时时间
internal var readTimeout = 10_000// 发起请求到读到响应数据的超时时间
internal var writeTimeout = 10_000// 写入超时(发起请求并被目标服务器接收的超时时间)
internal var pingInterval = 0//
internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
internal var routeDatabase: RouteDatabase? = null
...
}
...
}
Reqeust 也采用了建造者模式
class Request internal constructor(
@get:JvmName("url") val url: HttpUrl,
@get:JvmName("method") val method: String,
@get:JvmName("headers") val headers: Headers,
@get:JvmName("body") val body: RequestBody?,
internal val tags: Map, Any>
...
open class Builder {
internal var url: HttpUrl? = null// 请求的url
internal var method: String// 请求的方法 GET/POST
internal var headers: Headers.Builder// 请求头
internal var body: RequestBody? = null// 请求内容
...
open fun build(): Request {
return Request(
checkNotNull(url) { "url == null" },
method,
headers.build(),
body,
tags.toImmutableMap()
)
}
}
...
)
client.newCall() 方法实际会返回一个 RealCall 对象,它是 Call 接口的实现。当调用 RealCall .execute() 的方法时,RealCall.getResponseWithInterceptorChain() 会被调用,它会发起网络并拿到返回的响应,装进一个 Response 对象并作为返回值返回;
RealCall.enqueue() 调用的时候,会使用 Dispatcher 的线程池来把请求放在后台线程进行,实际上调用的也是RealCall.getResponseWithInterceptorChain().
// RealCall类
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
// AsyncCall类
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
...
fun executeOn(executorService: ExecutorService) {
...
executorService.execute(this)// 利用线程池执行自身,会调用run方法
...
}
override fun run() {
...
val response = getResponseWithInterceptorChain()
responseCallback.onResponse(this@RealCall, response)
...
}
}
// Dispatcher类
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()// 直接看这行
}
// Dispatcher类
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)// 调用线程池执行
}
return isRunning
}
getResponseWithInterceptorChain() 方法所做的事,把配置好的 Interceptor 放在一个 List 中,然后作为一个参数,创建一个 RealInterceptorChain 对象,并调用 chain.proceed(request) 来发起请求和获取响应。
在 RealInterceptorChain 中,每个 interceptor 会依次调用自己的 intercept() 方法。这个方法会做三件事:
// RealCall类
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf()
interceptors += client.interceptors// 开发者自定义的拦截,进行最早的预处理工作,以及在收到 Response 做善后的处理,比如 统一的header 或 网络请求日志打印 可以在这里进行配置
interceptors += RetryAndFollowUpInterceptor(client)// 它会对连接做一些初始化工作,并且负责在请求失败时重试,以及重定向的自动后续请求。它的存在,可以让重试和重定向对于开发者是无感知的
interceptors += BridgeInterceptor(client.cookieJar)// 对 HTTP 做一些额外处理,如Content-Length 的计算和添加,gzip的支持等
interceptors += CacheInterceptor(client.cache)// 负责 Cache 的处理,如果本地有了可用的Cache,一个请求可以在没有发生实质网络交互的请求下就返回缓存结果
interceptors += ConnectInterceptor// 负责建立连接,ConnectInterceptor会创建出网络请求所需要的 TCP连接 或 TLS连接,并且会创建出对应的 HttpCodec 对象,用于编码解码 HTTP 请求
if (!forWebSocket) {
interceptors += client.networkInterceptors// 一般用于网络调试
}
interceptors += CallServerInterceptor(forWebSocket)// 实质的的请求与响应的 I/O 操作,即往 Socket 里写入请求数据,和从 Socket 里读取响应数据
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
val response = chain.proceed(originalRequest)
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
负责重试和请求重定向的一个拦截器
class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
companion object {
// 最大重试次数或重定向次数
private const val MAX_FOLLOW_UPS = 20
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
...
while (true) {
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
response = realChain.proceed(request)// 让下一个拦截器处理
newExchangeFinder = true
} catch (e: RouteException) {
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue// 继续重试
} catch (e: IOException) {
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue// 继续重试
}
...
val followUp = followUpRequest(response, exchange)// 判断是否重定向
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
}
桥接,对请求和返回的数据进行转换。
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
if (body != null) {
// 添加各种请求头
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
// 添加 gzip 压缩
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
val networkResponse = chain.proceed(requestBuilder.build())// 调用下一个拦截器
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
}
缓存数据,节省流量,提高响应数据。
class CacheInterceptor(internal val cache: Cache?) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
...
// 读取候选缓存
val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
val networkRequest = strategy.networkRequest
val cacheResponse = strategy.cacheResponse
...
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build().also {
listener.cacheHit(call, it)
}
}
...
var networkResponse: Response? = null
try {
networkResponse = chain.proceed(networkRequest)// 执行下一个拦截器
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
cacheCandidate.body?.closeQuietly()
}
}
// 接收到网络结果,如果响应304,则使用缓存
if (cacheResponse != null) {
if (networkResponse?.code == HTTP_NOT_MODIFIED) {
val response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers, networkResponse.headers))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis)
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
networkResponse.body!!.close()
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache!!.trackConditionalCacheHit()
cache.update(cacheResponse, response)
return response.also {
listener.cacheHit(call, it)
}
} else {
cacheResponse.body?.closeQuietly()
}
}
val response = networkResponse!!.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build()
if (cache != null) {// 对数据进行缓存
...
}
return response
}
}
建立连接的拦截器
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.call.initExchange(chain)// 开始连接工作
val connectedChain = realChain.copy(exchange = exchange)// 将exchange传递到下一个拦截器中,exchange用来发送http和接收响应
return connectedChain.proceed(realChain.request)// 进行下一个拦截器工作
}
}
这是链中最后一个拦截器,它负责对服务器进行网络调用
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.exchange!!
val request = realChain.request
val requestBody = request.body
val sentRequestMillis = System.currentTimeMillis()
exchange.writeRequestHeaders(request)
var invokeStartEvent = true
var responseBuilder: Response.Builder? = null
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
invokeStartEvent = false
}
if (responseBuilder == null) {
if (requestBody.isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest()
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
requestBody.writeTo(bufferedRequestBody)
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
}
} else {
exchange.noRequestBody()
if (!exchange.connection.isMultiplexed) {
exchange.noNewExchangesOnConnection()
}
}
} else {
exchange.noRequestBody()
}
if (requestBody == null || !requestBody.isDuplex()) {
exchange.finishRequest()
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
invokeStartEvent = false
}
}
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code
if (code == 100) {
// Server sent a 100-continue even though we did not request one. Try again to read the actual
// response status.
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
}
response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
code = response.code
}
exchange.responseHeadersEnd(response)
response = if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response.newBuilder()
.body(EMPTY_RESPONSE)
.build()
} else {
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
"close".equals(response.header("Connection"), ignoreCase = true)) {
exchange.noNewExchangesOnConnection()
}
if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
throw ProtocolException(
"HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
}
return response
}
}