作为一名Android开发,平时也没少接触网络请求方面的东西。一直感觉网络请求方面距离我很远又很近,很近是说在日常开发中经常需要网络请求拿取后端数据,而很远是指虽然使用次数很多,但是用的都是前辈封装好的代码,用起来固然很方便很爽,但是对于所封装的框架及知识点却知之甚少,这就导致在遇到一些网络问题的时候往往不能很快的定位和解决。正好最近需要用到这方面的知识,借此机会对OkHttp内部实现进行深入的学习。
1、OkHttp介绍
OkHttp侧重底层通讯的实现,是网络通信框架,它是基于http协议封装的一套请求客户端。支持HTTP2.0并允许对同一个主机的所有请求共享同一个套接字,若非HTTP2.0,则通过连接池来减少请求延时。请求压缩数据时默认使用Gzip。支持重连机制、缓存响应数据及GZIP减少数据流量。
OkHttp核心设计模式是拦截器责任链模式,采用责任链模式将网络请求的各个阶段封装在各个链条中,每个拦截器自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑,实现了网络请求。这样设计的好处是实现了各层的解耦,并且可以对相应的数据做其他的逻辑处理。
OkHttp适用于数据量大的重量级网络请求。
2、OkHttp简单使用
从发起一次请求开始,熟悉一下OkHttp如何使用。
//创建OkHttpClient
val okHttpClient = OkHttpClient.Builder().build()
//创建请求
//描述单次请求的参数信息等,包含域名、请求方式、请求头、请求体等一系列信息。
val request = Request.Builder().url("www.google.com").build()
//newCall方法生成请求执行对象RealCall
val realCall = okHttpClient.newCall(request)
// 同步网络请求
val response = realCall.execute()
// 异步网络请求
realCall.enqueue(object : Callback{
override fun onFailure(call: Call, e: IOException) {
...
}
override fun onResponse(call: Call, response: Response) {
...
}
})
- 首先通过Builder构造器创建一个
OkHttpClient。 - 接着通过Builder构造器创建一个
Request请求,可以给这条请求配置一些相关的参数,其中url是必不可少的。 - 接下来是核心代码逻辑,通过
OkHttpClient的newCall方法创建了一个Call对象,代表一条即将被执行的网络请求。Call 实际上是一个接口,它封装了 Request,从下面的源码可以看到,newCall方法实际上是创建了一个RealCall对象,RealCall也是 Call 的唯一一个实现类。
#OkHttpClient
// 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的execute()方法表示同步发起网络请求,异步请求则调用enqueue方法,同时需要传入callback。
3、OkHttp的核心类
上面就是OkHttp的简单使用,从上面的使用中也不难看出OkHttp的几个核心类:
- OkHttpClient
- Request和Response
- RealCall
OkHttpClient:OkHttp用于请求的执行客户端,是整个OkHttp的核心管理类,所有的内部逻辑和对象归OkHttpClient统一来管理。可通过Builder构造器生成,构造参数和类成员很多。
Request和 Response:Request是请求封装类,可通过Builder构造器生成,内部有url, header, method, body等常见的参数。Response是请求的结果,从服务器返回的信息都在里面,包含code, message, header, body等信息。这两个类的定义是完全符合Http协议所定义的请求内容和响应内容。
RealCall:负责请求的调度(即同步请求的话走当前线程发送请求,异步请求的话则使用OkHttp内部的线程池进行);同时负责构造内部逻辑责任链,并执行责任链相关的逻辑,直到获取结果。虽然OkHttpClient是整个OkHttp的核心管理类,但是真正发出请求并且组织逻辑的是RealCall类,它同时肩负了调度和责任链组织的两大重任。
RealCall类中有两个很重要的方法,execute() 和 enqueue(),一个是处理同步请求,一个是处理异步请求。两者都通过getResponseWithInterceptorChain() 方法获取到请求结果。
4、同步请求
继续回到简单使用中,可以看到调用 RealCall.execute() 发起同步请求,代码如下:
#RealCall
override fun execute(): Response {
//检查该条请求是否执行过
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()//开始计时超时
callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
private fun callStart() {
this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
eventListener.callStart(this)
}
主要做了以下工作:
- 首先判断这条请求是否已经执行过,如果是则会抛出已执行的异常(一条请求只能执行一次,重复执行可以调用Call.clone())。
- 接着调用了callStart方法,在该方法中会回调eventListener的callStart()方法。
- 接着执行
client.dispatcher().executed(this),也就是调用Dispatcher的executed()方法,并把请求(RealCall)传递给它。 - 下面一行
getResponseWithInterceptorChain()是关键,在 getResponseWithInterceptorChain 方法中真正执行了网络请求并获得Response并返回。 - 最后在 finally 中调用 Dispatcher 的
finished(RealCall)方法,它的作用下面会说到。
5、Dispatcher分发器
继续看同步请求的第三步,第三步调用了Dispatcher的execute方法,那么Dispatcher是什么?字面意思是调度器,既然是调度器,那么它的作用主要用于控制网络请求的。前面也说过网络请求分为同步请求和异步请求,同步请求比较简单,走当前线程发送请求,所以Dispatcher的execute方法内部逻辑也很简单,代码如下:
#Dispatcher
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
从代码中可以看出,请求会被 Dispatcher 添加到runningSyncCalls(同步请求队列)中。因为同步请求不需要线程池,也不存在任何限制,所以该段代码的作用仅是分发器做一下记录,后续按照加入队列的顺序同步请求。
异步请求就稍复杂一些,先来看一下Dispatcher类中与异步请求相关的变量:
#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!!
}
Dispatcher有一个构造方法可以使用自己设定的线程池。如果没有设定线程池,则会使用默认线程池。这个线程池类似于CachedThreadPool,比较适合执行大量的耗时比较少的任务。
同时设置了最大并发请求数和最大请求主机数,当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时,会把请求加载到runningAsyncCalls(异步任务队列)中并在线程池中执行,否则就加入到readyAsyncCalls(异步任务等待队列)中进行缓存等待。下面是Dispatcher维护的三个队列。
#Dispatcher
/** Ready async calls in the order they'll be run. */
//按运行顺序准备异步调用的队列
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
//正在运行的异步请求队列, 包含取消但是还未finish的AsyncCall
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
//正在运行的同步请求队列, 包含取消但是还未finish的RealCall
private val runningSyncCalls = ArrayDeque<RealCall>()
Dispatcher的作用也就显而易见了:
- 发起网络请求:execute、enqueue。
- 记录同步任务、异步任务及等待执行的异步任务,维护任务队列。
- 线程池管理异步任务。
6、异步请求
调用 RealCall.enqueue() 发起异步请求.
#RealCall
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
与同步请求类似,首先会检查请求是否执行过并调用callStart方法,接着调用Dispatcher的enqueue()方法,enqueue方法需要传递一个AsyncCall对象,AsyncCall类是RealCall的内部类,实现了Runnable接口并接收responseCallback参数,所以AsyncCall类就是一个线程执行体,具体作用先按下不表,下面会有说到。接着看Dispatcher中的enqueue方法:
#Dispatcher
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
readyAsyncCalls.add(call)
// 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()
}
private fun findExistingCallWithHost(host: String): AsyncCall? {
for (existingCall in runningAsyncCalls) {
if (existingCall.host == host) return existingCall
}
for (existingCall in readyAsyncCalls) {
if (existingCall.host == host) return existingCall
}
return null
}
主要做了以下工作:
- 首先将AsyncCall对象加入readyAsyncCalls队列中。
- 然后通过
findExistingCallWithHost方法查找在runningAsyncCalls和readyAsyncCalls是否存在相同 host(主机) 的AsyncCall,如果存在则调用AsyncCall.reuseCallsPerHostFrom()方法进行复用。 - 最后是核心,调用
promoteAndExecute()通过线程池执行队列中的AsyncCall对象。
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
//新建一个运行请求队列
val executableCalls = mutableListOf<AsyncCall>()
//用于判断是否还有请求在执行
val isRunning: Boolean
//加锁,保证线程安全
synchronized(this) {
//遍历 readyAsyncCalls 队列
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
//runningAsyncCalls的数量不能大于最大并发请求数 64
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
//同一Host的数量不能超过5
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
//从readyAsyncCalls队列中移除该Runnable并加入到executableCalls和runningAsyncCalls中
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
//遍历executableCalls 执行asyncCall的executeOn方法
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
在这里遍历readyAsyncCalls队列,判断runningAsyncCalls的数量是否大于最大并发请求数64,否的话则停止循环,判断同一 Host(主机) 的请求是否大于5,否的话则跳过该次循环。然后将AsyncCall从readyAsyncCalls队列中移除,并加入到executableCalls和runningAsyncCalls中,遍历executableCalls队列执行asyncCall的executeOn方法,而传递的参数executorService就是OkHttp创建的线程池。
internal inner class AsyncCall(
private val responseCallback: Callback
) : Runnable {
......
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
//执行AsyncCall 的run方法
executorService.execute(this)
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!
}
}
}
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
//执行OkHttp的拦截器 获取response
val response = getResponseWithInterceptorChain()
signalledCallback = true
//将response对象回调出去
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
} else {
//遇到IO异常 回调失败方法
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)
}
}
}
}
这里可以看到AsyncCall就是一个Runable对象,在executeOn方法中executorService.execute(this)通过线程池执行该RealCall,线程执行就会调用该对象的run方法。在run方法中主要执行了以下几步:
- 在
getResponseWithInterceptorChain()中真正执行了网络请求并获得Response。 - 如果没有异常则调用
responseCallback的onResponse方法将 Response 对象回调出去。 - 如果遇见IOException异常则调用responseCallback的onFailure方法将异常回调出去
- 如果遇到其他异常,调用 cancel ()方法取消请求,同时调用responseCallback的onFailure方法将异常回调出去。
- 结束后调用Dispatcher的
finished(AsyncCall)方法。
不知道大家还记不记得在同步请求中通过getResponseWithInterceptorChain()方法获取到Response后调用了Dispatcher的finished(RealCall)方法,我们来看一下两个finished的区别:
同步请求finished:
#Dispatcher
/** Used by [Call.execute] to signal completion. */
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
异步请求finished:
#Dispatcher
/** Used by [AsyncCall.run] to signal completion. */
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
我们会发现二者最终都会调用finished(runningAsyncCalls, call)方法,在该方法中会把完成的任务从队列中删除,同时执行promoteAndExecute()方法继续完成异步等待队列中的任务,代码如下所示。
#Dispatcher
private fun <T> finished(calls: Deque<T>, call: T) {
val idleCallback: Runnable?
synchronized(this) {
//将完成的任务从队列中删除
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
//再次调用promoteAndExecute()方法将等待队列中的请求移入异步队列,并交由线程池执行。
val isRunning = promoteAndExecute()
//如果没有请求需要执行,回调闲置callback
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
可能大家已经发现了,不管是同步请求还是异步请求最终都是通过getResponseWithInterceptorChain()方法获取到请求结果,所以整个请求的核心逻辑就在该方法中。
7、拦截器
先看getResponseWithInterceptorChain源码:
#RealCall
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
// 建立一个完整的拦截器堆栈。
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
//如果不是sockect 添加networkInterceptors
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
//创建拦截器责任链
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)
}
}
}
从源码可以看到,首先会创建一个拦截器的 List 列表Interceptors,按顺序依次将:
- client.Interceptors
- RetryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- client.networkInterceptors
- CallServerInterceptor
这些拦截器成员添加到 Interceptors 中,然后利用该参数创建一个RealInterceptorChain对象(拦截器链),最后通过 RealInterceptorChain.proceed(originalRequest)方法获取到请求结果(Response)。
既然最终的结果是通过RealInterceptorChain.proceed(originalRequest)方法请求到的,那我们先把拦截器放一边,看看proceed()这个方法究竟做了什么。
#RealInterceptorChain
@Throws(IOException::class)
override fun proceed(request: Request): Response {
check(index < interceptors.size)
// 记录当前拦截器调用proceed方法的次数
calls++
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"
}
}
// 将除当前要执行的拦截器以外的剩余拦截器创建新的RealInterceptorChain对象。
val next = copy(index = index + 1, request = request)
// 取出要执行的拦截器
val interceptor = interceptors[index]
//调用拦截器的intercept方法,并把新建的RealInterceptorChain对象传递进去
@Suppress("USELESS_ELVIS")
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
}
从代码中可以看到拦截器会按照添加顺序依次执行,从RealInterceptorChain.proceed()方法开始进入到第一个拦截器的逻辑,每个拦截器在执行之前,会根据剩余尚未执行的拦截器创建新的RealInterceptorChain对象(这里我们以nextChain表示该对象),然后调用当前拦截器的intercept()方法,并把新建的nextChain对象传入该方法中。为保证责任链能依次进行下去,除最后一个拦截器(CallServerInterceptor)外,其他所有拦截器intercept()方法内部都会调用一次nextChain.proceed(request)方法执行剩余拦截器逻辑。
既然每个拦截器(CallServerInterceptor除外)都是调用nextChain.proceed(request)方法来执行剩余拦截器的逻辑的,那么每个拦截器的intercept()方法逻辑就被proceed()方法的调用切分为Start、nextChain.proceed(request)、End这三个部分,所以,所有拦截器的结构就是一个层层内嵌的嵌套结构。
按照拦截器链的顺序一层一层的递推下去,最终会执行到最后一个拦截器
CallServerInterceptor的intercept()方法,此方法会将网络响应的结果封装成一个Response对象并返回。之后沿着责任链一级一级的回溯,最终返回到getResponseWithInterceptorChain方法得到请求结果。
观察拦截器源码我们不难看出,Start部分或是做重写、重试处理,或是对Request对象的处理,或是从链中获取到该拦截器所需要的参数,End部分则多是对返回的Response结果的处理。所以说拦截器是一种能够监控、重写、重试调用的机制。通常情况下,拦截器用来添加、移除、转换请求和响应的头部信息。比如将域名替换为IP地址,在请求头中添加host属性;也可以添加我们应用中的一些公共参数,比如设备id、版本号,等等。
下面就来具体说说每个拦截器的作用,先大概了解下:
- Interceptors:用户自定义拦截器,可以添加一些自定义header、通用参数、参数加密、网关接入等等。
- RetryAndFollowUpInterceptor(失败和重定向拦截器):负责失败重试和重定向。
- BridgeInterceptor(桥接拦截器):封装request和response拦截器。主要工作是为请求添加cookie、添加固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,把用户构造的Request转换为发送给服务器的Request。同时保存响应结果的cookie,如果响应使用gzip压缩过,则还需要进行解压,把服务器返回的Response转换为对用户友好的Response。
- CacheInterceptor(缓存拦截器):负责读取缓存以及更新缓存。如果命中缓存则直接返回,不会发起网络请求。
- ConnectInterceptor(连接拦截器):负责与服务器建立连接并管理连接,这里才是真正的请求网络。内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。
- networkInterceptors(网络拦截器):用户自定义网络拦截器,通常用于监控网络层的数据传输。
- CallServerInterceptor(服务器拦截器):执行流操作(写出请求体、获得响应数据),负责向服务器发送请求数据和从服务器读取响应数据,进行http请求报文的封装与请求报文的解析。
7.1 Interceptors和NetworkInterceptors的区别
在OkHttpClient.Builder的构造方法有两个参数,使用者可以通过addInterceptor和 addNetworkdInterceptor添加自定义的拦截器。
- 执行次数可能不同。从整个责任链路的顺序来看
Interceptors和networkInterceptors一个在RetryAndFollowUpInterceptor的前面,一个在后面,假如一个请求在 RetryAndFollowUpInterceptor 这个拦截器内部重试或者重定向了N次,那么其内部嵌套的所有拦截器也会被调用N次,同样 networkInterceptors 自定义的拦截器也会被调用N次。而相对的 Interceptors 则一个请求只会调用一次,所以在OkHttp的内部也将其称之为Application(应用) Interceptor。同时,CacheInterceptor也居于Interceptors和networkInterceptors二者中间,这就意味着如果在CacheInterceptor中命中缓存就不会走网络请求了,因此也存在着Interceptors执行一次,而networkInterceptors不执行的情况。 - 使用场景不同,从第一条可以看出自定义拦截器一定且只会调用一次,所以通常用于统计客户端的网络请求发起情况;而网络拦截器一次调用代表了一定会发起一次网络通信,因此通常可用于统计网络链路上传输的数据。
总结
由于精力关系,拦截器部分只是进行了大致的分析,后续有时间会继续完善。总的来说,OkHttp将整个请求的复杂逻辑切成了一个一个的独立模块并命名为拦截器(Interceptor),通过责任链的设计模式串联到了一起,最终完成请求获取响应结果。