官网介绍
https://square.github.io/okhttp/
HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.
- OkHttp is an HTTP client that’s efficient by default:
- HTTP/2 support allows all requests to the same host to share a
socket. - Connection pooling reduces request latency (if HTTP/2 isn’t
available). - Transparent GZIP shrinks download sizes.
- Response caching avoids the network completely for repeat requests.
Get a URL
This program downloads a URL and prints its contents as a string.
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
Post to a Server
This program posts data to a service.
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
调用流程
OkHttp请求过程中OkHttpClient、Request与Call三个角色是我们经常接触到的。但是所有的逻辑大部分集中在拦截器中,在进入拦截器之前还需要依靠分发器来调配请求任务。
OkHttpClient和Request的创建可以使用它为我们提供的Builder (建造者模式)。
Call则是把Request交给OkHttpClient之后返回的一个已准备好执行的请求。
OkHttp在设计时采用的 门面模式,将整个系统的复杂性给隐藏起来,将子系统接口通过一个客户端OkHttpClient统一暴露出来。
OkHttpClient中全是一些配置,比如代理的配置、SSL证书的配置等。
Call本身是一个接口,我们获得的实现为:RealCall。
static RealCall newRealCall(OkHttpClient client, Request originalRequest,
boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
Call的execute代表了同步请求,而enqueue则代表异步请求。两者唯一区别在于一个会直接发起网络请求,而另一个使用OkHttp内置的线程池来进行(分发器)。
分发器:Dispatcher,分发器就是来调配请求任务的,内部会包含一个线程池。可以在创建OkHttpClient时,传递我们自己定义的线程池来创建分发器。
拦截器:五大默认拦截器完成整个请求过程。
分发器
成员有
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private @Nullable
Runnable idleCallback;
/**
* Executes calls. Created lazily.
*/
private @Nullable
ExecutorService executorService;
/**
* Ready async calls in the order they'll be run.
*/
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**
* Running asynchronous calls. Includes canceled calls that haven't finished yet.
*/
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/**
* Running synchronous calls. Includes canceled calls that haven't finished yet.
*/
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
同步请求
因为同步请求不需要线程池,也不存在任何限制。所以分发器仅做一下记录。
/**
* Used by {@code Call#execute} to signal it is in-flight.
*/
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
异步请求
synchronized void enqueue(AsyncCall call) {
//正在请求的数量是有限的,默认64
//用一个域名正在请求的数量也是有限的,默认5
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
当正在执行的任务未超过最大限制64,同时同一Host的请求不超过5个,则会添加到正在执行队列,同时提交给线程池。否则先加入等待队列。加入线程池直接执行,但是如果加入等待队列后,就需要等待有空闲名额才开始执行。
每次执行完一个请求后,都会调用分发器的finished方法
/**
* Used by {@code AsyncCall#run} to signal completion.
*/
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
/**
* Used by {@code Call#execute} to signal completion.
*/
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
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();
}
}
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
只有异步任务才会存在限制与等待,所以在执行完了移除后,异步任务结束会执行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();
//等待任务想要执行,还需要满足:这个等待任务请求的Host不能已经存在5个了
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
在满足条件下,会把等待队列中的任务移动到runningAsyncCalls并交给线程池执行。分发器到这里就结束了。
请求流程
用户是不需要直接操作任何分发器的,获得的RealCall中就分别提供了execute与enqueue来开始同步请求或异步请求。
同步请求
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//调用分发器
client.dispatcher().executed(this);
//执行请求
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//请求完成
client.dispatcher().finished(this);
}
}
异步请求
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//调用分发器
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
AsyncCall实际上是一个Runnable的子类,使用线程启动一个Runnable时会执行run方法,在AsyncCall中被重定向到execute方法。
同时AsyncCall也是RealCall的普通内部类,这意味着它是持有外部类RealCall的引用,可以获得直接调用外部类的方法。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
/**
* Runnable implementation which always sets its thread name.
*/
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();
}
可以看到无论是同步还是异步请求实际上真正执行请求的工作都在getResponseWithInterceptorChain()中。这个方法就是整个OkHttp的核心:拦截器 (责任链模式)。
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 (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
分发器线程池
分发器中默认的线程池
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;
}
和Executors.newCachedThreadPool()创建的线程一样。
当一个任务通过execute(Runnable)方法添加到线程池时:
- 线程数量小于corePoolSize,新建线程(核心)来处理被添加的任务;
- 线程数量大于等于 corePoolSize,存在空闲线程,使用空闲线程执行新任务;
- 线程数量大于等于 corePoolSize,不存在空闲线程,新任务被添加到等待队列,添加成功则等待空闲线程,添加失败:
- 线程数量小于maximumPoolSize,新建线程执行新任务;
- 线程数量等于maximumPoolSize,拒绝此任务。
核心线程为0,表示线程池不会一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收。而最大线程Integer.MAX_VALUE与等待队列SynchronousQueue的组合能够得到最大的吞吐量。即当需要线程池执行任务时,如果不存在空闲线程不需要等待,马上新建线程执行任务!等待队列的不同指定了线程池的不同排队机制。一般来说,等待队列BlockingQueue有:ArrayBlockingQueue、LinkedBlockingQueue与SynchronousQueue。
分发器常问问题
如何决定将请求放入ready还是running?
如果当前正在请求数不小于64放入ready;如果小于64,但是已经存在同一域名主机的请求5个放入ready。
从running移动ready的条件是什么?
每个请求执行完成就会从running移除,同时进行第一步相同逻辑的判断,决定是否移动。
分发器线程池的工作行为?
无等待,最大并发。SynchronousQueue : 无容量的队列。