OkHttp(二)
在(一)我们粗略的介绍了一下OkHttp的使用,现在我们来慢慢探究这个框架。
首先我们看下OkHttp发送HTTP请求的大致流程图
我将其分为三个阶段(纯主观):
- 外部构建网络请求
- 网络请求调度
- 真正执行网络请求
第一个阶段,就是我们能看到最外层的代码,如OkHttpClient的构建,Request的构建等,这阶段的一些内容在(一)中有描述,这里不再赘述。这个阶段主要是根据我们实际需求,将其抽象成的网络请求对象。
第二个阶段,我们对封装网络对象,进行调度。请求分为同步请求和异步请求,这两种调度情况有所不同,该阶段也是我们这篇文章要探究的东西。
第三个阶段,就是利用责任链模式来进行真正的网络请求。(后面详细探究)
Call调度
//Call对象的构建
client.newCall(request)
//从以下可以看出,真正构建的请求对象是RealCall
interface Factory {
Call newCall(Request request);
}
...
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
在实际应用中,我们调度的是RealCall对象。其主要方法有
- 同步请求:RealCall.execute()
- 异步请求:RealCall.enqueue()
我们先来看看异步请求的步骤。
RealCall.enqueue()
@Override
//CallBack 在异步回调的时候,我们都会重写匿名内部类,用以进行回调,以便进行请求抵达(失败)后面的操作
public void enqueue(Callback responseCallback) {
//TODO 保证每个Call只能被执行一次,不能重复执行。
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//TODO 交给 dispatcher调度器 进行调度
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
在源码中,出现了Dispatch调度器,其初始化在OkHttpClient中,其作用就是将Call加入队列,并通过线程池来执行Call。
Dispatchar类的属性和方法(异步相关)
private int maxRequests = 64;
//TODO 同时请求的相同HOST的最大个数 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
//TODO 如 https://restapi.amap.com restapi.amap.com - host
private int maxRequestsPerHost = 5;
/**
* Ready async calls in the order they'll be run.
* TODO 双端队列,支持首尾两端 双向开口可进可出,方便移除
* 异步等待队列
*
*/
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**
* Running asynchronous calls. Includes canceled calls that haven't finished yet.
* TODO 正在进行的异步队列
*/
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
// 构建线程池
public synchronized ExecutorService executorService() {
if (executorService == null) {
//TODO 核心线程 最大线程 非核心线程闲置60秒回收 任务队列
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
false));
}
return executorService;
}
我们可以看到,Dispatch类规定最大同时请求数,和对同一主机的最大请求数。并构建了线程池(延迟加载),两个关于异步的队列,正在执行和即将执行的队列。
接着就是enqueue方法(kotlin)
// 将请求加入队列后,执行了promoteAndExecute()方法
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
// 往准备队列中加入call
readyAsyncCalls.add(call)
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
// 判断是否有相同的host
// 并用原子类来记录host的个数,每次使用完后会删去
if (!call.get().forWebSocket) {
val existingCall = findExistingCallWithHost(call.host())
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
// 在该方法中,主要是判断是否超过64与5,将等待队列符合条件的加入正在执行的队列中,并执行这些AsyncCall请求
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
}
// 回归了AsyncCall方法中的执行方法,
fun executeOn(executorService: ExecutorService) {
assert(!Thread.holdsLock(client.dispatcher))
var success = false
try {
// 由于本身实现了Runnable接口,所以直接执行本身
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 {
// 运行不成功时,也需要结束该请求,finish的作用主要是将Call移除队列,并重新执行promoteandexecute方法,或者开启闲置调用
if (!success) {
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
//AsyncCall方法的Run方法
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 {
// finish()方法的作用同上
client.dispatcher.finished(this)
}
}
}
以上就是异步请求的全过程,从RealCall.enqueue()开始,借助AsyncCall和Dispatch来进行整体的调度,最后到异步执行的run()来结束。在run()将结果回调。
RealCall.execute()
同步请求,在Android中,由于网络请求是一个耗时操作,所以如果用同步请求,很容易会照成主线程的阻塞等问题,所以用的较少。
不过也让我们看看这部分的源码。
override fun execute(): Response {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
transmitter.timeoutEnter()
transmitter.callStart()
try {
//依然使用Dispatcher类来进行调度
client.dispatcher.executed(this)
// 实质上的请求
return getResponseWithInterceptorChain()
} finally {
// 结束请求,效果与前面异步的类似
client.dispatcher.finished(this)
}
}
// 只将请求添加到队列之中,Dispatch类实际有三个队列,有一个是为同步请求准备的队列就是这个
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
同步请求的方法较异步请求简便很多,但是都要经过Dispatch类的一个调度。其基本的调度思路如一开始的图所示。
两者都有调用getResponseWithInterceptorChain()方法,这也是我所说的第三阶段的内容,这部分放在后一章探究。
小结
本文中,我们对OkHttp的了解从表面的使用,往前走了一步,了解了具体的同/异步请求的调度过程。我们从Call类出发,到Dispatch类终止,算是对整体的框架有了一个大体的了解。接下来,就是对OkHttp的核心-责任链模式进行探究了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。