一个项目稳定运行,离不开基础能力建设,也就是我们常说的基础库,常见的有网络库、图片加载库、日志库、埋点库等等,所有的项目都会依赖这些基础库,因此基础库的迭代准则就是:改动不得影响上层业务的使用,架构设计最基本的开闭原则,那么这一篇文章中,我将会从网络基础库入手,从OkHttp到Retrofit源码开始讲起。
在此之前,如果熟悉OkHttp的伙伴,大概应该知道OkHttp作为Android最热门的网络请求框架,其自身的优势在于:
(1)内部实现Socket连接池,减少了延迟请求;
我们知道,socket是应用层的协议,传输层采用的是TCP/IP协议,如果每次请求都去和服务端建立一次连接,响应完成之后断开连接,势必影响到了请求的时效性,因此OkHttp内部通过缓存socket连接池,可复用socket,减少3次握手和4次挥手的操作,以达到快速响应。
(2)使用GZip压缩数据;
使用GZip能够最大化的压缩数据,数据packet减少能够提高传输效率。
(3)缓存响应数据,避免重复的网络请求;
当客户端发起一次接口请求之后,例如xxx/api/users,此时服务端会返回对应的响应response,此时OkHttp会选择将这些响应缓存起来(具体能否缓存取决于数据本身),当下次客户端发起同样的请求之后,就不需要过服务端,直接取本地的缓存响应返还给客户端。
(4)请求失败自动重定向
当客户端一次请求失败之后,OkHttp会自动重试主机的其他ip。
上述这么多的优势,我们均可以从源码中找到端倪。
1 OkHttp的基础使用
对于网络请求,我们常用的就是get和post请求,在OkHttp中,通过Request创建两种不同的请求方式,利用OkHttpClient,可以看作是客户端发起请求(newCall),最终获取服务端的响应结果。
Kotlin
init {
client = OkHttpClient.Builder()
.build()
}
fun get(url: String): String? {
//创建GET请求
val request = Request.Builder()
.url(url)
.build()
val response = client.newCall(request).execute()
return response.body?.string()
}
fun post(url: String, body: RequestBody): String? {
//创建POST请求
val request = Request.Builder()
.url(url)
.post(body)
.build()
val response = client.newCall(request).execute()
return response.body?.string()
}
具体的请求流程为:
我们可以看到,当OkHttp进行请求时,有两种方式:execute和enqueue,分别代表同步和异步请求,在上面代码中是同步请求的实现方式,会阻塞线程直到响应返回。
当然并不是创建完请求就直接拿到结果,中间涉及到了分发器和拦截器的处理逻辑:
(1)分发器:内部维护了队列和线程池,完成请求的分发;
为什么要使用线程池,其实很简单,如果只是一个单线程的框架,那么每次请求都需要判断当前是否有任务在执行,如果有那么就需要排队,显然不符合网络请求的场景,当有多个任务来的时候,需要线程调度并发处理。
(2)拦截器:OkHttp的核心组件,默认有5大拦截器
1.1 enqueue方法分析
对于OkHttp的同步和异步方法,我们先分析异步方法,因为两者有共同之处,首先当我们创建一个请求对象之后,会调用newCall方法。
kotlin
/** Prepares the [request] to be executed at some point in the future. */
override fun newCall(request: Request): Call {
return RealCall.newRealCall(this, request, forWebSocket = false)
}
通过调用client的newCall方法,我们看是以创建的Request为参数,最终返回了一个RealCall对象。
kotlin
companion object {
fun newRealCall(
client: OkHttpClient,
originalRequest: Request,
forWebSocket: Boolean
): RealCall {
// Safely publish the Call instance to the EventListener.
return RealCall(client, originalRequest, forWebSocket).apply {
transmitter = Transmitter(client, this)
}
}
}
那么最终调用enqueue或者execute方法,都是调用了RealCall对象的方法。
kotlin
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
//这里会进行事件的回调
transmitter.callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
开始会做一次同步判断验证,当执行一次enqueue方法时,会将executed置为true,如果多次调用enqueue方法,那么就会直接抛出异常。
接下来的核心方法就是,调用了Dispatcher的enqueue方法,在创建OkHttpClient时,可以将自定义的Dispatcher对象设置进去,否则将会使用默认的Dispatcher分发器。
kotlin
// OkHttpClient.Builder 构造函数
class Builder constructor() {
internal var dispatcher: Dispatcher = Dispatcher()
}
1.1.1 Dispatcher分发器
因为作为异步方法,对于结果的返回是通过回调通知客户端,因此调用RealCall的enqueue方法时,会设置一个Callback对象。
kotlin
interface Callback {
/**
* Called when the request could not be executed due to cancellation, a connectivity problem or
* timeout. Because networks can fail during an exchange, it is possible that the remote server
* accepted the request before the failure.
*/
fun onFailure(call: Call, e: IOException)
/**
* Called when the HTTP response was successfully returned by the remote server. The callback may
* proceed to read the response body with [Response.body]. The response is still live until its
* response body is [closed][ResponseBody]. The recipient of the callback may consume the response
* body on another thread.
*
* Note that transport-layer success (receiving a HTTP response code, headers and body) does not
* necessarily indicate application-layer success: `response` may still indicate an unhappy HTTP
* response code like 404 or 500.
*/
@Throws(IOException::class)
fun onResponse(call: Call, response: Response)
}
当请求成功之后,会在onResponse方法中回调响应对象,那么在交由Dispatcher处理时,会将Callback对象封装成AsyncCall对象。
Dispatcher # enqueue
kotlin
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.get().forWebSocket) {
val existingCall = findExistingCallWithHost(call.host())
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
Dispatcher # enqueue是一个同步方法,首先会将AsyncCall对象加入到readyAsyncCalls队列中。在Dispatcher中,有3个重要的队列需要知道:
kotlin
/** 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. */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>()
readyAsyncCalls
队列是等待任务队列,存储已经准备好的任务,可以随时执行的;runningAsyncCalls
队列是正在执行的异步任务队列,同时也包括正在取消的任务;runningSyncCalls
队列是正在执行的同步任务队列。
OKHttp在4.0之后和4.0之前还是有区别的,在4.0之前(java版本)中,在执行enqueue方法时,Dispatcher是会判断将异步任务加入到ready还是running队列中。而在4.0之后(kotlin版本)中,统一都是加入到了ready队列中。
接下来会判断当前请求是否为websocket请求,如果是,那么不做任何处理;如果不是,那么会调用findExistingCallWithHost方法,
kotlin
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
}
从runningAsyncCalls
和readyAsyncCalls
两个队列中,查找是否存在相同域名的请求,如果存在,那么就返回,同时修改当前AsyncCall对象的callsPerHost参数。
kotlin
fun callsPerHost(): AtomicInteger = callsPerHost
fun reuseCallsPerHostFrom(other: AsyncCall) {
this.callsPerHost = other.callsPerHost
}
为什么要统计这个数字,是因为OKHttp对于请求是有限制,在Dispatcher中有两个参数分别为:maxRequests
和maxRequestsPerHost
,代表最大请求数和同一域名下的最大请求数,超出这个数字将不会继续工作。
kotlin
/**
* The maximum number of requests to execute concurrently. Above this requests queue in memory,
* waiting for the running calls to complete.
*
* If more than [maxRequests] requests are in flight when this is invoked, those requests will
* remain in flight.
*/
@get:Synchronized var maxRequests = 64
set(maxRequests) {
require(maxRequests >= 1) { "max < 1: $maxRequests" }
synchronized(this) {
field = maxRequests
}
promoteAndExecute()
}
/**
* The maximum number of requests for each host to execute concurrently. This limits requests by
* the URL's host name. Note that concurrent requests to a single IP address may still exceed this
* limit: multiple hostnames may share an IP address or be routed through the same HTTP proxy.
*
* If more than [maxRequestsPerHost] requests are in flight when this is invoked, those requests
* will remain in flight.
*
* WebSocket connections to hosts **do not** count against this limit.
*/
@get:Synchronized var maxRequestsPerHost = 5
set(maxRequestsPerHost) {
require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
synchronized(this) {
field = maxRequestsPerHost
}
promoteAndExecute()
}
当碰到这两个参数时,我们再做解释,接下来会调用promoteAndExecute方法。
Dispatcher # promoteAndExecute
kotlin
/**
* Promotes eligible calls from [readyAsyncCalls] to [runningAsyncCalls] and runs them on the
* executor service. Must not be called with synchronization because executing calls can call
* into user code.
*
* @return true if the dispatcher is currently running calls.
*/
private fun promoteAndExecute(): Boolean {
assert(!Thread.holdsLock(this))
val executableCalls = mutableListOf<AsyncCall>()
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
}
在这个方法中,会从readyAsyncCalls
队列中,选择符合条件的异步任务放入runningAsyncCalls
队列中,以便在线程池中执行。
所以在这个方法中,会遍历readyAsyncCalls
队列:
(1)如果runningAsyncCalls
队列的size已经大于最大请求数,那么就直接跳出遍历;
(2)如果当前请求已经超过了同一域名下最大的请求数,那么就跳过遍历下一个任务;
所以只要都不满足上述两个条件,那么就会将此任务:
(1)从readyAsyncCalls
队列中移除;
(2)将此任务的callsPerHost参数加1,便于为后边的任务提供maxRequestsPerHost条件判断;
(3)将此任务加入到runningAsyncCalls
队列中。
选出符合条件的任务之后,执行每个任务的executeOn方法,这里会进入到线程池中。
这里注意一下,promoteAndExecute方法是有返回值的,isRunning是通过判断runningCallsCount是否大于0,即runningAsyncCalls
和runningSyncCalls
两个队列的任务数是否大于0.
kotlin
@Synchronized fun runningCallsCount(): Int = runningAsyncCalls.size + runningSyncCalls.size
1.1.2 AsyncCall的执行
通过Dispatcher挑选出符合条件的AsyncCall之后,会调用executeOn方法,这里会传入创建的线程池,这个线程池有什么特点呢?
kotlin
@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
if (executorServiceOrNull == null) {
executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
SynchronousQueue(), threadFactory("OkHttp Dispatcher", false))
}
return executorServiceOrNull!!
}
核心线程数为0,最大线程数无限大,这也意味着可以无限创建线程,支持高并发保证最大的吞吐量;其次创建线程池需要队列,SynchronousQueue也是BlokingQueue,但是为什么不选择LinkedBlockingQueue或者ArrayBlockingQueue,是因为SynchronousQueue会比另外两种有更高的吞吐量。
在executeOn方法中,就会直接执行AsyncCall自身对象,那么说明其实现了Runnable接口,如果执行成功了,那么就将success设置为true。
kotlin
fun executeOn(executorService: ExecutorService) {
assert(!Thread.holdsLock(client.dispatcher))
var success = false
try {
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
transmitter.noMoreExchanges(ioException)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
AsyncCall # run
kotlin
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
transmitter.timeoutEnter()
try {
val response = getResponseWithInterceptorChain()
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for ${toLoggableString()}", e)
} else {
responseCallback.onFailure(this@RealCall, e)
}
} finally {
client.dispatcher.finished(this)
}
}
}
那么看到这里,整个请求的基本流程算是接近尾声了,因为在调用getResponseWithInterceptorChain方法之后,返回了一个Response对象,并通过Callback回调onResponse方法将请求返回客户端;如果失败,那么就调用onFailure方法。
最终请求完成之后,调用了Dispatcher的finished方法。
Dispatcher # finished
kotlin
internal fun finished(call: AsyncCall) {
call.callsPerHost().decrementAndGet()
finished(runningAsyncCalls, call)
}
/** Used by `Call#execute` to signal completion. */
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
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
}
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
其实finished方法主要就是做一些状态同步:
(1)对于当前任务的callsPerHost
参数减1;
(2)从runningSyncCalls
队列中移除这个任务;
(3)有旧的任务移除,意味着等待队列中的任务有机会被执行,所以又再次调用了promoteAndExecute方法,进行新的一轮任务筛选。
1.1.3 拦截器分析
前面我们讲到,AsyncCall任务执行时,会通过调用getResponseWithInterceptorChain方法才能拿到响应结果,这里就涉及到了OkHttp 5大默认拦截器。
RealCall # getResponseWithInterceptorChain
kotlin
@Throws(IOException::class)
fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
var calledNoMoreExchanges = false
try {
val response = chain.proceed(originalRequest)
if (transmitter.isCanceled) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw transmitter.noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null)
}
}
}
在 getResponseWithInterceptorChain方法中,会先创建一个集合存储拦截器,其中会先把我们自定义的拦截器添加进来,然后添加RetryAndFollowUpInterceptor
、BridgeInterceptor
、CacheInterceptor
、ConnectInterceptor
,如果不是websocket,也会把与网络相关的自定义拦截器添加进来,最后添加了CallServerInterceptor
.
然后创建了RealInterceptorChain
对象,具体构造函数如下:
kotlin
class RealInterceptorChain(
private val interceptors: List<Interceptor>,
private val transmitter: Transmitter,
private val exchange: Exchange?,
private val index: Int,
private val request: Request,
private val call: Call,
private val connectTimeout: Int,
private val readTimeout: Int,
private val writeTimeout: Int
)
这里会把所有的拦截器interceptors
,以及拦截器在集合中的位置index
、我们创建的请求request
当做参数传递,然后调用其proceed方法进行解析。
RealInterceptorChain # proceed
kotlin
override fun proceed(request: Request): Response {
return proceed(request, transmitter, exchange)
}
@Throws(IOException::class)
fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
if (index >= interceptors.size) throw AssertionError()
calls++
// If we already have a stream, confirm that the incoming request will use it.
check(this.exchange == null || this.exchange.connection()!!.supportsUrl(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
check(this.exchange == null || calls <= 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
// Call the next interceptor in the chain.
val next = RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
val interceptor = interceptors[index]
@Suppress("USELESS_ELVIS")
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
// Confirm that the next interceptor made its required call to chain.proceed().
check(exchange == null || 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
}
这里其实用到了一个设计模式 - "责任链设计模式",在之前设计模式中,我介绍过责任链设计模式,有兴趣的伙伴可以走直通车看一下:
这里会看到执行proceed方法时,就会先创建一个RealInterceptorChain
对象,注意此时index变为index + 1
,也就是下一个拦截器的位置,然后根据index取出对应的拦截器并执行intercept方法,同时把下一个RealInterceptorChain
对象传递进去。
kotlin
class CustomInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
return chain.proceed(chain.request())
}
}
那么此时在当前拦截器执行Interceptor.Chain
的proceed方法时,其实已经是下一个拦截器的执行逻辑了,所以如果拦截器需要上下文的关系数据,那么排列顺序上就需要注意了,不能随便排列。
最终经过拦截器的层层处理,拿到了服务端的响应数据,系统的拦截器就不一一介绍了,我们关注原理即可。
1.2 execute方法分析
同步方法相较于异步方法就比较简单了
kotlin
override fun execute(): Response {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
transmitter.timeoutEnter()
transmitter.callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
因为是同步任务,所以没有并发相关的限制,都是直接放进runningSyncCalls
同步队列中,直接执行getResponseWithInterceptorChain方法
Dispatcher # executed
kotlin
/** Used by `Call#execute` to signal it is in-flight. */
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
任务执行完成,直接从同步任务中移除。
kotlin
/** Used by `Call#execute` to signal completion. */
internal fun finished(call: RealCall) {
finished(runningSyncCalls, call)
}
其实OkHttp作为现在主流的网络框架,最大的优势在于其分发器和拦截器,支持高并发,并支持同步异步请求,本文也只是介绍了"两大法宝"的基本原理,后续会对核心的拦截器做详细的分析。