在读这篇博客之前,如果想了解okhttp更多原理,可移步博主的okhttp分类博客。用过okhttp的应该都了解,Okhttp是支持同步和异步请求的,本篇就就对其原理做一个简单的梳理。算是加深okhttp的理解。
同步请求使用方式如下:
Response response = okhttpClient.newCall(request).execute();
异步请求使用方式如下:
Response response = okhttpClient.newCall(request).enqueue(callback);
可以发现在请求之前会通过OkhttpClient对象的newCall方法将Request对象转换成一个RealCall对象:
public Call newCall(Request request) {
return new RealCall(this, request, false);
}
同步execute方法简单说明:
先看看同步请求执行了什么,RealCall的execute方法如下:
public Response execute() throws IOException {
//省略了部分代码
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
client.dispatcher().finished(this);
}
1、把RealCall交给Okhttp的Dispathcer,放到一个队列里:
final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
void executed(RealCall call) {
runningSyncCalls.add(call);
}
2、调用getResponseWithInterceptorChain发起完整的网络请求流程。 对于此方法再此不会做说明,详细可参考博主的此博客。
3、执行完毕后,从队列里删除该对象(这个在异步请求中再做说明)
异步请求的简单说明:
其实异步请求当然也很简单,无非就是将getResponseWithInterceptorChain 放到线程中去执行而已,这其实是很有道理的废话。因为线程创建是比较好资源的,所以有了线程池,okhttp也不例外。线程池执行的单元为runanble,所以Okhttp也将异步请求封装了一个Ruannble,这个Ruannalbe就是AsyncCall.需要注意的是这个AsyncCall可不是RealCall的子类,也不是Call接口的实现类。啰嗦了这么多所以让我们看看okhttp是怎么实现异步请求的吧:
public void enqueue(Callback responseCallback) {
//省略部分代码
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
所以在看下Dispatcher的enqueue方法:
//正在运行的异步请求
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//等待异步执行的线程
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
上面的逻辑很清晰,当请求过多的时候放入放入等待队列中,否则放在正在执行的runningAsyncCalls队列中并用executorService来执行这个AsyncCall. 执行AsyncCall主要是让线程池的某个线程执行其run方法,而AsyncCall是一个NamedRunnable,所以分析AsyncCall的execute即可:
protected void execute() {
try {
Response response = getResponseWithInterceptorChain();
//当客户端主动取消请求的时候执行
if (retryAndFollowUpInterceptor.isCanceled()) {
//请求失败回调
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
//请求成功回调
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
//省略部分代码
} finally {
//从runningAsyncCalls移除,并且从readyAsyncCalls取一个AsyncCall执行
client.dispatcher().finished(this);
}
}
}
首先如同步请求那样调用getResponseWithInterceptorChain()进行网络请求,然后返回Response对象。如果当前请求取消的话,那么就回调onFailure方法,否则就执行onResponse方法。最后执行了Dispathcer来标志当前请求的结束!先看看这个finish方法执行了神马,这个跟同步的请求还是有区别的:
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
///////////////////////////////
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//删除执行完毕的Call
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//从等待队列中获取一个AsncCall对象并执行值
if (promoteCalls) promoteCalls();
//获取正在运行的请求个数
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
//当所有请求都执行完毕的时候执行这个runnable
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
finished方法总的来说做了如下几个工作:
1、从runningAsyncCalls删除已经结束的AsycCall对象。
2、执行promoteCalls()方法:
private void promoteCalls() {
//省略部分代码
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
//从等待队删除call对象
i.remove();
//将AsycnCall对象放入runningAsyncCalls中
runningAsyncCalls.add(call);
//执行AsycnCall
executorService().execute(call);
}
//省略部分代码
}
}
promoteCalls方法主要就是从等待队列里获取AsycCall,然后放入runningAsyncCalls,并且交给线程池里的线程执行。
3、调用runningCallsCount()获取当前正在执行的网络而请求个数,如果为0话说明此时OkhttpClient处于空闲状态,并且客户端在初始化Dispatcher对象的时候调用了setIdleCallback(@Nullable Runnable idleCallback) 方法,则直接调用者run方法。这个idleCallback可以用来告知我们OkhttpClient什么时候全部请求完毕,然后处理我们自己的业务逻辑。
//返回同步请求的个数和异步请求的个数之和
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
通过上面的分析,可以发现不论是同步请求还是异步请求,都会交给Dispatcher统一管理,该Dispathcer有三个队列:
//等待执行的请求队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//异步执行的请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//同步执行的请求队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
Dispather也提供了相应的的方法来获取这些队列对象,比如我们获取正在执行的请求,可以通过Dispatcher的runningCalls方法:
//将同步请求队列和异步请求队列的数据组装起来,返回一个集合
public synchronized List<Call> runningCalls() {
List<Call> result = new ArrayList<>();
result.addAll(runningSyncCalls);
for (AsyncCall asyncCall : runningAsyncCalls) {
//get()方法返回的是一个realCall
result.add(asyncCall.get());
}
return Collections.unmodifiableList(result);
}
通过上文我们知道AsyncCall并不是Call接口的实现类,而是RealCall的内部类,它提供了get()方法,来返回一个RealCall对象:
RealCall get() {
return RealCall.this;
}
其实Call接口还有一个cancel()方法用来取消当前的Request请求,那么我们就可以通过runningCalls返回的List来取消所有的请求:
List<Call> calls = runningCalls();
for(Call call : calls) {
call.cancel();
}
其实Dispatcher本身就提供了取消所有请求的cancelAll方法:
public synchronized void cancelAll() {
//取消所有等待执行的异步请求
for (AsyncCall call : readyAsyncCalls) {
call.get().cancel();
}
//取消所有的同步请求
for (AsyncCall call : runningAsyncCalls) {
call.get().cancel();
}
//取消正在执行的异步请求
for (RealCall call : runningSyncCalls) {
call.cancel();
}
}
那么这个cancel对象的cancel方法都做了什么呢?进入RealCall对象查看之:
public void cancel() {
retryAndFollowUpInterceptor.cancel();
}
很简单就是将调用retryAndFollowUpInterceptor的cancel方法,取消拦截器链的执行。(想了解retryAndFollowUpInterceptor干什么的,点击此处)
注意的是cancel方法并不能取消通过CallServerInterceptor拦截器发送的数据(除非你能顺着网线把发送的数据在拉回来),如果该拦截器已经执行的话,服务器会正常返回Resonse对象,此时对于异步请求来说就会走callback的onFailure方法而已:
//当客户端主动取消请求的时候执行
if (retryAndFollowUpInterceptor.isCanceled()) {
//请求失败回调
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
//请求成功回调
responseCallback.onResponse(RealCall.this, response);
}
那么既然Dispatcher提供了cancelAll方法了,我们拿到runningCalls()还有什么用呢?在Okhttp的api上官方建议我们使用OkhttpClient的时候最好就实例化一个OkhttpClient对象:
OkHttp performs best when you create a single instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools.
那么如果我们在Android应用中使用了OkhttpClient单利对象,在某Activity的onDestory方法调用了cancelAll()方法,很可能会导致如下问题:当一个页面的A Activity onDestory的时候,另一个Activity B也已经创建且Activity B会通过OkhttpClient来访问网络.有时候onDestroy的执行会比较之后,也就是说另B activity都已经启动了,A 的onDestroy才执行,那么B页面因为A页面的调用了callAll()方法就不会加载B页面所需的数据了,尴了个尬!
那么有没有解决的方法呢?有,大写的有!
在我们构建Reqeust对象的时候,Reqeust.Buider对象提供了如下方法:
public Builder tag(Object tag) {
this.tag = tag;
return this;
}
可以通过tag方法为每一个Requset设置一个tag标识,此标识可以作为Request的唯一标识来用,且通过newCall(request)返回的RealCall也提供了获取Request引用的方法:
//该方法是接口call提供的方法,由RealCall来实现
public Request request() {
return originalRequest;
}
那么我们就可以通过为不同页面的访问请求来设置tag,在onDestroy取消掉自己的请求就可以了,代码如下:
public void cancelByTag(Object tag) {
Dispatcher dispatcher = client.dispatcher();
//取消等待执行的异步请求
for (Call call : dispatcher .queuedCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
//取消所有执行的异步和同步请求
for (Call call : dispatcher .runningCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
}
其实这个cancelAll可以用在所有页面或者应用退出的时候调用。
到此为止,其简单的清流流程可以用如下来表示: