OkHttp3源码阅读1之同步和异步请求的实现

时间:2022-08-27 13:59:02

系列

OkHttp3源码阅读1之同步和异步请求的实现

前文

本文基于Okhttp3版本

compile ‘com.squareup.okhttp3:okhttp:3.4.1’

https://github.com/square/okhttp

基本使用

同步请求:

private static final String ENDPOINT = "https://api.github.com/repos/square/okhttp/contributors";
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url(ENDPOINT)
.build();
Response response = client.newCall(request).execute();

异步请求:

private static final String ENDPOINT = "https://api.github.com/repos/square/okhttp/contributors";
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder()
.url(ENDPOINT)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}

@Override
public void onResponse(Call call, Response response) throws IOException {
}
});

从上面使用代码可以看出,不管是同步请求还是异步请求,都需要初始化OkHttpClient以及创建一个Request,然后再调用OkHttpClinet的newCall方法,创建出一个RealCall对象。

对于同步请求,是调用RealCall的execute方法;而异步请求,则是调用RealCall的enqueue方法实现。

初步认识OkHttpClient,Request,Response

OkHttpCLient:在Okhttp库中,OkHttpClient处于一个中心者的地位,很多功能都需要通过它来转发或者实现的。在创建的时候,初始化了很多功能类,比如缓存,拦截器,网络连接池,分发器等类。
由于初始化比较复杂,OkHttpClient内部提供了Builder模式来创建。

一般情况下,OkHttpClient是唯一的。因此,在看源码的时候,发现有传递OkHttpClient对象,我们就可以认为,这些都是同一个对象。

Request:是用来构建一个请求对象的,符合Http请求的标准,包含了请求头,方法等等属性,较为复杂,因此同样提供Builder模式构建。

Response:是用来构建一个响应对象的,包含了响应头,响应码,数据等等属性,同样也提供Builder模式构建。

RealCall

// 同步
client.newCall(request).execute()
// 异步
client.newCall(request).enqueue(Callback)

同步和异步请求,都是调用OkHttpClient的newCall方法创建一个RealCall对象,然后通过这个对象,执行请求的。

OkHttpClient.java

@Override 
public Call newCall(Request request) {
return new RealCall(this, request);
}

getResponseWithInterceptorChain-请求响应数据的过程

private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));

Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}

从请求到响应这一过程,Okhttp会对数据(请求流和响应流)进行拦截进行一些额外的处理,比如失败重连,添加请求头,没网络优先返回缓存数据等等。

这一拦截过程,采用责任链模式来实现的,和Android中的事件传递机制差不多,这里不做详细的讨论。
可以这样理解getResponseWithInterceptorChain方法返回响应数据(数据可以是缓存的,也可以是网络的)

execute-同步请求

@Override 
public Response execute() throws IOException {
// 方法检测,execute只能调用1次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
// 将RealCall添加到DIspatcher中的同步请求队列中
client.dispatcher().executed(this);
// 获取响应的数据
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
// 请求完成或者取消,都会将RealCall从同步请求队列中移除
client.dispatcher().finished(this);
}
}

流程:

  • 1.execute方法只能执行1次,超过则抛出IllegalStateException异常

  • 2.将请求添加到Dispatcher对象的同步请求队列中,请求任务执行完毕或者取消请求,会将对应请求移除

  • 3.调用getResponseWithInterceptorChain方法,来获取Response

  • 4.最后将请求从Dispatch而中移除,返回Response

enqueue-异步请求

@Override 
public void enqueue(Callback responseCallback) {
// 和execute一样,enqueue也只能调用1次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
// 执行请求
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

在看Dispatcher类中enqueue方法的具体细节前,我们先看下AsyncCall类

AsyncCall–实际上是一个Runnable

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,继承Runnable的抽象类,根据传入的名称来修改线程名称,抽象出执行任务的execute方法

final class AsyncCall extends NamedRunnable {
// 其他省略,execute方法式真正执行请求
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}

AsyncCall中的execute方法,是请求任务执行的方法,获取相应数据最终也是调用了getResponseWithInterceptorChain方法。

public final class Dispatcher {
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private Runnable idleCallback;

// 异步请求线程池
private ExecutorService executorService;
// 等待执行异步请求的队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// 正在执行异步请求的任务队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

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;
}

synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

}

对于异步请求来说,Dispatcher类内部持有一个正在执行异步任务的队列和一个等待执行异步请求的队列,以及一个线程池池。

原理:使用线程池池,对异步请求进行管理,最大并发数为64,主机相同的请求最大并发数为5。

  • 1.如果当前正在执行任务不大于最大并发数,主机相同的请求不大于最大主机请求限制,则添加到正在执行队列中,否则添加到等待队列中。

  • 2.请求执行完成,则将异步请求从异步请求队列中移除,并将等待中的异步请求移动到正在执行的异步请求队列中,并执行任务。