当前位置: 首页 > 编程日记 > 正文

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

OkHttp 现在统治了Android的网络请求领域,最常用的框架是:Retrofit+okhttp。OkHttp的实现原理和设计思想是必须要了解的,读懂和理解流行的框架也是程序员进阶的必经之路,代码和语言只是工具,重要的是思想。

在OKhttp 源码解析之前,我们必须先要了解http的相关基础知识,任何的网络请求都离不开http。

概述

okhttp的源码分析,网上有好多博客讲解,但讲解的都是一些源码可有可无的知识,并没有将okhttp的核心设计思想讲解到位,我们阅读一些框架的源码,学习的其实就是其设计思想,了解了整体的框架设计,在深入了解细节的实现会更加容易。

OkHttp 源码解析

1、OkHttp 的整体框架设计

建议将okhttp的源码下载下来,用AndroidStudio 打开,整篇文章是根据源码的分析来学习okhttp的设计技巧和思想,如果本篇文章有内容分析不到位的地方,欢迎大家和我一起讨论。

下图为okhttp请求网络的完整流程图(大致看一遍)

okhttp的使用方法

OkHttpClient client = new OkHttpClient();
复制代码

我们第一步先看一下okhttp的构造函数OkHttpClient()和一些配置相关,大致了解一下。

public OkHttpClient() {this(new Builder());}
复制代码

原来OkHttpClient 内部已经实现了OkHttpClient(Builder builder),如果我们不需要配置client,okhttp已将帮我们默认实现了配置。

 public Builder() {dispatcher = new Dispatcher();protocols = DEFAULT_PROTOCOLS;connectionSpecs = DEFAULT_CONNECTION_SPECS;eventListenerFactory = EventListener.factory(EventListener.NONE);proxySelector = ProxySelector.getDefault();cookieJar = CookieJar.NO_COOKIES;socketFactory = SocketFactory.getDefault();hostnameVerifier = OkHostnameVerifier.INSTANCE;certificatePinner = CertificatePinner.DEFAULT;proxyAuthenticator = Authenticator.NONE;authenticator = Authenticator.NONE;connectionPool = new ConnectionPool();dns = Dns.SYSTEM;followSslRedirects = true;followRedirects = true;retryOnConnectionFailure = true;connectTimeout = 10_000;readTimeout = 10_000;writeTimeout = 10_000;pingInterval = 0;}
复制代码

如果需要一些配置如添加拦截器等,则需要这样调用即可:

 mOkHttpClient = new OkHttpClient.Builder().addInterceptor(loggingInterceptor).retryOnConnectionFailure(true).connectTimeout(TIME_OUT, TimeUnit.SECONDS).readTimeout(TIME_OUT, TimeUnit.SECONDS).build();
复制代码

我们看一下OkHttpClient 都有那些属性,稍微了解一下即可。后面在分析

    final Dispatcher dispatcher;//调度器final @NullableProxy proxy;//代理final List<Protocol> protocols;//协议final List<ConnectionSpec> connectionSpecs;//传输层版本和连接协议final List<Interceptor> interceptors;//拦截器final List<Interceptor> networkInterceptors;//网络拦截器final EventListener.Factory eventListenerFactory;final ProxySelector proxySelector;//代理选择器final CookieJar cookieJar;//cookiefinal @NullableCache cache;//cache 缓存final @NullableInternalCache internalCache;//内部缓存final SocketFactory socketFactory;//socket 工厂final @NullableSSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于httpsfinal @NullableCertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名final HostnameVerifier hostnameVerifier;//主机名字确认final CertificatePinner certificatePinner;//证书链final Authenticator proxyAuthenticator;//代理身份验证final Authenticator authenticator;//本地省份验证final ConnectionPool connectionPool;//链接池 复用连接final Dns dns; //域名final boolean followSslRedirects;//安全套接层重定向final boolean followRedirects;//本地重定向final boolean retryOnConnectionFailure;//重试连接失败final int connectTimeout;//连接超时final int readTimeout;//读取超时final int writeTimeout;//写入超时
复制代码
请求网络
String run(String url) throws IOException {Request request = new Request.Builder().url(url).build();Response response = client.newCall(request).execute();return response.body().string();
}
复制代码

从源码中可以看出 okhttp 实现了Call.Factory接口

interface Factory {Call newCall(Request request);}
复制代码

我们看一下okhttpClient 如何实现的Call接口,代码如下:

 @Overridepublic Call newCall(Request request) {return RealCall.newRealCall(this, request, false /* for web socket */);}
复制代码

可以看出 真正的请求交给了 RealCall 类,并且RealCall 实现了Call方法,RealCall是真正的核心代码。

RealCall 主要方法:同步请求 :client.newCall(request).execute(); 和 异步请求: client.newCall(request).enqueue();

下面我们着重分析一下异步请求,因为在项目中所有的网络请求基本都是异步的,同步很少用到,最后我们在分析一下同步请求即可。

异步请求

跟着源码 走一遍 RealCall.enqueue() 的实现

RealCall.java@Override public void enqueue(Callback responseCallback) {//TODO 不能重复执行synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}captureCallStackTrace();eventListener.callStart(this);//TODO 交给 dispatcher调度器 进行调度client.dispatcher().enqueue(new AsyncCall(responseCallback));}
复制代码

可以看到上述代码做了几件事:

  1. synchronized (this) 确保每个call只能被执行一次不能重复执行,如果想要完全相同的call,可以调用如下方法:进行克隆
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.@Override public RealCall clone() {return RealCall.newRealCall(client, originalRequest, forWebSocket);}
复制代码
  1. 利用dispatcher调度器,来进行实际的执行client.dispatcher().enqueue(new AsyncCall(responseCallback));,在上面的OkHttpClient.Builder可以看出 已经初始化了Dispatcher。

下面我们着重看一下调度器的实现。

Dispatcher 调度器

Dispatcher#enqueue 的方法实现如下:

 //TODO 执行异步请求synchronized void enqueue(AsyncCall call) {//TODO 同时请求不能超过并发数(64,可配置调度器调整)//TODO okhttp会使用共享主机即 地址相同的会共享socket//TODO 同一个host最多允许5条线程通知执行请求if (runningAsyncCalls.size() < maxRequests &&runningCallsForHost(call) < maxRequestsPerHost) {//TODO 加入运行队列 并交给线程池执行runningAsyncCalls.add(call);//TODO AsyncCall 是一个runnable,放到线程池中去执行,查看其execute实现executorService().execute(call);} else {//TODO 加入等候队列readyAsyncCalls.add(call);}}
复制代码

从上述代码可以看到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-cached)").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;}@Overridepublic String proceed(String request) {if (index >= interceptors.size()) return null;//获取下一个责任链RealInterceptorChain next = new RealInterceptorChain(interceptors, index+1, request);// 执行当前的拦截器Interceptor interceptor = interceptors.get(index);return interceptor.interceptor(next);}
}
复制代码

然后进行测试,看看我们是否分析的正确

List<Interceptor> interceptors = new ArrayList<>();interceptors.add(new BridgeInterceptor());interceptors.add(new RetryAndFollowInterceptor());interceptors.add(new CacheInterceptor());RealInterceptorChain request = new RealInterceptorChain(interceptors, 0, "request");request.proceed("request");
复制代码

打印的log日志如下:

执行 BridgeInterceptor 拦截器之前代码
执行 RetryAndFollowInterceptor 拦截器之前代码
执行 CacheInterceptor 最后一个拦截器 返回最终数据
执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:success
执行 BridgeInterceptor 拦截器之后代码 得到最终数据:success
复制代码

OK 完美,验证没有问题,我想至此大家都应该懂了 okhttp的核心设计思想了。

okhttp的其他拦截器的具体实现大家可以自己研究一下即可,okhttp的这种设计思想我们完全可以应用到项目中去,解决一些问题。

同步请求

这里在稍微讲一下,okhttp的同步请求,代码很简单 同样是在RealCall 类中实现的

//TODO 同步执行请求 直接返回一个请求的结果@Override public Response execute() throws IOException {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}captureCallStackTrace();//TODO 调用监听的开始方法eventListener.callStart(this);try {//TODO 交给调度器去执行client.dispatcher().executed(this);//TODO 获取请求的返回数据Response result = getResponseWithInterceptorChain();if (result == null) throw new IOException("Canceled");return result;} catch (IOException e) {eventListener.callFailed(this, e);throw e;} finally {//TODO 执行调度器的完成方法 移除队列client.dispatcher().finished(this);}}
复制代码

主要做了几件事:

  1. synchronized (this) 避免重复执行,上面的文章部分有讲。
  2. client.dispatcher().executed(this); 实际上调度器只是将call 加入到了同步执行队列中。代码如下:
//TODO 调度器执行同步请求synchronized void executed(RealCall call) {runningSyncCalls.add(call);}
复制代码
  1. getResponseWithInterceptorChain()最核心的代码,上面已经分析过了,请求网络得到响应数据,返回给用户。
  2. client.dispatcher().finished(this); 执行调度器的完成方法 移除队列

可以看出,在同步请求的方法中,涉及到dispatcher 只是告知了执行状态,开始执行了(调用 executed),执行完毕了(调用 finished)其他的并没有涉及到。dispatcher 更多的是服务异步请求。

总结

okhttp还有很多细节在本文中并没有涉及到,例如:okhttp是如何利用DiskLruCache实现缓存的、HTTP2/HTTPS 的支持等,本文主要讲解okhttp的核心设计思想,对整体有了清晰的认识之后,在深入细节,更容易理解。

简述okhttp的执行流程:

  1. OkhttpClient 实现了Call.Fctory,负责为Request 创建 Call;
  2. RealCall 为Call的具体实现,其enqueue() 异步请求接口通过Dispatcher()调度器利用ExcutorService实现,而最终进行网络请求时和同步的execute()接口一致,都是通过 getResponseWithInterceptorChain() 函数实现
  3. getResponseWithInterceptorChain() 中利用 Interceptor 链条,责任链模式 分层实现缓存、透明压缩、网络 IO 等功能;最终将响应数据返回给用户。

拆轮子系列:拆 OkHttp

OkHttp

转载于:https://juejin.im/post/5c1b23b9e51d4529096aaaee

相关文章:

访问 Microsoft SQL Server 元数据的三种

上海微创软件有限公司 肖桂东适用读者&#xff1a;Microsoft SQL Server 中、高级用户元数据简介元数据 (metadata) 最常见的定义为"有关数据的结构数据"&#xff0c;或者再简单一点就是"关于数据的信息"&#xff0c;日常生活中的图例、图书馆目录卡和名片…

apply()智用:需要几个参数但只有一个参数数组

比奇堡的居民海绵宝宝&#xff0c;派大星&#xff0c;蟹老板正在开party let arr [SpongeBob,Patrick,Mr.Crab]; 如果章鱼哥来了&#xff0c;珊迪也来了 arr.push(Squidward Tentacles,Sandy); 此时一共派对有5人 如果章鱼哥和珊迪一起来了 arr.push.apply(arr,[Squidwar…

HTML转WORD WORD转PDF--来源网络

从网上找的代码&#xff0c;先收藏下。 功能&#xff1a;实现HTML转WORD&#xff0c;WORD转PDF view source print?using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using Syst…

正则表达式小结

基础 数量匹配 *代表{0,} 代表{1,} ?代表{0,1} [0-9][0-9]{1,} 表示匹配0~9中的任意数字&#xff0c;并且至少1位。 [0-9]*[0-9]{0,} 表示匹配0~9中的任意数字&#xff0c;并且可以是0位&#xff08;不存在&#xff09;。 [0-9]?[0-9]{0,1} 表示匹配0~9中的任意数字&…

Log控制台打印设置

android Log.isLoggable方法的使用 android 动态控制logcat日志开关,通过Log.isLoggable&#xff08;TAG,level&#xff09;方法动态控制&#xff0c;1.添加日志的时候加入判断&#xff0c; String TAG"Volley"; boolean isDbugLog.isLoggable(TAG, Lo…

JavaScript 定义类时如何将方法提取出来

现在我们有一个海洋生物类 function marineLife(name,job,friend){this.name name;this.job job;this.friend friend;this.introduceMyself function(){console.log(大家好&#xff01;我叫this.name,我是一名this.job,我最好的朋友是this.friend);} } 让我们用这个类新建…

错误:Error #2032解决方案

问题&#xff1a; Error #2032错误要访问外部数据&#xff0c;必须信任此文件。 现象&#xff1a; 要访问外部数据&#xff0c;必须信任此文件。 对于 PDF 文件&#xff0c;在 Adobe Reader 中&#xff0c;单击“Edit”&#xff08;编辑&#xff09;>“Preferences”&#…

[转]c# 泛类型(泛型) 以及强类型与弱类型的 理解及优化

[泛型的概念]&#xff08;1&#xff09;没有泛型的时候&#xff0c;所有的对象都是以object为基础&#xff0c;如果要使用时必须进行强制类型转换&#xff0c;如果对于值类型&#xff0c;则会导致不断拆箱装箱的过程&#xff0c;对系统消耗很大。&#xff08;2&#xff09;使用…

docker宿主机访问docker容器服务失败

2019独角兽企业重金招聘Python工程师标准>>> 原因&#xff1a; 因为docker的虚拟ip网段是172.17.*。*与局域网的ip网段172.17冲突了&#xff0c;所以有两种方式&#xff1a; 解决方法&#xff1a; 一、 修改docker网卡信息&#xff0c;将网段改为与局域网不同的即可…

从变量地址到指针再到指针变量

标题中的3个概念&#xff1a;变量地址、指针and指针变量是依次出现的&#xff0c;逐个确定就可以明晰到底什么是我们常说的指针(其实是指针变量) 首先&#xff0c;变量在本质上是一段存储空间&#xff0c;既然是存储空间&#xff0c;就必有地址&#xff0c;一般每个字节有一个…

Lua bind 和 conf 实现

Lua &#xff0c;语法简单&#xff08;极像javascript), 移植性好(纯C实现), 启动速度快&#xff0c;空间占用小&#xff0c; 真不愧是潜入式脚本语言之王。 本人想拿它来做 配置文件(conf)&#xff0c;也想加一点IoC, 就是配置脚本可以调用主程序的函数。 实现如下&#xff1…

通过反射执行get、set方法

Class clazz sourceObj.getClass(); 1、获取所有属性 BeanInfo beanInfo Introspector.getBeanInfo(clazz); PropertyDescriptor[] pds beanInfo.getPropertyDescriptors(); 2、获取指定属性 PropertyDescriptor pd new PropertyDescriptor(fieldName, clazz); Method getM…

h5 移动端 关于监测切换程序到后台或息屏事件和visibilitychange的使用

需求&#xff1a;当我们页面上正在播放视频或者播放背景音乐时&#xff0c;我们屏幕自动息屏或者切换程序去看消息时&#xff0c;我们希望暂停视频或背景音乐&#xff0c;回到程序我们希望继续播放视频或播放背景音乐。小程序上提供了 onUnload返回 onHide退出 onShow重新进入等…

一份整理 | PyTorch是什么,为何选择它

PyTorch是什么 PyTorch的特性 PyTorch是什么 PyTorch是一个基于Python的科学计算包&#xff0c;主要提供以下两种用途&#xff1a; 在GPU算力或其他加速器上作为NumPy的替代一个用于实现神经网络的自动求导库 PyTorch的特性 PyTorch的祖先是Chainer,HIPS autograd,twitter…

jquery实现心算练习

看看大家做完要多长时间&#xff0c;不能上传附近&#xff0c;就只得贴代码。代码如下&#xff1a; 代码 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">2 <htm…

C/C++利用三元组实现稀疏矩阵运算

三元组&#xff08;&#xff08;x&#xff0c;y&#xff09;&#xff0c;z&#xff09;其中&#xff08;x&#xff0c;y&#xff09;表示非零元位置&#xff0c;z表示该坐标的值 由于实际操作时&#xff0c;我们所用的矩阵0非常多&#xff0c;所以一个一个输入值很浪费时间&…

Database项目中关于Procedure sp_refreshsqlmodule_internal的错误

最近项目中发现一怪问题&#xff0c;使用DB项目发布数据库时&#xff0c;总提示 “(110,1): SQL72014: .Net SqlClient Data Provider: Msg 1222, Level 16, State 56, Procedure sp_refreshsqlmodule_internal, Line 67 Lock request time out period exceeded. An error occu…

脱离公式谈谈对反向传播算法的理解

机器学习的训练过程可看作是最优化问题的求解过程。 根据原理 对于函数f(x),如果f(x)在点xt附近是连续可微的&#xff0c;那么f(x)下降最快的方向是f(x)在xt点的梯度的反方向 得到最简单最常用的优化算法&#xff1a;梯度下降法(Gradient Descent Method)。 可以想见&#xf…

如何在Form中使用键弹性域(Key Flexfield)

在应用弹性域之前必须先定义弹性域&#xff0c;定义之前必须先注册表列。如果你的弹性域已经在Oracle Application Object Library中已经定义和注册了&#xff0c;并且弹性域表和列已经在数据库中存在&#xff0c;则忽略1、2、3步骤&#xff0c;适用于关键性也适用于描述性弹性…

什么是SOLID原则(第3部分)

让我们从最后一个 SOLID 原则开始吧&#xff0c;即依赖倒置原则&#xff08;Dependency Inversion Principle&#xff0c;简称 DIP&#xff09;&#xff08;不要和依赖注入Dependency Injection &#xff0c;DI 弄混淆了&#xff09;。这个原则所说的是高级模块不应该依赖具象的…

李彦宏,韩寒等入围本年度《时代百人》候选名单

美国《时代》杂志周六&#xff08;4月3日&#xff09;公布了2010年度 “百位全球最具影响力人物”的200名候选人名单,其中中国最大网络搜索公司“百度”总裁李彦宏也以成功企业家入围候选人,同时入围的还有年仅27岁的80后作家韩寒。 其它“全球最具影响力人物”候选人名单中还包…

win10如何查看NVIDIA驱动的版本

入口 输入&#xff1a;控制面板 选择&#xff1a;硬件和声音 选择NVIDIA控制面板 点击小房子图标 看到版本是391.25

vb中5种打开文件夹浏览框的方法总结(转)

代码 众所周知&#xff0c;在vb中如果是打开某一个文件的话&#xff0c;非常简单&#xff0c;使用CommonDialog组件即可轻松完成&#xff0c;但是他只能选择文件&#xff0c;之后或许选取的文件路径&#xff0c;而如果想要浏览文件夹&#xff0c;就没这么方便了。这里介绍3个办…

R语言文摘:Subsetting Data

原文地址&#xff1a;https://www.statmethods.net/management/subset.html R has powerful indexing features for accessing object elements. These features can be used to select and exclude variables and observations. The following code snippets demonstrate ways…

Ubuntu系统

1. Ubuntu 14.04 LTS安装 直接从官网下载Ubuntu14.04.2LTS http://www.ubuntu.com/download/desktop (你也可以下载最新的14.10---据说改变不大) 个人采用的是U盘安装,用了UltraISO这款软件(百度软件中心中便有---可以不破解试用来完成目的):具体流程: UltraISO上端文件打开,将…

win10下Anaconda如何查看PyTorch版本

以管理员身份打开Anaconda Powershell Prompt 按顺序输入以下三行命令即可

6年iOS开发程序员总结组件化—让你的项目一步到位

纯个人学习笔记分享, 不喜勿喷,自行取关! 技术不缺乏缔造者,网络不缺乏键盘侠,但缺乏分享技术的源动力! 近几年组件化大家吵的沸沸扬扬的&#xff0c;它其实也不是什么黄金圣衣&#xff0c;穿上立马让你的小宇宙提升几个档次&#xff0c;也不是海皇的三叉戟&#xff0c;入手就能…

处理问题的方法--抽象和特例化

事实上我们在软件开发的过程中总是&#xff1a;遇到问题&#xff0c;解决问题&#xff0c;这么一个 简单的过程。处理一般类似问题的时候&#xff0c;我们经过抽象&#xff0c;有的提取算法&#xff0c;有的提取结构&#xff0c;有的提取流程等等&#xff0c;这样的过程可以简单…

121-Best Time to Buy and Sell Stock

题目&#xff1a; Say you have an array for which the ith element is the price of a given stock on day i. If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum p…

控制行输入以下两句命令16倍速播放青年大学习

//得到视频标签 playRate document.getElementsByTagName(video); //改变播放速率 playRate.Bvideo.playbackRate 16;