OkHttp 现在统治了Android的网络请求领域,最常用的框架是:Retrofit+okhttp。OkHttp的实现原理和设计思想是必须要了解的,读懂和理解流行的框架也是程序员进阶的必经之路,代码和语言只是工具,重要的是思想。
在OKhttp 源码解析之前,我们必须先要了解http的相关基础知识,任何的网络请求都离不开http。
okhttp的源码分析,网上有好多博客讲解,但讲解的都是一些源码可有可无的知识,并没有将okhttp的核心设计思想讲解到位,我们阅读一些框架的源码,学习的其实就是其设计思想,了解了整体的框架设计,在深入了解细节的实现会更加容易。
建议将okhttp的 源码 下载下来,用AndroidStudio 打开,整篇文章是根据源码的分析来学习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 @Nullable Proxy 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;//cookie final @Nullable Cache cache;//cache 缓存 final @Nullable InternalCache internalCache;//内部缓存 final SocketFactory socketFactory;//socket 工厂 final @Nullable SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https final @Nullable CertificateChainCleaner 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接口,代码如下:
@Override public 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)); } 复制代码
可以看到上述代码做了几件事:
@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state. @Override public RealCall clone() { return RealCall.newRealCall(client, originalRequest, forWebSocket); } 复制代码
下面我们着重看一下调度器的实现。
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 - host private 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+1 RealInterceptorChain 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 == null Response 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)的Response Response 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(); } } 复制代码
上述的代码,主要做了几件事:
上部分代码,其实就是没有网络的时候的处理。
那么下部分代码肯定是,有网络的时候处理
//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 缓存Response Response 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; } 复制代码
下部分代码主要做了这几件事:
这样就体现出了,责任链这样实现的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。
这也是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 { @Override public 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 { @Override public 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 { @Override public 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; } @Override public String request() { return request; } @Override public 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); } } 复制代码
主要做了几件事:
//TODO 调度器执行同步请求 synchronized void executed(RealCall call) { runningSyncCalls.add(call); } 复制代码
可以看出,在同步请求的方法中,涉及到dispatcher 只是告知了执行状态,开始执行了(调用 executed),执行完毕了(调用 finished)其他的并没有涉及到。dispatcher 更多的是服务异步请求。
okhttp还有很多细节在本文中并没有涉及到,例如:okhttp是如何利用DiskLruCache实现缓存的、HTTP2/HTTPS 的支持等,本文主要讲解okhttp的核心设计思想,对整体有了清晰的认识之后,在深入细节,更容易理解。
简述okhttp的执行流程:
拆轮子系列:拆 OkHttp
OkHttp