Android小知识-剖析OkHttp中的异步请求
本平台的文章更新会有延迟,大家可以关注微信公众号-顾林海,包括年底前会更新kotlin由浅入深系列教程,目前计划在微信公众号进行首发,如果大家想获取最新教程,请关注微信公众号,谢谢
其实从OkHttp的同步和异步的调用来看差别不是很大,在剖析OkHttp中的同步请求一节中知道同步是通过Call对象的execute()方法,而这节的异步请求调用的是Call对象的enqueue方法,但异步请求机制与同步请求相比,还是有所区别,这节就来分析异步请求的流程以及源码分析。
还是先贴出异步请求的代码:
private OkHttpClient mHttpClient = null;
private void initHttpClient() {
if (null == mHttpClient) {
mHttpClient = new OkHttpClient.Builder()
.readTimeout(5, TimeUnit.SECONDS)//设置读超时
.writeTimeout(5, TimeUnit.SECONDS)////设置写超时
.connectTimeout(15, TimeUnit.SECONDS)//设置连接超时
.retryOnConnectionFailure(true)//是否自动重连
.build();
}
}
private void asyRequest() {
final Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = mHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(request.body().toString());
}
});
}
这段代码很熟悉,我们快速过一下流程:
-
创建OkHttpClient对象。
-
创建Request对象。
-
通过OkHttpClient的newCall方法将Request对象封装Http实际请求的Call对象。
-
最后通过Call对象的enqueue方法传入Callback对象并实现两个回调方法。
这里最大的区别就是最后一步调用的是enqueue方法,前三步都没有发起真正的网络请求,真正的网络请求是在第四步,所以我们着重看最后一步。
在enqueue方法中,会传入Callback对象进来,这个Callback对象就是用于请求结束后对结果进行回调的,进入enqueu方法。
public interface Call extends Cloneable {
...
void enqueue(Callback responseCallback);
...
}
发现这个Call只是接口,在剖析OkHttp中的同步请求一节中知道RealCall才是真正实现Call的类。
点击进入RealCall的enqueue方法:
@Override public void enqueue(Callback responseCallback) {
//判断同一Http是否请求过
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//捕捉Http请求的异常堆栈信息
captureCallStackTrace();
eventListener.callStart(this);
//重点1
client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}
在enqueue方法中会先判断RealCall这个Http请求是否请求过,请求过会抛出异常。
接着看最后一行代码重点1:
1、传入的Callback对象被封装成了AsyncCall对象,点进去看一下AsyncCall对象到底是干什么的。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
...
}
这个AsyncCall继承了NameRunnable,这个NameRunnable又是什么呢?点进去看一下:
public abstract class NamedRunnable implements Runnable {
}
原来NameRunnable就是一个Runnable对象。回过头来总结一下,也就是说我们传入的Callback对象被封装成AsyncCall对象,这个AsyncCall对象本质就是一个Runnable对象。
2、获取Dispatcher分发器,调用Dispatcher对象的enqueue方法并将AsyncCall对象作为参数传递过去,最终完成异步请求,我们看下Dispatcher的enqueue方法。
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
synchronized void enqueue(RealCall.AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//第一步
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
//第二步
readyAsyncCalls.add(call);
}
}
在enqueue方法前使用了synchronized关键字进行修饰,也就是为这个方法加了个同步锁,继续往下看第一步,先是判断当前异步请求总数是否小于设定的最大请求数(默认是64),以及正在运行的每个主机请求数是否小于设定的主机最大请求数(默认是5),如果满足这两个条件,就会把传递进来的AsyncCall对象添加到正在运行的异步请求队列中,然后通过线程池执行这个请求。如果满足不了上面的两个条件就会走第二步,将AsyncCall对象存入readyAsyncCalls队列中,这个readyAsyncCalls就是用来存放等待请求的一个队列。
总结RealCall的enqueue方法:
-
判断当前Call:实际的Http请求是否只执行一次,如果不是抛出异常。
-
封装成一个AsyncCall对象:将Callback对象封装成一个AsyncCall对象,AsyncCall对象就是一个Runnable对象。
-
client.dispatcher().enqueue():构建完AsyncCall也就是Runnable对象后,调用Dispatcher对象的enqueue方法来进行异步的网络请求,并判断当前请求数小于64以及当前host请求数小于5的情况下,将Runnable对象放入正在请求的异步队列中并通过线程池执行RealCall请求。如果不满足条件,将Runnable添加到等待就绪的异步请求队列当中。
在上面总结的第三步中,满足条件会将AsyncCall对象通过线程池执行,我们看一下线程池方法executorService():
private @Nullable ExecutorService executorService;
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
executorService方法只是返回一个线程池对象executorService。
获取线程池对象后,就可以调用它的execute方法,execute方法需要传入一个Runnable对象,AsyncCall对象继承NamedRunnable对象,而NamedRunnable又继承了Runnable对象,那么AsyncCall就是一个Runnable对象,这里就会将AsyncCall对象传入。
在源码中发现AsyncCall并没有实现run方法,那么这个run方法一定就是在它的父类NamedRunnable中,我们点击进去看下:
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
发现NamedRunnable是一个抽象类,在run方法中并没有做实际操作,只是调用了抽象方法execute,这是一个典型的模板方法模式。既然AsyncCall继承了NamedRunnable这个抽象类,那么抽象方法execute的具体实现就交由AsyncCall来实现了。
进入AsyncCall中的execute方法:
@Override protected void execute() {
boolean signalledCallback = false;
try {
//重点1
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
//重点2:重定向和重试拦截器
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//重点3:请求当前的异步请求
client.dispatcher().finished(this);
}
}
在重点1处通过getResponseWithInterceptorChain()方法获取返回的Response对象,getResponseWithInterceptorChain方法的作用是通过一系列的拦截器获取Response对象。
在重点2处判断重定向和重试拦截器是否取消,如果取消,调用responseCallback的onFailure回调,responseCallback就是我们通过enqueue方法传入的Callback对象。如果没取消,调用responseCallback的onResponse回调。
由于execute方法是在run方法中执行的,所以onFailure和onResponse回调都是在子线程当中。
在重点3处finally块中,调用Dispatcher的finished方法。
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) {
//移除请求
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
//计算同步请求队列+异步请求队列的总数
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
finished方法内部会将本次的异步请求RealCall从正在请求的异步请求队列中移除,由于promoteCalls传入的是true,接着调用promoteCalls()方法,接着统计正在请求的同步和异步的请求总数,以及判断当前总的请求数如果等于0并且idleCallback对象不为空的情况下执行idleCallback对象的run方法。
finished方法的介绍在剖析OkHttp中的同步请求一节中其实已经介绍过了,唯一有区别的就是promoteCalls参数,同步的时候传入的是false,但在异步请求时传入的是true,也就是会执行promoteCalls方法。
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
在完成异步请求后,需要将当前的异步请求RealCall从正在请求的异步队列中移除,移除完毕后会通过promoteCalls方法,将等待就绪的异步队列中的请求添加到正在请求的异步请求队列中去并通过线程池来执行异步请求。
838794-506ddad529df4cd4.webp.jpg
搜索微信“顾林海”公众号,定期推送优质文章。