okhttp 一 概述及同步和异步请求的实现

时间:2021-08-03 04:06:14

OkHttp是一个高效的Http客户端,有如下的特点: 参考  http://www.jianshu.com/p/aad5aacd79bf

  1. 支持HTTP2/SPDY黑科技
  2. socket自动选择最好路线,并支持自动重连
  3. 拥有自动维护的socket连接池,减少握手次数
  4. 拥有队列线程池,轻松写并发
  5. 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
  6. 基于Headers的缓存策略

工作流程的概述

okhttp 一 概述及同步和异步请求的实现

当我们用OkHttpClient.newCall(request)进行execute/enenqueue时,实际是将请求Call放到了Dispatcher中,okhttp使用Dispatcher进行线程分发,它有两种方法,一个是普通的同步单线程;另一种是使用了队列进行并发任务的分发(Dispatch)与回调,我们下面主要分析第二种,也就是队列这种情况,这也是okhttp能够竞争过其它库的核心功能之一

okhttp 一 概述及同步和异步请求的实现


RealCall

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

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

流程:

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

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

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

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

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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

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

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));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

AsyncCall:RealCall的内部类,继承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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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);
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

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

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

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

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

总结:
  1. OkHttp采用Dispatcher技术,类似于Nginx,与线程池ThreadPoolExecutor配合实现了高并发,低阻塞的运行
  2. Okhttp采用Deque作为缓存,按照入队的顺序先进先出,Deque双端队列,继承自Queue,阻塞队列使用于生产者-消费者模式,双端队列同样适用于另一种相关模式,即工作密取模式:在生产者-消费者设计中,所有消费者有一个共享的工作队列,而在工作密取设计中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其它消费者双端队列末尾秘密地获取工作。密取工作模式比传统的生产者-消费者模式具有更高的可伸缩性,这是因为工作者线程不会在单个共享的任务队列上发生竞争。在大多数时候,它们都只是访问自己的双端队列,从而极大地减少了竞争。当工作者线程需要访问另一个队列时,它会从队列的尾部而不是头部获取工作,因此进一步降低了队列上的竞争程度。
  3. OkHttp最出彩的地方就是在try/finally中调用了finished函数,可以主动控制等待队列的移动,而不是采用锁或者wait/notify,极大减少了编码复杂性(readyAsyncCalls队列中 的Call请求,如何被获取执行的:难道只是通过AsyncCall的 execute的 finally中调用dispatcher.finished中调用promoteCalls()执行的吗???)
参考:深入理解OKHttp http://blog.csdn.net/qq_19431333/article/details/53207220
okhttp 网络操作 http://blog.csdn.net/qq_19431333/article/details/53419249 拆*系列:拆okHttp  https://blog.piasy.com/2016/07/11/Understand-OkHttp/