okhttp可以说是面试中被问到的频率最高的第三方框架了,应该说没有之一吧。那正好趁着找工作,复习拆分一下这个第三方库。
第一部分:用到的设计模式
1.建造者模式:比如 OkHttpClient、Request。为什么要用建造者模式?建造者模式使用特点就是,如果一个类有多个(三个吧)构造器,并且有些参数是非必填的,那么就适合用建造者模式。刚好这两个类所有的参数都是有默认值得(至少是非必填的);
2.静态工厂模式:RequestBody.create();啥也不用说,就是简单。
3.责任链模式:啥是责任链模式?首先要是链式调用,就是我要可以调用下一个形成链子,所以说拦截器最后一个必须是调用由系统默认写好的,我们可以在中间按照模板写自己的拦截器插入节点。
4.生产者消费者模式:Dispatcher类职责。我们创建请求就是生产者,调用器从线程池中取出请求来联网处理就是消费者,而线程池(队列)则是缓冲池。
第二部分:如何使用
1.创建OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(50000, TimeUnit.MILLISECONDS)
.readTimeout(50000, TimeUnit.MILLISECONDS)
.build();
2.创建Request
Request request = new Request.Builder()
.url("https://baidu.com")
.post(RequestBody.create(MediaType.parse(""), ""))
.tag("TAG")
.build();
3.发送请求
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
第三部分:逐个分析拆分源码
1.创建 OkHttpClient,建造者模式创建,可以随意配置所需参数,并且官方说建议全局只有一个这个类对象,就是建议设置成静态的。后面分析。
* <p>OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
* all of your HTTP calls. This is because each client holds its own connection pool and thread
* pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
* client for each request wastes resources on idle pools.
2.创建Request,创建请求,建造者模式,就是设置url啊header啊什么的,没什么可说的。
3.发送请求,需要一点点拆分了。
client.newCall(request)
进去看看
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
创建了一个真正的请求类realcall,就是说后面的client.newCall(request).enqueue()中后半部分,实际上是realCall.enqueue(),我们看看
//realcall中
@Override public void enqueue(Callback responseCallback) {
//省略...
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
核心代码就2点:第一client登场了,用他的调度器插入了任务,调度器我们后面说,先知道他是一个线程池就可以了。第二,这里又创建了一个AsyncCall类,这个类是RealCall的内部类,内部类特点就是持有外部类的引用,所以RealCall中所有参数都可以直接使用。看一眼这个类:
final class AsyncCall extends NamedRunnable {
//省略...
@Override protected void execute() {
//暂时不需要...
}
}
暂时只需要看2点,第一点是RealCall内部类,且是NamedRunnable的子类,也就是是一个任务。那么既然是一个任务,我们把这个任务放到上面那个线程池中,就会调用run方法,而run方法啥也没干,就调用了execute方法.下面重点看这个方法:
@Override protected void execute() {
try {
Response response = getResponseWithInterceptorChain();//请求
if (retryAndFollowUpInterceptor.isCanceled()) {//失败(取消了)
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
responseCallback.onResponse(RealCall.this, response);//成功
}
} catch (IOException e) {
responseCallback.onFailure(RealCall.this, e);//失败
} finally {
client.dispatcher().finished(this);//结束任务
}
}
发送请求(这里就有各种拦截器)---成功、失败、取消----结束任务(client.dispatcher().finished(this) 方法,后面讲client会用到)。没了,就这么简单。下面就是重点看看发送请求和拿到数据怎么来的:
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);
return chain.proceed(originalRequest);
}
先添加了一大堆拦截器,关注点,第一,我们可以在最开始加入自己的拦截器,也可以在后面发送网络前加入拦截器。第二就是最后执行的方法 RealInterceptorChain 的proceed方法:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection connection) throws IOException {
//。。。
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
//...
return response;
}
省略代码做了一些校验,然后就是开始执行责任链中逐个拦截器...最后再分析拦截器。到此,我们就已经联网成功拿到了数据。
4.上面把联网的流程讲完了,该重点说两处忽略的细节。
4.1)client.dispatcher() 也就是 Dispatcher类是如何执行我们的联网请求的。这里就是生产者消费者模式。
public final class Dispatcher {
//最大请求数64个
private int maxRequests = 64;
//最大域名数5个
private int maxRequestsPerHost = 5;
private Runnable idleCallback;
/** Executes calls. Created lazily. */
private 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<>();
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
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;
}
//...先省略
}
这类中有写参数很关键,说明一下:
maxRequests 最大请求数64个
maxRequestsPerHost 最大请求域名是5个
readyAsyncCalls 等待异步执行的队列,没有大小限制
runningAsyncCalls 正在执行的异步请求队列,没有大小限制
runningSyncCalls 正在执行的同步请求队列,没有大小限制
executorService 线程池,懒加载,核心线程数为0,非核心线程数为最大值,非核心线程空闲存活最长时间为60s,没有缓冲队列(来了请求就执行)。
通过上面这些参数配置,我们可以得知,线程池大小不限制,请求来了就处理,那就是maxRequests 变量控制着最大联网数量,64个,如果超了则将请求放到上面的异步等待队列中。当然这些参数都可以修改。默认值最大为64。下面可以看看
client.dispatcher().enqueue(new AsyncCall(responseCallback));中enqueue方法
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
如果说正在执行的网络请求小于64个且域名总数小于5个,则直接执行,否则添加到等待队列中。那等待队列什么时候执行呢?当然是RealCall联网结束的时候就用到了,上面有一行 client.dispatcher().finished(this) 代码,就是realcall中结束后调用的
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
//第二个参数为true
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) 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.
}
}
其实源码里已经把调用关系做了注释了,finished方法调用重载方法,先是将当前请求从正在请求队列中移除掉,然后执行promoteCalls 方法,这个方法做一些比如还有没有请求啊,域名多少个限制啊判断,然后从等待队列里取出最后一个请求,交给线程池执行,同时添加到正在执行队列中。至此调度器分析完毕。
4.2)上面还有一个地方没有仔细分析,那就是经过一系列拦截器之后,我们就拿到了数据,到底怎么联网的?我们来分析一下ConnectInterceptor这个拦截器,其他拦截器大同小异,只是职责不同。
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
这个类中intercept方法中,首先获取到了realChain,但是这个类在RealCall的责任链刚开始调用的构造地方所有参数基本上都是null
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));
//看这里,创建RealInterceptorChain的参数中中间三个全是StreamAllocation HttpCodec
//Connection 三个参数都是空
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
这就尴尬了,也就是说我们上面拦截器中realChain.streamAllocation();获取到的值为null,那么肯定在这个拦截器前面的拦截器中对他进行赋值操作了,最终在RetryAndFollowUpInterceptor 这个拦截器中找到了:
public final class RetryAndFollowUpInterceptor implements Interceptor {
private StreamAllocation streamAllocation;
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//创建实例
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
//...
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
} catch (RouteException e) {
//。。。
}
}
}
下面看看streamAllocation.newStream()方法吧:
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
调用了查找可以复用的健康链接findHealthyConnection方法:
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
//调用查找方法
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);
//找到了,返回
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// 没找到,去创建一个
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
这里调用了查找链接方法,如果找到了直接返回,没找到就创建一个,先看找的方法:
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
//三种情况不行
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// 难点一:这里开始看看是否有链接可以复用,但是this.connection什么时候赋的值呢?
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// 难点二:如果没有可以复用的就去链接池里找,但是这个方法有点奇怪呢?咋给connection 赋的值?
Internal.instance.get(connectionPool, address, this);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// 如果没有找到,则去创建一个链接,这是一个阻塞耗时操作
RealConnection result;
synchronized (connectionPool) {
//新建
result = new RealConnection(connectionPool, selectedRoute);
//同步数据 this.connection 赋值
acquire(result);
//取消了就退出
if (canceled) throw new IOException("Canceled");
}
// Do TCP + TLS handshakes. This is a blocking operation.三次握手和验证
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// 将创建的链接放到缓存池中
Internal.instance.put(connectionPool, result);
//
}
return result;
}
上面逻辑依旧很清晰,如果说上一个联网还可以用,就复用,如果不能用就去连接池中找看看还有能用的吗,如果有就返回,没有就去创建一个新的链接,并放到缓存池中。
难点一:this.connection 表示上一个刚结束的链接是否可以复用,为什么是上一个呢?因为,第一个链接这个值肯定是个null,而从第二个开始,这个值就还是null,从第三个开始的时候就不是null了,那什么时候赋值的呢?肯定是上一个链接结束的地方或者说是第二个请求开始的时候,答案是从第二个请求开始的时候,这就是为什么前两个请求拿到的都是null,从第三个开始的时候才不是null了。看源码:还是StreamAllocation中方法
public void acquire(RealConnection connection) {
//赋值
this.connection = connection;
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
那 什么时候调用的这个方法呢?在ConnectionPool链接池中调用的,
/** Returns a recycled connection to {@code address}, or null if no such connection exists. */
RealConnection get(Address address, StreamAllocation streamAllocation) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address)) {
streamAllocation.acquire(connection);
return connection;
}
}
return null;
}
链接池中这个方法在哪调用呢?在OkhttpClient 中静态代码块中 Internal.instance实例中的get()方法调用:
static {
Internal.instance = new Internal() {
//这里调用了
@Override public RealConnection get(
ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
return pool.get(address, streamAllocation);
}
};
}
那这个get方法啥时候调用呢?回头看看上面的难点一下面的难点二处代码,就是调用的这个方法。这就是为什么第一次、第二次都没办法复用,只有到了第三次才可以复用原因。难点二也就一起解决了。
好了,至此复用链接就讲完了,至于联网细节就不看了。