合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
[TOC] ### 文章链接: [Android OkHttp源码解析入门教程:同步和异步(一)](https://juejin.im/post/5c46822c6fb9a049ea394510) [Android OkHttp源码解析入门教程:拦截器和责任链(二)](https://juejin.im/post/5c4682d2f265da6130752a1d) ### **工欲善其事,必先利其器** Android API23(6.0)版本以后,[Google正式移除Apache-HttpClient](https://developer.android.com/about/versions/marshmallow/android-6.0-changes#behavior-apache-http-client)。[OkHttp](https://github.com/square/okhttp)作为一个现代,快速,高效的HttpClient,其功能之强大也是显而易见的 > * 支持[SPDY](https://zh.wikipedia.org/wiki/SPDY)可以合并多个请求到同一个主机的请求、连接池、[GZIP](https://baike.baidu.com/item/gzip/4487553)和HTTP缓存 > * 支持HTTP/2协议,通过HTTP/2 可以让客户端中到服务器的所有请求共用同一个Socket连接 > * 非HTTP/2 请求时,[OkHttp](https://github.com/square/okhttp)内部会维护一个线程池,通过线程池可以对HTTP/1.x的连接进行复用,减少延迟 > * 支持post,get请求,基于http的文件上传和下载 > * 默认情况下,[OkHttp](https://github.com/square/okhttp)会自动处理常见的网络问题,像二次连接、SSL的握手问题 当然[OkHttp](https://github.com/square/okhttp)的功能远不止这些,这里只是说明平时经常用到的。既然[OkHttp](https://github.com/square/okhttp)已经作为官方库使用,相比我们在做项目的时候也会用,但对于其底层的实现原理还是一知半解,那我们就从这篇文章开始解释其底层实现原理。开车前先来一波介绍: Ecplise引用:下载最新的[Jar](https://search.maven.org/search?q=g:com.squareup.okhttp3)包 Android studio引用:`implementation 'com.squareup.okhttp3:okhttp:3.11.0' //最新版本号请关注okhttp官网` Maven引用: ~~~ <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>3.11.0</version> //最新版本号请关注okhttp官网 </dependency> ~~~ >[info]注意:这里并没有以OKHttp最新版本来讲解,是因为最新版本是专为kotlin而写的 ### **一、基本使用方法** 流程如下 ~~~ // 启动客户端类,主要有两种方法进行创建,new对象和Builder内部类实现实例化 OkHttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build(); // get请求 // 通过Builder模式创建一个Request对象(即请求报文) // 这里可以设置请求基本参数:url地址,get请求,POST请求,请求头,cookie参数等 Request request = new Request.Builder() .url("http://www.baidu.com") .header("User-Agent", "xxx.java") .addHeader("token", "xxx") .get() .build(); // POST请求 // 表单形式上传 RequestBody body = new FormBody.Builder().add("xxx","xxx").build(); // JSON参数形式,File对象上传 RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json); RequestBody body = RequestBody.create(MediaType.parse("File/*"), file); Request request = new Request.Builder() .post(body) .url(url) .header("User-Agent", "xxx.java") .addHeader("token", "xxx") .build(); // 创建Call对象(Http请求) ,即连接Request和Response的桥梁 // newCall方法将request封装成Call对象 Call call = client.newCall(request); try{ // Response即响应报文信息,包含返回状态码,响应头,响应体等 Response response = call.execute(); // 这里深入一点,Call其实是一个接口,调用Call的execute()发送同步请求其实是调用了Realcall实现类的方法,Realcall从源码可以看出示一个Runable System.out.println(response.body().string()); }catch(IOException e){ e.printStackTrace(); } ~~~ 看完代码你可能觉得OkHttp基本流程很繁琐,但是去掉一些扩展参数,你会发现OkHttp的使用其实很简单,无非就是 > * 创建一个OkHttpClient并实例化,可设置相关参数连接时长connectTimeout等 > * 创建一个Request对象并实例化,可设置网络地址url,请求方式get,post,携带参数等; > * 创建一个[Call](https://github.com/square/okhttp/wiki/Calls)对象,通过okhttpClient的newCall()方法将Request封装成Call对象 > * 创建一个Response响应,用于接收服务器返回的相关信息; 即OkHttpClient客户端通过newCall()方法接受你的Request请求并生成Response响应 ### **二、同步和异步请求** 看到这里你可能会问,为什么不继续讲些关于文件上传,文件下载,Interceptors拦截器这些内容?其实同步和异步请求的实现可以说是源码中非常重要的一环,涉及到`线程池`,`Dispatch调度`,`反向代理`等,掌握核心科技,剩下的都是开胃小菜。 1)基本使用方法 **同步请求方法** ~~~ OkhttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build(); Request request = new Request.Builder() .url("http://www.baidu.com") .get() .build(); Call call = client.newCall(request); try{ Response response = call.execute();//调用同步请求 System.out.println(response.body().string()); }catch(IOException e){ e.printStackTrace(); } ~~~ 通过上面代码可以看出同步请求的基本流程: 1.创建OkHttpClient和Request对象 2.将Request封装成Call对象 3.调用Call的excute()发起同步请求 `*特别注意*:`当前线程发送同步请求后,就会进入`阻塞状态`,直到数据有响应才会停止(`和异步最大的不同点`) **异步请求方法** ~~~ OkhttpClient client = new OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).build(); Request request = new Request.Builder() .url("http://www.baidu.com") .get() .build(); Call call = client.newCall(request); call.enqueue(new Callback() { //调用异步请求,CallBack用于请求结束以后来进行接口回调 @Override public void onFailure(Call call, IOException e) { System.out.println("Failure");} @Override public void onResponse(Call call, Response response) throw s IOException { System.out.println(response.body().string()); } }); ~~~ 通过上面代码可以看出异步请求的基本流程: 1.创建OkHttpClient和Request对象 2.将Request封装成Call对象 3.调用Call的enqueue发起异步请求 `*特别注意*:`onFailure和onResponse都是执行在`子线程`中 不难看出,其实异步和同步请求的`不同点`在于 > * `发起请求方法调用` > * `是否阻塞线程` 到此,我们已经熟悉了OkHttp的同步,异步请求方法的基本使用;不管同步还是异步的调用都需要先初始化OkHttpClient,创建Request ,调用OkHttpClient.newcall()封装Call对象,但其内部又是如何实现的呢? **同步请求执行流程** 第一步:初始化OkHttpClient(Builder builder) ~~~ public Builder() { dispatcher = new Dispatcher(); // 调度分发器(核心之一) protocols = DEFAULT_PROTOCOLS; // 协议 connectionSpecs = DEFAULT_CONNECTION_SPECS; // 传输层版本和连接协议 eventListenerFactory = EventListener.factory(EventListener.NONE); // 监听器 proxySelector = ProxySelector.getDefault(); // 代理选择器 cookieJar = CookieJar.NO_COOKIES; // cookie socketFactory = SocketFactory.getDefault(); // socket 工厂 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; // 命令间隔 } ~~~ 从这里看到,其实OkHttpClient的初始化已经帮我们配置了基本参数,我们也可以根据自身业务需求进行相应的参数设置(失败重连,添加拦截器,cookie等等),一般遇到创建对象需要大量参数时,推荐使用[Builider模式](https://blog.csdn.net/justloveyou_/article/details/78298420)链式调用完成参数初始化,具体使用可以去Android源码中的AlertDialog、Notification中详细了解; 这里我们重点注意两个核心,[Dispatcher](https://blog.csdn.net/sk719887916/article/details/78534583)和[ConnectionPool](https://www.jianshu.com/p/e6fccf55ca01),这两点会在后面做详细讲解 `Dispatcher`:OkHttp请求的调度分发器,由它决定异步请求在线程池中是直接处理还是缓存等待,当然对于同步请求,只是将相应的同步请求放到请求队列当中执行 `ConnectionPool`: 统一管理客户端和服务器之间连接的每一个Connection,作用在于 > * 当你的Connection请求的URL相同时,可以选择是否复用; > * 控制Connection保持打开状态还是复用 第二步:创建 (Builder builder) ~~~ public static class Builder { HttpUrl url; String method; Headers.Builder headers; RequestBody body; //请求体 Object tag; //标签 public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); //Headers内部类 } ~~~ 这个构造方法很简单,在Request.Builder模式下默认指定请求方式为GET请求,创建了Headers内部类来保存头部信息,我们再来看build方法 ~~~ public Request build() { if (url == null) throw new IllegalStateException("url == null"); return new Request(this); } Request(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = builder.headers.build(); this.body = builder.body; this.tag = builder.tag != null ? builder.tag : this; } ~~~ Request的构造方法就是为其初始化指定需求的请求方式,请求URL,请求头部信息,这样就完成同步请求的前两步 第三步:调用OkHttpClient.newcall()封装Call对象 ~~~ /** * Prepares the {@code request} to be executed at some point in the future. * 准备在将来某个时候执行{@code请求} */ @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } ~~~ 上面我们也提到过,Call是一个接口,所以它的实际操作是在RealCall类中实现的 ~~~ final class RealCall implements Call { final OkHttpClient client; final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor; //重定向拦截器 /** * There is a cycle between the {@link Call} and {@link EventListener} that makes this awkward. * This will be set after we create the call instance then create the event listener instance. */ private EventListener eventListener; /** The application's original request unadulterated by redirects or auth headers. */ final Request originalRequest; final boolean forWebSocket; // Guarded by this. private boolean executed; //实际构造方法 private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; } } ~~~ 从这里就可以看到,RealCall其实是持有之前初始化好的OkHttpClient和Request对象,同时赋值了RetryAndFollowUpInterceptor重定向拦截器,关于拦截器的内容,我们会后面具体讲解OKhttp内部的5大拦截器; 第四步,调用call.exucte方法实现同步请求 ~~~ @Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); // 捕捉异常堆栈信息 eventListener.callStart(this); // 调用监听方法 try { client.dispatcher().executed(this); // 调度器将call请求 加入到了同步执行队列中 Response result = getResponseWithInterceptorChain(); // 获取返回数据 if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); } } ~~~ 首先, 加入了[synchronized](https://blog.csdn.net/javazejian/article/details/72828483)同步锁,判断executed标识位是否为true,确保每个call只能被执行一次不能重复执行,然后开启了eventListener监听事件,接收相应的事件回调,通过dispatcher将Call请求添加到同步队列中 ~~~ public Dispatcher dispatcher() { return dispatcher; } /** Used by {@code Call#execute} to signal it is in-flight. */ synchronized void executed(RealCall call) { runningSyncCalls.add(call); } /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ // 同步请求队列 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); ~~~ 每当调用executed同步方法时,dispather就会帮我们把同步请求添加到同步请求队列中去,由此可以看出Dispather调度器的作用就是`维持Call请求发送状态`和`维护线程池`并把`Call请求添加到相应的执行队列`当中,由它决定当前Call请求是缓存等待还是直接执行,流程如下 ![](https://img.kancloud.cn/87/76/8776fe020800481a0a7fefc858d75b6d_1240x495.jpg) getResponseWithInterceptorChain()是一个拦截器链,依次调用拦截器对返回的response进行相应的操作,我们在讲解到责任链模式时会详细介绍,如图 ![](https://img.kancloud.cn/cb/5e/cb5e644e22d0143cfcd59ca427311799_411x589.jpg) 另外要特别注意下`client.dispatcher().finished(this);` ~~~ /** Used by {@code Call#execute} to signal completion. */ void finished(RealCall call) { finished(runningSyncCalls, call, false); // 注意参数传递的值 } private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; // 闲置接口 synchronized (this) { if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); // 将等待队列的请求加入运行队列并开始执行,只会在异 步方法中调用 runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } } /***********************************************************************************************************************/ public synchronized int runningCallsCount() { return runningAsyncCalls.size() + runningSyncCalls.size(); } ~~~ 当同步请求完成后会调用finished()方法将队列中的请求清除掉,runningCallsCount()计算返回正在执行同步请求和正在执行异步请求的数量总和,最后判断如果runningCallsCount 为0的时候,表示整个Dispatcher分发器中没有可运行的请求,同时在满足idleCallback不为空的情况下,就调用Run方法开启闲置接口;这里可以看出,在同步请求的方法中,dispatcher的作用只是调用 executed将Call请求添加到同步队列中,执行完毕后调用 finished清除队列中的请求,可见dispatcher更多的是为异步服务 **异步请求执行流程** 关于OkHttpClient和Request初始化流程上文已经讲解,不清楚的可以返回去看看,所以直奔主题 第四步,调用call.enqueue方法实现异步请求 ~~~ //RealCall实现类 @Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; // executed用于表示Call请求是否执行过 } captureCallStackTrace();// 捕捉异常堆栈信息 eventListener.callStart(this);// 开启监听事件 client.dispatcher().enqueue(new AsyncCall(responseCallback)); } ~~~ 有没有发现和同步的excute方法很类似,都是先使用synchronized 防止请求重复执行,然后开启监听事件,最后在执行相应的方法,但奇怪的是同步在执行完excute方法后是直接通过getResponseWithInterceptorChain()返回数据,异步又是如何返回数据的呢?AsyncCall又是干什么的? ~~~ final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } @Override protected void execute() { boolean signalledCallback = false; try { // 返回数据 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 { client.dispatcher().finished(this); } } } ~~~ 这里的 AsyncCall 是 RealCall 的一个内部类,它继承于NamedRunnable抽象类,NamedRunnable抽象类又实现了 Runnable,所以可以被提交到[ExecutorService](https://www.cnblogs.com/zhaoyan001/p/7049627.html)上执行,在execute方法里,我们看到了熟悉的流程,上文也说到getResponseWithInterceptorChain是一个拦截器链,会依次执行相应的拦截器后返回数据,所以当返回数据后,通过retryAndFollowUpInterceptor重定向拦截器判断请求是否正常执行,并且通过Callback接口返回相应数据信息,最后调用finished方法清除队列 这里有个疑问,Dispatcher是通过什么把异步就绪队列的请求`调度分发`到异步执行队列中的? 还记得我们讲client.dispatcher().finished(this)的时候,说到过promoteCalls方法,只是同步传参的是false没有调用,但异步传参是true,所以promoteCalls方法才真正在异步中调用 ~~~ //Disaptcher /** 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<>(); private void promoteCalls() { // maxRequests最大请求数量64 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. } } ~~~ 源码是不是很清楚明了,原来异步队列就是在这里进行调度的,在for循环中,Disaptcher首先对异步就绪队列进行遍历,如果满足runningCallsForHost(当前调用请求主机数)小于maxRequestsPerHost( 最大请求主机数5个)并且异步并发数量没有超过最大请求数量64的前提下,就把异步就绪队列中最后一个元素移除加入到异步执行队列中 ![](https://img.kancloud.cn/5a/40/5a4025870e5ba47da95cacd5798aceef_1240x536.jpg) 我们接着看enqueue方法具体做了哪些操作 ~~~ //Disaptcher synchronized void enqueue(AsyncCall call) { // 异步并发请求数量不能超过最大请求数量64 // 当前网络请求的host是否小于5个请求的host if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { // 加入执行队列 并交给线程池执行 runningAsyncCalls.add(call); executorService().execute(call); } else { // 加入就绪队列等待 readyAsyncCalls.add(call); } } *************************************************************************************************************** public synchronized ExecutorService executorService() { // 核心线程 最大线程 非核心线程闲置60秒回收 任务队列 if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } ~~~ Disaptcher的enqueue方法只是做了一个异步请求的逻辑判断,即判断当前异步并发执行队列的数量是否超过最大承载运行数量64和相同host主机最多允许5条线程同时执行请求,满足以上条件,则将传进来的AsyncCall添加到异步执行队列,同时启动线程池执行,反之则添加到异步就绪队列中等待,[executorService](https://www.cnblogs.com/zhaoyan001/p/7049627.html)调用的就是AsyncCall的execute方法 ![](https://user-gold-cdn.xitu.io/2019/1/22/168736b72a5b07d1?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) 同步和异步请求的源码就讲到这里,对过程还有不理解的可以在下方评论中提出问题