OkHttp是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,Retrofit + OkHttp实现网络请求似乎成了一种标配。因此它也是每一个 Android 开发工程师的必备技能,了解其内部实现原理可以更好地进行功能扩展、封装以及优化。
OkHttp的高效性体现在:
- HTTP/2 允许对同一主机的所有请求共享一个套接字
- 连接池可以降低请求延迟
- GZIP压缩帮助开发者减小下载内容的体积
- 响应缓存避免重复的网络请求
OkHttp简单使用
第一步:创建OkHttpClient,创建OkHttpClient有两种方式:
- 使用
OkHttpClient()
创建(使用默认配置) - 使用
OkHttpClient.Builder()
构建(自定义配置信息)
val okHttpClient = OkHttpClient()
//OR
val okHttpClient = OkHttpClient.Builder().build();
OkHttpClient提供了丰富的配置方法,例如添加拦截器、指定连接池、设置请求超时等等。
第二步:创建请求
使用Request.Builder() 构建Request实例
val request: Request = Request.Builder()
.addHeader("token", "abcd")
.get()
.url("https://publicobject.com/helloworld.txt")
.build()
第三步:发起网络请求
OkHttp支持同步和异步两种请求方式
- 同步请求
executorService.execute {
try {
val response = okHttpClient.newCall(request).execute() //同步
Log.d("response:", response.body!!.string())
} catch (e: IOException) {
e.printStackTrace()
}
}
- execute()方法需要运行在try-catch块中
- 网络请求是耗时操作,不能直接在主线程发起网络请求
- 异步请求
okHttpClient.newCall(request).enqueue(object : Callback {
//异步
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
Log.d("response:", response.body!!.string())
}
})
OkHttp的使用方法非常简单,三步操作就可以发起一个简单的同步或异步请求。我们也可以很轻松地对网络请求进行配置,例如添加请求头、设置请求方式、设置请求超时等等,这些配置参数会在源码分析过程中详细介绍。
源码分析
现在我们已经学会了三步操作发起网络请求,接下来以这三个步骤为切入点,深入到源码中学习OkHttp的实现原理,废话少说马上开车。
OkHttpClient
OkHttpClient创建方式有两种,我们看看两种方式有什么区别。
第一种直接使用默认构造函数,内部依然是使用建造者模式
constructor() : this(Builder()) //this方法参数是默认的Builder
第二种使用建造者模式
fun build(): OkHttpClient = OkHttpClient(this)
两种方式最终都是调用构造函数OkHttpClient(builder:Builder),由参数builder负责所有的参数配置工作。
//构造函数OkHttpClient(builder:Builder)
open class OkHttpClient internal constructor(
builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher
@get:JvmName("connectionPool") val connectionPool: ConnectionPool = builder.connectionPool
/**
* Returns an immutable list of interceptors that observe the full span of each call: from before
* the connection is established (if any) until after the response source is selected (either the
* origin server, cache, or both).
*/
@get:JvmName("interceptors") val interceptors: List =
builder.interceptors.toImmutableList()
……
constructor() : this(Builder()) //OkHttpClient 默认构造函数
class Builder constructor() { //Builder默认构造函数
internal var dispatcher: Dispatcher = Dispatcher()
internal var connectionPool: ConnectionPool = ConnectionPool()
internal val interceptors: MutableList = mutableListOf()
……
internal constructor(okHttpClient: OkHttpClient) : this() {
this.dispatcher = okHttpClient.dispatcher
this.connectionPool = okHttpClient.connectionPool
this.interceptors += okHttpClient.interceptors
……
}
fun build(): OkHttpClient = OkHttpClient(this)
}
}
当您创建单个OkHttpClient实例并将其用于所有 HTTP 调用时,OkHttp 性能最佳。 这是因为每个OkHttpClient都拥有自己的连接池和线程池,重用连接和线程可减少延迟并节省内存。 相反,为每个请求创建一个客户端会浪费空闲池上的资源。
Request
Request同样使用建造者模式来创建,这里贴上部分重要源码,很简单就不细说了。
//部分代码
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
internal var method: String
internal var headers: Headers.Builder
internal var body: RequestBody? = null
/** A mutable map of tags, or an immutable empty map if we don't have any. */
internal var tags: MutableMap, Any> = mutableMapOf()
constructor() {
this.method = "GET"
this.headers = Headers.Builder()
}
internal constructor(request: Request) {
this.url = request.url
this.method = request.method
this.body = request.body
this.tags = if (request.tags.isEmpty()) {
mutableMapOf()
} else {
request.tags.toMutableMap()
}
this.headers = request.headers.newBuilder()
}
open fun url(url: HttpUrl): Builder = apply {
this.url = url
}
open fun addHeader(name: String, value: String) = apply {
headers.add(name, value)
}
open fun get() = method("GET", null)
open fun head() = method("HEAD", null)
open fun post(body: RequestBody) = method("POST", body)
open fun method(method: String, body: RequestBody?): Builder = apply {
require(method.isNotEmpty()) {
"method.isEmpty() == true"
}
if (body == null) {
require(!HttpMethod.requiresRequestBody(method)) {
"method $method must have a request body."
}
} else {
require(HttpMethod.permitsRequestBody(method)) {
"method $method must not have a request body."
}
}
this.method = method
this.body = body
}
open fun build(): Request {
return Request(
checkNotNull(url) { "url == null" },
method,
headers.build(),
body,
tags.toImmutableMap()
)
}
}
}
发起网络请求
OkHttp发起网络请求分为同步请求和异步请求两种方式,我们只分析异步请求流程,因为只要理解了异步请求过程,基本上也就明白同步请求是怎么一回事了。
//异步请求
okHttpClient.newCall(request) //1.创建RealCall
.enqueue(object : Callback { //2.发起异步请求,通过接口回调请求结果
//异步
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
@Throws(IOException::class)
override fun onResponse(call: Call, response: Response) {
Log.d("response:", response.body!!.string())
}
})
- 首先在okHttpClient中根据request新建一个RealCall对象,这个Call才是网络请求的真正发起者。
/** Prepares the [request] to be executed at some point in the future. */
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
RealCall是连接应用层与网络层的桥梁,负责处理连接、请求、响应和数据流。
- RealCall发起异步请求,通过接口回调这个请求的结果。
override fun enqueue(responseCallback: Callback) {
//首先检查请求任务是否已经执行
check(executed.compareAndSet(false, true)) { "Already Executed" }
//请求开始前可以执行一些操作
callStart()
//分发器执行异步任务
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
Dispatcher维护着一套异步任务执行策略,分析策略之前先介绍几个重要概念:
@get:Synchronized var maxRequests = 64 //并发执行的最大请求数
@get:Synchronized var maxRequestsPerHost = 5 //单个主机并发执行的最大请求数
@get:JvmName("executorService") val executorService: ExecutorService //异步请求的线程池
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
}
return executorServiceOrNull!!
}
/** Ready async calls in the order they'll be run. */
//等待执行的异步请求队列
private val readyAsyncCalls = ArrayDeque()
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
//正在执行的异步请求队列,包括被取消的请求
private val runningAsyncCalls = ArrayDeque()
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
//正在执行的同步请求队列,包括被取消的请求
private val runningSyncCalls = ArrayDeque()
client.dispatcher.enqueue(AsyncCall(responseCallback))
执行步骤为:
- 异步请求包装成AsyncCall,AsyncCall是实现Runnable接口的异步任务
- 将异步任务交提交给dispatcher执行
//Dispatcher.java
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call) //1
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.call.forWebSocket) {
val existingCall = findExistingCallWithHost(call.host)
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute() //2
}
- 将任务添加到等待执行的异步请求队列readyAsyncCalls
- 执行promoteAndExecute()方法
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator() //1
while (i.hasNext()) {
val asyncCall = i.next()
//2
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
//3
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) { //4
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
- 遍历等待执行的异步请求队列,开始处理异步请求任务
- 如果正在执行的异步请求任务数 >= maxRequests,跳出while循环;如果每个主机上的异步请求任务数量 >= maxRequestsPerHost,进入下一次循环
- 把当前任务从readyAsyncCalls移除并且加入到队列executableCalls和runningAsyncCalls
- 遍历executableCalls任务队列,丢进线程池executorService执行
//AsyncCall.java
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
executorService.execute(this) //1
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
noMoreExchanges(ioException)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
client.dispatcher.finished(this) // This call is no longer running! //2
}
}
}
/** 通知分发器[AsyncCall.run]处理完毕*/
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
/** 通知分发器[Call.execute]处理完毕 */
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
private fun finished(calls: Deque, call: T) {
val idleCallback: Runnable?
synchronized(this) {
//从calls队列移除当前Call
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
//处理任务的过程中又会有新的任务加入到readyAsyncCalls,
//所以这里要重新执行promoteAndExecute()方法
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
- 将AsyncCall加入到线程池中等待执行(AsyncCall实现了Runnable接口)
- 如果任务处理失败即
success=false
,调用client.dispatcher.finished(this)
方法表示当前AsyncCall任务执行完毕
异步任务执行策略小结
1、将网络请求包装成异步任务AsyncCall,加入到等待执行队列readyAsyncCalls
2、遍历readyAsyncCalls,如果正在执行的异步请求任务数 >= maxRequests,跳出遍历循环;如果每个主机上的异步请求任务数量 >= maxRequestsPerHost,进入下一次遍历循环
3、通过第二步筛选,符合条件的任务加入到executableCalls
4、遍历executableCalls,取出任务丢进线程池中执行
AsyncCall实现了Runnable接口,因此一旦被线程池中的线程处理就会调用它的run()方法:
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
……
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
val response = getResponseWithInterceptorChain() //1
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
} else {
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
client.dispatcher.finished(this) //2
}
}
}
}
}
- 拦截器责任链处理并获取网络请求结果。这是责任链模式的具体应用,是OkHttp框架的精髓所在
- 通知分发器:当前AsyncCall已经执行完毕,看看还有没有其他任务需要执行
话休絮烦,我们开始分析拦截器责任链:
//RealCall.java
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf() //1
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
//2
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) //3
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
-
将所有拦截器添加到interceptors 列表中,各种拦截器作用见下表
- 构建拦截器责任链RealInterceptorChain
- 调用chain.proceed()开始处理责任链
//RealInterceptorChain.java
override fun proceed(request: Request): Response {
check(index < interceptors.size)
calls++
//如果责任链是处理network interceptor,则exchange != null
if (exchange != null) {
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
//调用责任链上的下一个拦截器,next就是index+1后的责任链对象
val next = copy(index = index + 1, request = request)
//当前拦截器
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
//执行当前拦截器的intercept方法
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
if (exchange != null) {
check(index + 1 >= interceptors.size || next.calls == 1) {
"network interceptor $interceptor must call proceed() exactly once"
}
}
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
责任链执行流程:首先获取当前拦截器interceptor,并且调用interceptor.intercept(next)执行拦截器操作。这里的next表示的是index+1后的责任链对象,拦截器的intercept()方法内部会调用next.proceed(request)方法再次进入到责任链,由于此时index已经加1,所以处理的是下一个拦截器。
如此循环往复,直到处理完责任链上最后一个拦截器为止。
注意除最后一个拦截器CallServerInterceptor不会调用chain.proceed(request)方法之外,其他拦截器都应该至少调用一次chain.proceed(request)方法。
为了验证上面的结论,我们进入到RetryAndFollowUpInterceptor的intercept()方法一探究竟:
//省略处理错误重试和重定向代码
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
var newExchangeFinder = true
var recoveredFailures = listOf()
while (true) {
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
response = realChain.proceed(request) //1.重新进入责任链处理下一个拦截器
newExchangeFinder = true
} catch (e: RouteException) {
// The attempt to connect via a route failed. The request will not have been sent.
continue
} catch (e: IOException) {
// An attempt to communicate with a server failed. The request may have been sent.
continue
}
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
可以看到注释1处重新进入责任链处理下一个拦截器。
有兴趣可以自行查看最后一个拦截器CallServerInterceptor源码,此处只给出本人阅读源码后得出的结论:
- CallServerInterceptor主要负责处理客户端发起的request请求数据和服务端返回的response响应数据
- CallServerInterceptor#intercept()方法中没有调用chain.proceed(request)方法
以上就是拦截器责任链的工作流程,我们再通过流程图仔细感受一下。
分析完拦截器责任链,我们继续分析AsyncCall#run()方法:
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
……
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
val response = getResponseWithInterceptorChain() //1
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
} else {
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
client.dispatcher.finished(this)
}
}
}
}
}
我们看到,如果getResponseWithInterceptorChain()方法成功获得服务端返回的数据,则调用responseCallback.onResponse(this@RealCall, response)方法完成异步回调;如果服务端数据获取失败(请求异常),则调用responseCallback.onFailure(this@RealCall, canceledException)方法完成异步回调
需要注意的是,responseCallback回调是在子线程中完成的,所以如果想把数据显示到UI上,需要切换回主线程进行UI操作。
总结
OkHttp发起网络请求全过程:
- 通过建造者模式构建OKHttpClient与Request
- OKHttpClient通过newCall发起一个新的请求
- 通过分发器维护请求队列与线程池,完成请求调配
- 通过五大默认拦截器完成请求重试,缓存处理,建立连接等一系列操作
- 得到网络请求结果
参考资料
【知识点】OkHttp 原理 8 连问