0 前言
作为系列的开篇,还是想解释一下“初级必会”这个标题,并不是凡尔赛!相信点进来的Android小伙伴们都能体会到客户端多难了。各类技术社区随处都能看到Android-er的焦虑:
如今随便找个公司,面试都要来几道算法,左手AMS/WMS/JVM,右手性能优化/JetPack。至于几年前常见于中高级面试中第三方原理,放到现在也就是初级水平了😂。
"万物皆可卷",对未来迷茫的Android工程师建议认真拜读刘望舒大佬的文章。 寒冬已至!视频聊聊四面楚歌的Android工程师该何去何从?
内卷化效应就是长期从事一项相同的工作,并且保持在一定的层面,没有任何变化和改观。这种行为通常是一种自我懈怠,自我消耗。
1 示例代码
//1、创建client对象
OkHttpClient okHttpClient1 = new OkHttpClient();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
//2、client+call 创建 RealCall
Request request = new Request.Builder()
.url(URL)
.build();
Call call = okHttpClient.newCall(request);
//3、同步请求
Response response = call.execute();
//3、异步请求
okHttpClient1.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
System.out.println(response.body().string());
}
});
OkHttp请求可以分为三个阶段:
- OkHttpClient + Request 构造RealCall;
- RealCall同步或异步执行,由Dispatcher分发;
- 通过getResponseWithInterceptorChain()获取Response。
2 OkHttpClient
OkHttpClient对象有两种创建方式,一种是直接new出来,使用默认配置,另一种是通过建造者模式自由设置配置参数。
public OkHttpClient() {
this(new Builder());
}
OkHttpClient类将各种功能模块包装进来,让其对外提供统一API,这种设计模式称为外观模式。
创建完client和request后,需要生成一个Call对象。Call对象在OkHttp中可以代表一次请求,通过Call接口提供的各类方法,对此请求进行操作。Call 接口内部提供了内部接口Factory,使用了工厂方法模式。
interface Factory {
Call newCall(Request request);
}
代码中通过client.newCall()获取Call对象。
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
3 RealCall + Dispatcher
RealCall实现Call接口,是发起请求的实现类,下面来看两种不同请求方式的具体实现。
3.1 同步请求
RealCall.class
@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);
}
}
方法使用了synchronized (this) 来保证同一请求(Call对象)不重复执行,之后利用dispatcher调度器,将请求添加进同步双向队列runningSyncCalls,得到请求结果后,将call从队列中移除。
Dispatcher.class
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
3.2 异步请求
RealCall.class
@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));
}
Dispatcher.class
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
与同步请求类似,RealCall中的enqueue方法调用了Dispatcher中具体实现的enqueue,将请求加入到了异步双向队列runningAsyncCalls。这里注意一下入队的条件(不满足条件则进入等待队列):
- runningAsyncCalls.size() < maxRequests 并发请求小于64
- runningCallsForHost(call) < maxRequestsPerHost 同一个请求数量小于5
下一步,具体的执行交由Dispatcher内部的线程池。挖个坑😥Java线程池有关内容期待后续文章。
Dispatcher.class
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;
}
现在按照“套路”,需要使用getResponseWithInterceptorChain()获取Response拿到请求结果,这部分的实现在哪呢?来看方法中的call对象,它的类型是AsyncCall。似乎有点眼熟,在RealCall中,okHttpClient通过调度器执行enqueue时,为了得到请求结果,之前已经将它new过一次了。
RealCall.class
client.dispatcher().enqueue(new AsyncCall(responseCallback));
既然AsyncCall能拿到请求结果并返回出来,那点进来看看具体的类实现。
RealCall.class
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
...
@Override
protected void execute() {
try {
Response response = getResponseWithInterceptorChain();
} catch (IOException e) {
...
} finally {
client.dispatcher().finished(this);
}
}
}
AsyncCall继承了NamedRunnable,本质上其实是一个runnable。这里剔除部分代码,只看请求的主干,可以发现异步请求流程目前已经和同步请求一致了。说清楚了同步异步请求的流程,现在要来说说调度器对异步请求的管理与控制。
3.3 ArrayDeque
Dispatcher.class
/** 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<>();
Deque接口继承自Queue接口,支持同时从两端添加或移除元素,因此又被成为双端队列。ArrayDeque是依赖于可变数组来实现的,非线程安全。
前文已经提过,同步请求将请求直接添加到runningSyncCalls中,异步请求根据maxRequests和maxRequestsPerHost的限制条件,选择进入执行队列runningAsyncCalls或等待队列readyAsyncCalls中。
同步请求交由发起请求的线程执行,异步请求交由线程池执行,虽然线程池支持的最大线程数是Integer.MAX_VALUE,但是这个值受maxRequests限制,那么当请求数量达到最大值限制后,后续请求如何调度呢?我们来看回收部分的逻辑,同步异步回收请求时都执行了client.dispatcher().finished(this)方法。
/** 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);
}
同异步请求都调用finished方法,区别只是boolean promoteCalls参数值。
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();
}
}
重点关注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.
}
}
将此方法的功能概括一下:通过判断请求数量,将等待队列中的请求加入执行队列中,并交由线程池执行。 可以看出,Dispatcher内,线程池主要起到的是缓存、执行作用,主要的调度策略还是由调度器自身完成。
4 InterceptorChain
OkHttp请求的最后一步,通过getResponseWithInterceptorChain()获取Response。这里用到了责任链模式。
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);
}
创建责任链时用到的是RealInterceptorChain类,实现了Interceptor.Chain接口,并通过调用chain.proceed推动请求进行。下面来看看RealInterceptorChain的实现。
public final class RealInterceptorChain implements Interceptor.Chain {
....
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
}
这里其实就是一个递归。每个责任链都调用下一个,推动拦截链执行,最后依次返回Response。可以看到网上出现的Response流程图,既有单向,也有双向,本质上其实都是一致。这部分不具体展开各个Interceptor的作用了,这里只需要知道整个请求过程的调用链条。
网上找了一份流程图供大家参考,图片来源大佬博客拆轮子系列:拆 OkHttp,侵删。
5 最后
这篇博客只梳理了基本的请求流程,OkHttp几个重要的内容还没说:Interceptors和NetworkInterceptors的区别、连接池,这些也算是日常常问的问题了。其中连接池非常重要,但是需要HTTP/TCP的扩展知识,作为“初级必会”本文也就不展开了。后续如果介绍到HTTP,会以此为例。
最后补充一句,也算预告吧,“初级必会”系列其实是想安利几套常用的项目/通信框架(MVP、MVVM、Netty),让有需要的小伙伴能拿来直接上手;第一套是OkHttp+Retrofit+RxJava+Dagger2,后续几篇会把涉及到第三方的源码都做个介绍,最后把框架地址贴出来。
flag:年前把第一套框架全部总结完,给2020画上句号🤪。