OkHttp 原理解析
一、前言:
HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽。OkHttp是一个高效的HTTP客户端,它有以下默认特性:
支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接
连接池减少请求延时
透明的GZIP压缩减少响应数据的大小
缓存响应内容,避免一些完全重复的请求
当网络出现问题的时候OkHttp依然坚守自己的职责,它会自动恢复一般的连接问题,如果你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其他IP,OkHttp使用现代TLS技术(SNI, ALPN)初始化新的连接,当握手失败时会回退到TLS 1.0。
注意: OkHttp 支持 Android 2.3 及以上版本Android平台, 对于 Java, JDK 1.7及以上.
二、OkHttp 原理解析
1. OkHttp 的整体框架设计
建议将okhttp的 源码下载下来,用AndroidStudio 打开,整篇文章是根据源码的分析来学习okhttp的设计技巧和思想,如果本篇文章有内容分析不到位的地方,欢迎大家和我一起讨论。
下图为okhttp请求网络的完整流程图(大致看一遍)
2. okhttp的使用方法
1 OkHttpClient client = new OkHttpClient();
我们第一步先看一下okhttp的构造函数OkHttpClient()和一些配置相关,大致了解一下。
1 public OkHttpClient() { 2 this(new Builder()); 3 }
原来OkHttpClient 内部已经实现了**OkHttpClient(Builder builder),**如果我们不需要配置client,okhttp已将帮我们默认实现了配置。
1 public Builder() { 2 dispatcher = new Dispatcher(); 3 protocols = DEFAULT_PROTOCOLS; 4 connectionSpecs = DEFAULT_CONNECTION_SPECS; 5 eventListenerFactory = EventListener.factory(EventListener.NONE); 6 proxySelector = ProxySelector.getDefault(); 7 cookieJar = CookieJar.NO_COOKIES; 8 socketFactory = SocketFactory.getDefault(); 9 hostnameVerifier = OkHostnameVerifier.INSTANCE; 10 certificatePinner = CertificatePinner.DEFAULT; 11 proxyAuthenticator = Authenticator.NONE; 12 authenticator = Authenticator.NONE; 13 connectionPool = new ConnectionPool(); 14 dns = Dns.SYSTEM; 15 followSslRedirects = true; 16 followRedirects = true; 17 retryOnConnectionFailure = true; 18 connectTimeout = 10_000; 19 readTimeout = 10_000; 20 writeTimeout = 10_000; 21 pingInterval = 0; 22 }
如果需要一些配置如添加拦截器等,则需要这样调用即可:
1 mOkHttpClient = new OkHttpClient.Builder() 2 .addInterceptor(loggingInterceptor) 3 .retryOnConnectionFailure(true) 4 .connectTimeout(TIME_OUT, TimeUnit.SECONDS) 5 .readTimeout(TIME_OUT, TimeUnit.SECONDS) 6 .build();
我们看一下OkHttpClient 都有那些属性,稍微了解一下即可。后面在分析
1 final Dispatcher dispatcher;//调度器 2 final @Nullable 3 Proxy proxy;//代理 4 final List<Protocol> protocols;//协议 5 final List<ConnectionSpec> connectionSpecs;//传输层版本和连接协议 6 final List<Interceptor> interceptors;//拦截器 7 final List<Interceptor> networkInterceptors;//网络拦截器 8 final EventListener.Factory eventListenerFactory; 9 final ProxySelector proxySelector;//代理选择器 10 final CookieJar cookieJar;//cookie 11 final @Nullable 12 Cache cache;//cache 缓存 13 final @Nullable 14 InternalCache internalCache;//内部缓存 15 final SocketFactory socketFactory;//socket 工厂 16 final @Nullable 17 SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https 18 final @Nullable 19 CertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名 20 final HostnameVerifier hostnameVerifier;//主机名字确认 21 final CertificatePinner certificatePinner;//证书链 22 final Authenticator proxyAuthenticator;//代理身份验证 23 final Authenticator authenticator;//本地省份验证 24 final ConnectionPool connectionPool;//链接池 复用连接 25 final Dns dns; //域名 26 final boolean followSslRedirects;//安全套接层重定向 27 final boolean followRedirects;//本地重定向 28 final boolean retryOnConnectionFailure;//重试连接失败 29 final int connectTimeout;//连接超时 30 final int readTimeout;//读取超时 31 final int writeTimeout;//写入超时
3. 请求网络
1 String run(String url) throws IOException { 2 Request request = new Request.Builder() 3 .url(url) 4 .build(); 5 6 Response response = client.newCall(request).execute(); 7 return response.body().string(); 8 }
从源码中可以看出 okhttp 实现了Call.Factory接口
1 interface Factory { 2 Call newCall(Request request); 3 }
我们看一下okhttpClient 如何实现的Call接口,代码如下:
1 @Override 2 public Call newCall(Request request) { 3 return RealCall.newRealCall(this, request, false /* for web socket */); 4 }
可以看出 真正的请求交给了 RealCall 类,并且RealCall 实现了Call方法,RealCall是真正的核心代码。
RealCall 主要方法:
- 同步请求 :client.newCall(request).execute();
- 异步请求: client.newCall(request).enqueue();
下面我们着重分析一下异步请求,因为在项目中所有的网络请求基本都是异步的,同步很少用到,最后我们在分析一下同步请求即可。
4. 异步请求
跟着源码 走一遍 RealCall.enqueue() 的实现
1 #RealCall.java 2 3 @Override public void enqueue(Callback responseCallback) { 4 //TODO 不能重复执行 5 synchronized (this) { 6 if (executed) throw new IllegalStateException("Already Executed"); 7 executed = true; 8 } 9 captureCallStackTrace(); 10 eventListener.callStart(this); 11 //TODO 交给 dispatcher调度器 进行调度 12 client.dispatcher().enqueue(new AsyncCall(responseCallback)); 13 }
可以看到上述代码做了几件事:
1、synchronized (this) 确保每个call只能被执行一次不能重复执行,如果想要完全相同的call,可以调用如下方法:进行克隆
1 @SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state. 2 @Override public RealCall clone() { 3 return RealCall.newRealCall(client, originalRequest, forWebSocket); 4 }
2、利用dispatcher调度器,来进行实际的执行client.dispatcher().enqueue(new AsyncCall(responseCallback));,在上面的OkHttpClient.Builder可以看出 已经初始化了Dispatcher。
下面我们着重看一下调度器的实现。
5. Dispatcher 调度器
Dispatcher#enqueue 的方法实现如下:
1 //TODO 执行异步请求 2 synchronized void enqueue(AsyncCall call) { 3 //TODO 同时请求不能超过并发数(64,可配置调度器调整) 4 //TODO okhttp会使用共享主机即 地址相同的会共享socket 5 //TODO 同一个host最多允许5条线程通知执行请求 6 if (runningAsyncCalls.size() < maxRequests && 7 runningCallsForHost(call) < maxRequestsPerHost) { 8 //TODO 加入运行队列 并交给线程池执行 9 runningAsyncCalls.add(call); 10 //TODO AsyncCall 是一个runnable,放到线程池中去执行,查看其execute实现 11 executorService().execute(call); 12 } else { 13 //TODO 加入等候队列 14 readyAsyncCalls.add(call); 15 } 16 }
从上述代码可以看到Dispatcher将call 加入到队列中,然后通过线程池来执行call。
可能大家看了还是懵懵的,我们先了解一下Dispatcher几个属性和方法
1 //TODO 同时能进行的最大请求数 2 private int maxRequests = 64; 3 //TODO 同时请求的相同HOST的最大个数 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]] 4 //TODO 如 https://restapi.amap.com restapi.amap.com - host 5 private int maxRequestsPerHost = 5; 6 /** 7 * Ready async calls in the order they'll be run. 8 * TODO 双端队列,支持首尾两端 双向开口可进可出,方便移除 9 * 异步等待队列 10 * 11 */ 12 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); 13 14 /** 15 * Running asynchronous calls. Includes canceled calls that haven't finished yet. 16 * TODO 正在进行的异步队列 17 */ 18 private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
很明显,okhttp 可以进行多个并发网络请求,并且可以设置最大的请求数
executorService() 这个方法很简单,只是创建了一个线程池
1 public synchronized ExecutorService executorService() { 2 if (executorService == null) { 3 //TODO 线程池的相关概念 需要理解 4 //TODO 核心线程 最大线程 非核心线程闲置60秒回收 任务队列 5 executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, 6 new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", 7 false)); 8 } 9 return executorService; 10 }
既然我们将 call放到了线程池中那么它是如何执行的呢?注意这里的call是AsyncCall。
我们看一下AsyncCall的实现:
1 final class AsyncCall extends NamedRunnable
NamedRunnable 是什么鬼?点进去看一下
1 public abstract class NamedRunnable implements Runnable { 2 protected final String name; 3 4 public NamedRunnable(String format, Object... args) { 5 this.name = Util.format(format, args); 6 } 7 8 @Override public final void run() { 9 String oldName = Thread.currentThread().getName(); 10 Thread.currentThread().setName(name); 11 try { 12 execute(); 13 } finally { 14 Thread.currentThread().setName(oldName); 15 } 16 } 17 18 protected abstract void execute(); 19 }
原来如此 AsyncCall其实就是一个 Runnable,线程池实际上就是执行了execute()。
我们在看一下AsyncCall的execute()
1 final class AsyncCall extends NamedRunnable { 2 @Override protected void execute() { 3 boolean signalledCallback = false; 4 try { 5 //TODO 责任链模式 6 //TODO 拦截器链 执行请求 7 Response response = getResponseWithInterceptorChain(); 8 //回调结果 9 if (retryAndFollowUpInterceptor.isCanceled()) { 10 signalledCallback = true; 11 responseCallback.onFailure(RealCall.this, new IOException("Canceled")); 12 } else { 13 signalledCallback = true; 14 responseCallback.onResponse(RealCall.this, response); 15 } 16 } catch (IOException e) { 17 if (signalledCallback) { 18 // Do not signal the callback twice! 19 Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); 20 } else { 21 eventListener.callFailed(RealCall.this, e); 22 responseCallback.onFailure(RealCall.this, e); 23 } 24 } finally { 25 //TODO 移除队列 26 client.dispatcher().finished(this); 27 } 28 } 29 }
从上述代码可以看出真正执行请求的是getResponseWithInterceptorChain(); 然后通过回调将Response返回给用户。
值得注意的finally 执行了client.dispatcher().finished(this); 通过调度器移除队列,并且判断是否存在等待队列,如果存在,检查执行队列是否达到最大值,如果没有将等待队列变为执行队列。这样也就确保了等待队列被执行。
1 private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { 2 int runningCallsCount; 3 Runnable idleCallback; 4 synchronized (this) { 5 //TODO calls 移除队列 6 if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); 7 //TODO 检查是否为异步请求,检查等候的队列 readyAsyncCalls,如果存在等候队列,则将等候队列加入执行队列 8 if (promoteCalls) promoteCalls(); 9 //TODO 运行队列的数量 10 runningCallsCount = runningCallsCount(); 11 idleCallback = this.idleCallback; 12 } 13 //闲置调用 14 if (runningCallsCount == 0 && idleCallback != null) { 15 idleCallback.run(); 16 } 17 } 18 19 private void promoteCalls() { 20 //TODO 检查 运行队列 与 等待队列 21 if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. 22 if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. 23 24 //TODO 将等待队列加入到运行队列中 25 for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { 26 AsyncCall call = i.next(); 27 //TODO 相同host的请求没有达到最大,加入运行队列 28 if (runningCallsForHost(call) < maxRequestsPerHost) { 29 i.remove(); 30 runningAsyncCalls.add(call); 31 executorService().execute(call); 32 } 33 34 if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. 35 } 36 }
真正的执行网络请求和返回响应结果:getResponseWithInterceptorChain(),下面我们着重分析一下这个方法:
每段代码我都加上了注释。
1 //TODO 核心代码 开始真正的执行网络请求 2 Response getResponseWithInterceptorChain() throws IOException { 3 // Build a full stack of interceptors. 4 //TODO 责任链 5 List<Interceptor> interceptors = new ArrayList<>(); 6 //TODO 在配置okhttpClient 时设置的intercept 由用户自己设置 7 interceptors.addAll(client.interceptors()); 8 //TODO 负责处理失败后的重试与重定向 9 interceptors.add(retryAndFollowUpInterceptor); 10 //TODO 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息 11 //TODO 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。 12 interceptors.add(new BridgeInterceptor(client.cookieJar())); 13 //TODO 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应 14 //TODO 设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改) 15 //TODO 可配置用户自己设置的缓存拦截器 16 interceptors.add(new CacheInterceptor(client.internalCache())); 17 //TODO 连接服务器 负责和服务器建立连接 这里才是真正的请求网络 18 interceptors.add(new ConnectInterceptor(client)); 19 if (!forWebSocket) { 20 //TODO 配置okhttpClient 时设置的networkInterceptors 21 //TODO 返回观察单个网络请求和响应的不可变拦截器列表。 22 interceptors.addAll(client.networkInterceptors()); 23 } 24 //TODO 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据 25 //TODO 进行http请求报文的封装与请求报文的解析 26 interceptors.add(new CallServerInterceptor(forWebSocket)); 27 28 //TODO 创建责任链 29 Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, 30 originalRequest, this, eventListener, client.connectTimeoutMillis(), 31 client.readTimeoutMillis(), client.writeTimeoutMillis()); 32 33 //TODO 执行责任链 34 return chain.proceed(originalRequest); 35 }
从上述代码中,可以看出都实现了Interceptor接口,这是okhttp最核心的部分,采用责任链的模式来使每个功能分开,每个Interceptor自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。
责任链模式是设计模式中的一种也相当简单参考链接,这里不在复述。
我们着重分析一下,okhttp的设计实现,如何通过责任链来进行传递返回数据的。
上述代码中可以看出interceptors,是传递到了RealInterceptorChain该类实现了Interceptor.Chain,并且执行了chain.proceed(originalRequest)。
其实核心代码就是chain.proceed() 通过该方法进行责任链的执行。
1 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, 2 RealConnection connection) throws IOException { 3 if (index >= interceptors.size()) throw new AssertionError(); 4 calls++; 5 //TODO 创建新的拦截链,链中的拦截器集合index+1 6 RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, 7 connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, 8 writeTimeout); 9 //TODO 执行当前的拦截器-如果在配置okhttpClient,时没有设置intercept默认是先执行:retryAndFollowUpInterceptor 拦截器 10 Interceptor interceptor = interceptors.get(index); 11 //TODO 执行拦截器 12 Response response = interceptor.intercept(next); 13 return response; 14 }
从上述代码,我们可以知道,新建了一个RealInterceptorChain 责任链 并且 index+1,然后 执行interceptors.get(index); 返回Response。
其实就是按顺序执行了拦截器,这里我画了一个简图:
拦截器的执行顺序便是如上图这样执行的。
这样设计的一个好处就是,责任链中每个拦截器都会执行chain.proceed()方法之前的代码,等责任链最后一个拦截器执行完毕后会返回最终的响应数据,而chain.proceed() 方法会得到最终的响应数据,这时就会执行每个拦截器的chain.proceed()方法之后的代码,其实就是对响应数据的一些操作。
CacheInterceptor 缓存拦截器就是很好的证明,我们来通过CacheInterceptor 缓存拦截器来进行分析,大家就会明白了。
CacheInterceptor 的实现如下:
代码比较长,我们一步一步的来进行分析。
首先我们先分析上部分代码当没有网络的情况下是如何处理获取缓存的。
1 @Override public Response intercept(Chain chain) throws IOException 2 { 3 //TODO 获取request对应缓存的Response 如果用户没有配置缓存拦截器 cacheCandidate == null 4 Response cacheCandidate = cache != null 5 ? cache.get(chain.request()) 6 : null; 7 8 //TODO 执行响应缓存策略 9 long now = System.currentTimeMillis(); 10 CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); 11 //TODO 如果networkRequest == null 则说明不使用网络请求 12 Request networkRequest = strategy.networkRequest; 13 //TODO 获取缓存中(CacheStrategy)的Response 14 Response cacheResponse = strategy.cacheResponse; 15 16 if (cache != null) { 17 cache.trackResponse(strategy); 18 } 19 //TODO 缓存无效 关闭资源 20 if (cacheCandidate != null && cacheResponse == null) { 21 closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. 22 } 23 24 // If we're forbidden from using the network and the cache is insufficient, fail. 25 //TODO networkRequest == null 不实用网路请求 且没有缓存 cacheResponse == null 返回失败 26 if (networkRequest == null && cacheResponse == null) { 27 return new Response.Builder() 28 .request(chain.request()) 29 .protocol(Protocol.HTTP_1_1) 30 .code(504) 31 .message("Unsatisfiable Request (only-if-cached)") 32 .body(Util.EMPTY_RESPONSE) 33 .sentRequestAtMillis(-1L) 34 .receivedResponseAtMillis(System.currentTimeMillis()) 35 .build(); 36 } 37 38 //TODO 不使用网络请求 且存在缓存 直接返回响应 39 // If we don't need the network, we're done. 40 if (networkRequest == null) { 41 return cacheResponse.newBuilder() 42 .cacheResponse(stripBody(cacheResponse)) 43 .build(); 44 } 45 }
上述的代码,主要做了几件事:
- 如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的Response,否则 cacheCandidate = null;同时从CacheStrategy 获取cacheResponse 和 networkRequest
- 如果cacheCandidate != null 而 cacheResponse == null 说明缓存无效清楚cacheCandidate缓存。
- 如果networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。
- 如果networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。
上部分代码,其实就是没有网络的时候的处理。
那么下部分代码肯定是,有网络的时候处理
1 //TODO 执行下一个拦截器 2 Response networkResponse = null; 3 try { 4 networkResponse = chain.proceed(networkRequest); 5 } finally { 6 // If we're crashing on I/O or otherwise, don't leak the cache body. 7 if (networkResponse == null && cacheCandidate != null) { 8 closeQuietly(cacheCandidate.body()); 9 } 10 } 11 12 //TODO 网络请求 回来 更新缓存 13 // If we have a cache response too, then we're doing a conditional get. 14 //TODO 如果存在缓存 更新 15 if (cacheResponse != null) { 16 //TODO 304响应码 自从上次请求后,请求需要响应的内容未发生改变 17 if (networkResponse.code() == HTTP_NOT_MODIFIED) { 18 Response response = cacheResponse.newBuilder() 19 .headers(combine(cacheResponse.headers(), networkResponse.headers())) 20 .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) 21 .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) 22 .cacheResponse(stripBody(cacheResponse)) 23 .networkResponse(stripBody(networkResponse)) 24 .build(); 25 networkResponse.body().close(); 26 27 // Update the cache after combining headers but before stripping the 28 // Content-Encoding header (as performed by initContentStream()). 29 cache.trackConditionalCacheHit(); 30 cache.update(cacheResponse, response); 31 return response; 32 } else { 33 closeQuietly(cacheResponse.body()); 34 } 35 } 36 //TODO 缓存Response 37 Response response = networkResponse.newBuilder() 38 .cacheResponse(stripBody(cacheResponse)) 39 .networkResponse(stripBody(networkResponse)) 40 .build(); 41 42 if (cache != null) { 43 if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { 44 // Offer this request to the cache. 45 CacheRequest cacheRequest = cache.put(response); 46 return cacheWritingResponse(cacheRequest, response); 47 } 48 49 if (HttpMethod.invalidatesCache(networkRequest.method())) { 50 try { 51 cache.remove(networkRequest); 52 } catch (IOException ignored) { 53 // The cache cannot be written. 54 } 55 } 56 } 57 58 return response; 59 }
下部分代码主要做了这几件事:
- 执行下一个拦截器,也就是请求网络
- 责任链执行完毕后,会返回最终响应数据,如果缓存存在更新缓存,如果缓存不存在加入到缓存中去。
这样就体现出了,责任链这样实现的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。
这也是okhttp设计的最优雅最核心的功能。
当然我们可以通过一个小例子来进行验证,实践才最重要。
首先我们模拟一个 拦截器的接口
1 /** 2 * @author prim 3 * @version 1.0.0 4 * @desc 模拟okhttp拦截器 5 * @time 2018/8/3 - 下午4:29 6 */ 7 public interface Interceptor { 8 String interceptor(Chain chain); 9 10 interface Chain { 11 String request(); 12 13 String proceed(String request); 14 } 15 }
然后在实现几个拦截器
1 public class BridgeInterceptor implements Interceptor { 2 @Override 3 public String interceptor(Chain chain) { 4 System.out.println("执行 BridgeInterceptor 拦截器之前代码"); 5 String proceed = chain.proceed(chain.request()); 6 System.out.println("执行 BridgeInterceptor 拦截器之后代码 得到最终数据:"+proceed); 7 return proceed; 8 } 9 } 10 11 public class RetryAndFollowInterceptor implements Interceptor { 12 @Override 13 public String interceptor(Chain chain) { 14 System.out.println("执行 RetryAndFollowInterceptor 拦截器之前代码"); 15 String proceed = chain.proceed(chain.request()); 16 System.out.println("执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:" + proceed); 17 return proceed; 18 } 19 } 20 21 public class CacheInterceptor implements Interceptor { 22 @Override 23 public String interceptor(Chain chain) { 24 System.out.println("执行 CacheInterceptor 最后一个拦截器 返回最终数据"); 25 return "success"; 26 } 27 }
然后实现Chain 接口
1 public class RealInterceptorChain implements Interceptor.Chain { 2 3 private List<Interceptor> interceptors; 4 5 private int index; 6 7 private String request; 8 9 public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) { 10 this.interceptors = interceptors; 11 this.index = index; 12 this.request = request; 13 } 14 15 @Override 16 public String request() { 17 return request; 18 } 19 20 @Override 21 public String proceed(String request) { 22 if (index >= interceptors.size()) return null; 23 24 //获取下一个责任链 25 RealInterceptorChain next = new RealInterceptorChain(interceptors, index+1, request); 26 // 执行当前的拦截器 27 Interceptor interceptor = interceptors.get(index); 28 29 return interceptor.interceptor(next); 30 } 31 }
然后进行测试,看看我们是否分析的正确
1 List<Interceptor> interceptors = new ArrayList<>(); 2 interceptors.add(new BridgeInterceptor()); 3 interceptors.add(new RetryAndFollowInterceptor()); 4 interceptors.add(new CacheInterceptor()); 5 6 RealInterceptorChain request = new RealInterceptorChain(interceptors, 0, "request"); 7 8 request.proceed("request");
打印的log日志如下:
1 执行 BridgeInterceptor 拦截器之前代码 2 执行 RetryAndFollowInterceptor 拦截器之前代码 3 执行 CacheInterceptor 最后一个拦截器 返回最终数据 4 执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:success 5 执行 BridgeInterceptor 拦截器之后代码 得到最终数据:success
OK 完美,验证没有问题,我想至此大家都应该懂了 okhttp的核心设计思想了。
okhttp的其他拦截器的具体实现大家可以自己研究一下即可,okhttp的这种设计思想我们完全可以应用到项目中去,解决一些问题。
5. 同步请求
这里在稍微讲一下,okhttp的同步请求,代码很简单
同样是在RealCall 类中实现的
1 //TODO 同步执行请求 直接返回一个请求的结果 2 @Override public Response execute() throws IOException { 3 synchronized (this) { 4 if (executed) throw new IllegalStateException("Already Executed"); 5 executed = true; 6 } 7 captureCallStackTrace(); 8 //TODO 调用监听的开始方法 9 eventListener.callStart(this); 10 try { 11 //TODO 交给调度器去执行 12 client.dispatcher().executed(this); 13 //TODO 获取请求的返回数据 14 Response result = getResponseWithInterceptorChain(); 15 if (result == null) throw new IOException("Canceled"); 16 return result; 17 } catch (IOException e) { 18 eventListener.callFailed(this, e); 19 throw e; 20 } finally { 21 //TODO 执行调度器的完成方法 移除队列 22 client.dispatcher().finished(this); 23 } 24 }
主要做了几件事:
- synchronized (this) 避免重复执行,上面的文章部分有讲。
- client.dispatcher().executed(this); 实际上调度器只是将call 加入到了同步执行队列中。代码如下:
1 //TODO 调度器执行同步请求 2 synchronized void executed(RealCall call) { 3 runningSyncCalls.add(call); 4 }
3.getResponseWithInterceptorChain()最核心的代码,上面已经分析过了,请求网络得到响应数据,返回给用户。
4.client.dispatcher().finished(this); 执行调度器的完成方法 移除队列
可以看出,在同步请求的方法中,涉及到dispatcher 只是告知了执行状态,开始执行了(调用 executed),执行完毕了(调用 finished)其他的并没有涉及到。dispatcher 更多的是服务异步请求。
三、总结
1. 异步请求线程池-OkHttp Dispatcher
1 public synchronized ExecutorService executorService() { 2 if (executorService == null) { 3 executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, 4 new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); 5 } 6 return executorService; 7 }
- 该线程池与Android下的 Executors.newCachedThreadPool() 比较类似;
- 无任务上限,自动回收闲置60s的线程,适用于大量耗时较短的任务;
- 虽然线程池无任务上限,但是Dispatcher对入口enqueue()进行了把关,最大的异步任务数默认是64,同一个主机默认是5,当然这两个默认值是可以修改的,Dispatcher提供的修改接口;
1 okHttpClient.dispatcher().setMaxRequests(128); 2 okHttpClient.dispatcher().setMaxRequestsPerHost(10);
2. 连接池清理线程池-OkHttp ConnectionPool
1 /** 2 * Background threads are used to cleanup expired connections. There will be at most a single 3 * thread running per connection pool. The thread pool executor permits the pool itself to be 4 * garbage collected. 5 */ 6 private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */, 7 Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS, 8 new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
- 该线程池用来清理长时间闲置的和泄漏的连接;
- 该线程池本身无任务上限,线程闲置60s自动回收;
- 虽然任务无上限,但其通过 cleanupRunning 标记来控制只有一个线程在运行,当连接池中没有连接后才会被重新设置为 false;
3. 缓存整理线程池-OkHttp DiskLruCache
1 // Use a single background thread to evict entries. 2 Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, 3 new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true));
- 该线程池用于整理本地请求缓存数据;
- 缓存的整理包含: 达到阀值大小的文件,删除最近最少使用的记录,在有关操作达到一定数量以后对记录进行重建;
- 最大运行线程数1,无需考虑线程安全问题,自动回收闲置60s的线程;
4. HTTP2异步事务线程池-OkHttp Http2Connection
- HTTP2采用了多路复用,因此需要维护连接有效性,本线程池就是用于维护相关的各类HTTP2事务;
- 线程池本身无任务上限,自动回收闲置60s的线程;
- 每一个HTTP2连接都有这么一个线程池存在;
四、采用的设计模式:
1. 对象创建型模式(Builder模式)
使用Builder模式处理需要很多参数的构造函数,提高代码可读性。(Builder模式的优点)
比如:OkHttpClient、Request、Response、MultipartBody、HttpUrl
1 public OkHttpClient() { 2 this(new Builder()); 3 } 4 public Builder() { 5 dispatcher = new Dispatcher(); 6 protocols = DEFAULT_PROTOCOLS; 7 connectionSpecs = DEFAULT_CONNECTION_SPECS; 8 proxySelector = ProxySelector.getDefault(); 9 cookieJar = CookieJar.NO_COOKIES; 10 socketFactory = SocketFactory.getDefault(); 11 hostnameVerifier = OkHostnameVerifier.INSTANCE; 12 certificatePinner = CertificatePinner.DEFAULT; 13 proxyAuthenticator = Authenticator.NONE; 14 authenticator = Authenticator.NONE; 15 connectionPool = new ConnectionPool(); 16 dns = Dns.SYSTEM; 17 followSslRedirects = true; 18 followRedirects = true; 19 retryOnConnectionFailure = true; 20 connectTimeout = 10_000; 21 readTimeout = 10_000; 22 writeTimeout = 10_000; 23 } 24 25 对象创建型模式(Builder模式)
下面例子比较可以很好理解Builder模式的好处。
本地缓存对象构造对比:
1 //如果这样构造对象可读性很差,虽然实现了功能 2 RxCache rxCache = new RxCache(new File(getCacheDir().getPath() + File.separator + "data-wuxiao"), 3 new DiskConverter(),2*1024*1024,40*1024*1024)
使用Builder模式
1 RxCache rxCache = new RxCache.Builder() 2 .diskDir(new File(getCacheDir().getPath() + File.separator + "data-wuxiao")) 3 .diskConverter(new DiskConverter()) 4 .memory(2*1024*1024) 5 .disk(40*1024*1024) 6 .build();
2. OkHttp - Interceptor责任链模式
Interceptor是 OkHttp 核心类,它把网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都是一个 Interceptor,它们再连接成一个 Interceptor.Chain,如链条一般,分工明确,完美完成一次网络请求。
1 private Response getResponseWithInterceptorChain() throws IOException { 2 // Build a full stack of interceptors. 3 List<Interceptor> interceptors = new ArrayList<>(); 4 interceptors.addAll(client.interceptors()); 5 interceptors.add(retryAndFollowUpInterceptor); 6 interceptors.add(new BridgeInterceptor(client.cookieJar())); 7 interceptors.add(new CacheInterceptor(client.internalCache())); 8 interceptors.add(new ConnectInterceptor(client)); 9 if (!retryAndFollowUpInterceptor.isForWebSocket()) { 10 interceptors.addAll(client.networkInterceptors()); 11 } 12 interceptors.add(new CallServerInterceptor( 13 retryAndFollowUpInterceptor.isForWebSocket())); 14 15 Interceptor.Chain chain = new RealInterceptorChain( 16 interceptors, null, null, null, 0, originalRequest); 17 return chain.proceed(originalRequest); 18 }
责任链其实在Android应用也存在(如:事件传递就是责任链机制)。
3. 工厂模式(Factory Method)
如果简单工厂也算设计模式的话,在okhttp中倒是有很多。不过工厂方法却用的不多。下面是其中2个:
1 public interface Call extends Cloneable { 2 ... 3 interface Factory { 4 Call newCall(Request request); 5 } 6 } 7 8 public interface WebSocket { 9 ... 10 interface Factory { 11 WebSocket newWebSocket(Request request, WebSocketListener listener); 12 } 13 }
4. 观察者模式(Observer)
观察者有两个,一个是EventListener,另一个是WebSocketListener。两者都可以看作是生命周期监听器,前者监听请求/响应,后者监听web socket。
1 public abstract class EventListener { 2 ... 3 public void requestHeadersStart(Call call) {} 4 public void requestHeadersEnd(Call call, Request request) {} 5 public void requestBodyStart(Call call) {} 6 public void requestBodyEnd(Call call, long byteCount) {} 7 public void responseHeadersStart(Call call) {} 8 public void responseHeadersEnd(Call call, Response response) {} 9 public void responseBodyStart(Call call) {} 10 public void responseBodyEnd(Call call, long byteCount) {} 11 ... 12 }
上面列出的方法被称作Request Events:
1 graph LR 2 requestHeaders --> requestBody 3 requestBody --> responseHeaders 4 responseHeaders --> responseBody
5. 策略模式(Strategy)
无论是源码,还是注释中很多地方都有strategy、policy这样的单词,但真正是策略模式的笔者只发现了CookieJar,定义了管理cookie的方法:
1 public interface CookieJar { 2 CookieJar NO_COOKIES = ... 3 4 void saveFromResponse(HttpUrl url, List<Cookie> cookies); 5 List<Cookie> loadForRequest(HttpUrl url); 6 }
其它诸如CacheStrategy、ConnectionSpecSelector,笔者认为都不是策略模式。
五、总结
本文主要讲解okhttp的核心设计思想,对整体有了清晰的认识之后,在深入细节,更容易理解。
简述okhttp的执行流程:
- OkhttpClient 实现了Call.Fctory,负责为Request 创建 Call;
- RealCall 为Call的具体实现,其enqueue() 异步请求接口通过Dispatcher()调度器利用ExcutorService实现,而最终进行网络请求时和同步的execute()接口一致,都是通过
- getResponseWithInterceptorChain() 函数实现
- getResponseWithInterceptorChain() 中利用 Interceptor 链条,责任链模式 分层实现缓存、透明压缩、网络 IO 等功能;最终将响应数据返回给用户。
转自:https://blog.csdn.net/weixin_39069034/article/details/100171954
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
2017-09-14 Android Studio调试报错am startservice