写点什么

彻底理解 OkHttp - OkHttp 源码解析及 OkHttp 的设计思想 (1)

用户头像
Android架构
关注
发布于: 刚刚

从上述代码可以看到 Dispatcher 将 call 加入到队列中,然后通过线程池来执行 call。


可能大家看了还是懵懵的,我们先了解一下 Dispatcher 几个属性和方法


//TODO 同时能进行的最大请求数 private int maxRequests = 64;//TODO 同时请求的相同 HOST 的最大个数 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]//TODO 如 https://restapi.amap.com restapi.amap.com - hostprivate int maxRequestsPerHost = 5;/**


  • Ready async calls in the order they'll be run.

  • TODO 双端队列,支持首尾两端 双向开口可进可出,方便移除

  • 异步等待队列


*/private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();


/**


  • Running asynchronous calls. Includes canceled calls that haven't finished yet.

  • TODO 正在进行的异步队列*/private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();


很明显,okhttp 可以进行多个并发网络请求,并且可以设置最大的请求数


executorService() 这个方法很简单,只是创建了一个线程池


public synchronized ExecutorService executorService() {if (executorService == null) {//TODO 线程池的相关概念 需要理解//TODO 核心线程 最大线程 非核心线程闲置 60 秒回收 任务队列 executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",false));}return executorService;}


既然我们将 call 放到了线程池中那么它是如何执行的呢?注意这里的 call 是 AsyncCall。


我们看一下 AsyncCall 的实现:


final class AsyncCall extends NamedRunnable


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


原来如此 AsyncCall 其实就是一个 Runnable,线程池实际上就是执行了 execute()。


我们在看一下 AsyncCall 的 execute()


final class AsyncCall extends NamedRunnable {@Override protected void execute() {boolean signalledCallback = false;try {//TODO 责任链模式//TODO 拦截器链 执行请求 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 {//TODO 移除队列 client.dispatcher().finished(this);}}}


从上述代码可以看出真正执行请求的是_getResponseWithInterceptorChain();_ 然后通过回调将 Response 返回给用户。


值得注意的 finally 执行了_client.dispatcher().finished(this);_ 通过调度器移除队列,并且判断是否存在等待队列,如果存在,检查执行队列是否达到最大值,如果没有将等待队列变为执行队列。这样也就确保了等待队列被执行。


private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {int runningCallsCount;Runnable idleCallback;synchronized (this) {//TODO calls 移除队列 if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");//TODO 检查是否为异步请求,检查等候的队列 readyAsyncCalls,如果存在等候队列,则将等候队列加入执行队列 if (promoteCalls) promoteCalls();//TODO 运行队列的数量 runningCallsCount = runningCallsCount();idleCallback = this.idleCallback;}//闲置调用 if (runningCallsCount == 0 && idleCallback != null) {idleCallback.run();}}


private void promoteCalls() {//TODO 检查 运行队列 与 等待队列 if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.


//TODO 将等待队列加入到运行队列中 for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();//TODO 相同 host 的请求没有达到最大,加入运行队列 if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}


if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}


真正的执行网络请求和返回响应结果:getResponseWithInterceptorChain(),下面我们着重分析一下这个方法:


每段代码我都加上了注释。


//TODO 核心代码 开始真正的执行网络请求 Response getResponseWithInterceptorChain() throws IOException {// Build a full stack of interceptors.//TODO 责任链 List<Interceptor> interceptors = new ArrayList<>();//TODO 在配置 okhttpClient 时设置的 intercept 由用户自己设置 interceptors.addAll(client.interceptors());//TODO 负责处理失败后的重试与重定向 interceptors.add(retryAndFollowUpInterceptor);//TODO 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息//TODO 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。interceptors.add(new BridgeInterceptor(client.cookieJar()));//TODO 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应//TODO 设置请求头(If-None-Match、If-Modified-Since 等) 服务器可能返回 304(未修改)//TODO 可配置用户自己设置的缓存拦截器 interceptors.add(new CacheInterceptor(client.internalCache()));//TODO 连接服务器 负责和服务器建立连接 这里才是真正的请求网络 interceptors.add(new ConnectInterceptor(client));if (!forWebSocket) {//TODO 配置 okhttpClient 时设置的 networkInterceptors//TODO 返回观察单个网络请求和响应的不可变拦截器列表。interceptors.addAll(client.networkInterceptors());}//TODO 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据//TODO 进行 http 请求报文的封装与请求报文的解析 interceptors.add(new CallServerInterceptor(forWebSocket));


//TODO 创建责任链 Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());


//TODO 执行责任链 return chain.proceed(originalRequest);}


从上述代码中,可以看出都实现了 Interceptor 接口,这是 okhttp 最核心的部分,采用责任链的模式来使每个功能分开,每个 Interceptor 自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。


责任链模式是设计模式中的一种也相当简单参考链接,这里不在复述。


我们着重分析一下,okhttp 的设计实现,如何通过责任链来进行传递返回数据的。


上述代码中可以看出 interceptors,是传递到了 RealInterceptorChain 该类实现了 Interceptor.Chain,并且执行了 chain.proceed(originalRequest)。


其实核心代码就是 chain.proceed() 通过该方法进行责任链的执行。


public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {if (index >= interceptors.size()) throw new AssertionError();calls++;//TODO 创建新的拦截链,链中的拦截器集合 index+1RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,writeTimeout);//TODO 执行当前的拦截器-如果在配置 okhttpClient,时没有设置 intercept 默认是先执行:retryAndFollowUpInterceptor 拦截器 Interceptor interceptor = interceptors.get(index);//TODO 执行拦截器 Response response = interceptor.intercept(next);return response;}


从上述代码,我们可以知道,新建了一个 RealInterceptorChain 责任链 并且 index+1,然后 执行 interceptors.get(index); 返回 Response。


其实就是按顺序执行了拦截器,这里我画了一个简图:



拦截器的执行顺序便是如上图这样执行的。


这样设计的一个好处就是,责任链中每个拦截器都会执行 chain.proceed()方法之前的代码,等责任链最后一个拦截器执行完毕后会返回最终的响应数据,而 chain.proceed() 方法会得到最终的响应数据,这时就会执行每个拦截器的 chain.proceed()方法之后的代码,其实就是对响应数据的一些操作。


CacheInterceptor 缓存拦截器就是很好的证明,我们来通过 CacheInterceptor 缓存拦截器来进行分析,大家就会明白了。


CacheInterceptor 的实现如下:


代码比较长,我们一步一步的来进行分析。


首先我们先分析上部分代码当没有网络的情况下是如何处理获取缓存的。


@Override public Response intercept(Chain chain) throws IOException{//TODO 获取 request 对应缓存的 Response 如果用户没有配置缓存拦截器 cacheCandidate == nullResponse cacheCandidate = cache != null? cache.get(chain.request()): null;


//TODO 执行响应缓存策略 long now = System.currentTimeMillis();CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();//TODO 如果 networkRequest == null 则说明不使用网络请求 Request networkRequest = strategy.networkRequest;//TODO 获取缓存中(CacheStrategy)的 ResponseResponse cacheResponse = strategy.cacheResponse;


if (cache != null) {cache.trackResponse(strategy);}//TODO 缓存无效 关闭资源 if (cacheCandidate != null && cacheResponse == null) {closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.}


// If we're forbidden from using the network and the cache is insufficient, fail.//TODO networkRequest == null 不实用网路请求 且没有缓存 cacheResponse == null 返回失败 if (networkRequest == null && cacheResponse == null) {return new Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-ca


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ched)").body(Util.EMPTY_RESPONSE).sentRequestAtMillis(-1L).receivedResponseAtMillis(System.currentTimeMillis()).build();}


//TODO 不使用网络请求 且存在缓存 直接返回响应// If we don't need the network, we're done.if (networkRequest == null) {return cacheResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).build();}}


上述的代码,主要做了几件事:


  1. 如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的 Response,否则 cacheCandidate = null;同时从 CacheStrategy 获取 cacheResponse 和 networkRequest

  2. 如果 cacheCandidate != null 而 cacheResponse == null 说明缓存无效清楚 cacheCandidate 缓存。

  3. 如果 networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。

  4. 如果 networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。


上部分代码,其实就是没有网络的时候的处理。


那么下部分代码肯定是,有网络的时候处理


//TODO 执行下一个拦截器 Response networkResponse = null;try {networkResponse = chain.proceed(networkRequest);} finally {// If we're crashing on I/O or otherwise, don't leak the cache body.if (networkResponse == null && cacheCandidate != null) {closeQuietly(cacheCandidate.body());}}


//TODO 网络请求 回来 更新缓存// If we have a cache response too, then we're doing a conditional get.//TODO 如果存在缓存 更新 if (cacheResponse != null) {//TODO 304 响应码 自从上次请求后,请求需要响应的内容未发生改变 if (networkResponse.code() == HTTP_NOT_MODIFIED) {Response response = cacheResponse.newBuilder().headers(combine(cacheResponse.headers(), networkResponse.headers())).sentRequestAtMillis(networkResponse.sentRequestAtMillis()).receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()).cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();networkResponse.body().close();


// Update the cache after combining headers but before stripping the// Content-Encoding header (as performed by initContentStream()).cache.trackConditionalCacheHit();cache.update(cacheResponse, response);return response;} else {closeQuietly(cacheResponse.body());}}//TODO 缓存 ResponseResponse response = networkResponse.newBuilder().cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();


if (cache != null) {if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {// Offer this request to the cache.CacheRequest cacheRequest = cache.put(response);return cacheWritingResponse(cacheRequest, response);}


if (HttpMethod.invalidatesCache(networkRequest.method())) {try {cache.remove(networkRequest);} catch (IOException ignored) {// The cache cannot be written.}}}


return response;}


下部分代码主要做了这几件事:


  1. 执行下一个拦截器,也就是请求网络

  2. 责任链执行完毕后,会返回最终响应数据,如果缓存存在更新缓存,如果缓存不存在加入到缓存中去。


这样就体现出了,责任链这样实现的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。


这也是 okhttp 设计的最优雅最核心的功能。


当然我们可以通过一个小例子来进行验证,实践才最重要。


首先我们模拟一个 拦截器的接口


/**


  • @author prim

  • @version 1.0.0

  • @desc 模拟 okhttp 拦截器

  • @time 2018/8/3 - 下午 4:29*/public interface Interceptor {String interceptor(Chain chain);


interface Chain {String request();


String proceed(String request);}}


然后在实现几个拦截器


public class BridgeInterceptor implements Interceptor {@Overridepublic String interceptor(Chain chain) {System.out.println("执行 BridgeInterceptor 拦截器之前代码");String proceed = chain.proceed(chain.request());System.out.println("执行 BridgeInterceptor 拦截器之后代码 得到最终数据:"+proceed);return proceed;}}


public class RetryAndFollowInterceptor implements Interceptor {@Overridepublic String interceptor(Chain chain) {System.out.println("执行 RetryAndFollowInterceptor 拦截器之前代码");String proceed = chain.proceed(chain.request());System.out.println("执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:" + proceed);return proceed;}}


public class CacheInterceptor implements Interceptor {@Overridepublic String interceptor(Chain chain) {System.out.println("执行 CacheInterceptor 最后一个拦截器 返回最终数据");return "success";}}


然后实现 Chain 接口


public class RealInterceptorChain implements Interceptor.Chain {


private List<Interceptor> interceptors;


private int index;


private String request;


public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {this.interceptors = interceptors;this.index = index;this.request = request;}


@Overridepublic String request() {return request;}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
彻底理解OkHttp - OkHttp 源码解析及OkHttp的设计思想(1)