1 概述

我们先来看一个使用OKHttp的典型例子

//builder模式创建一个Request
Request request = new Request.Builder().url("https://baidu.com").build();
//创建okHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建call
Call call = mOkHttpClient.newCall(request);
//异步方式加入队列中,并添加回调方法
call.enqueue(new Callback() {@Overridepublic void onFailure(Request request, IOException e) {}@Overridepublic void onResponse(final Response response) throws IOException {}});

OKHttp使用起来还是相当简单的,大家是否已经跃跃欲试想了解下它的底层实现呢。从使用中我们看到,它大致分为两步:Request的创建和Request的发送。现在就带大家一起来分析下这两个步骤。对Http协议不熟悉的同学,可以先看看我的这篇文章Http协议简介

2 Request创建流程分析

Request创建使用了十分典型的builder模式。

Request.javapublic static class Builder {public Builder() {// 默认采用的GET方式this.method = "GET";// request的首部也是采用builder模式创建this.headers = new Headers.Builder();}// url肯定是少不了的方法,不然可就要报Exception了哦~public Builder url(HttpUrl url) {if (url == null) throw new IllegalArgumentException("url == null");this.url = url;return this;}public Request build() {// 看到了吧,少了谁都不能少了urlif (url == null) throw new IllegalStateException("url == null");// builder模式最终创建出Request对象return new Request(this);}// Request类的构造方法private Request(Builder 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的创建是不是简单到小儿科了啊。为了扩展下大家视野,后面核心类讲解部分会详细说Request中的一些主要参数,这样咱们才可以掌握一些高级用法了。很期待,是不是?

3 Request发送流程分析

Request对象代表了客户端HTTP请求的数据,它被封装在Call这个对象中。这个过程也很简单,看下面的代码分析。

public class OkHttpClient implements Cloneable {// 包装Request对象public Call newCall(Request request) {return new Call(this, request);}protected Call(OkHttpClient client, Request originalRequest) {// 创建一个新的OKHttpClient对象,然后使用default的OKHttpClient赋值给它,不用关注这个方法,不是重点this.client = client.copyWithDefaults();this.originalRequest = originalRequest;}
}

下面就到了Request发送的主要过程了。使用Dispatcher调度器根据当前request总数目,立即执行request或先放入等待队列中。立即执行时采用了线程池方式利用子线程来执行。

public class Call {// request发送的起始点,一看enqueue小编就猜到跟队列啥的有关系了public void enqueue(Callback responseCallback) {enqueue(responseCallback, false);}void enqueue(Callback responseCallback, boolean forWebSocket) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}// 调用了Dispatcher对象的enqueue,将回调方法封装在了AsyncCall里面,我们接下来重点分析它们client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));}
}// 调度器,用来管理多个Request的发送的
public final class Dispatcher {// APP内当前存活总request数目最大值,setMaxRequests()方法可更改它private int maxRequests = 64;// 发往单个host的request的最大值。不清楚host的同学可以先简单将host理解为http://www.baidu.com中的www.baidu.com,// 它最终会被DNS解析为服务器的IP地址// setMaxRequestsPerHost()可更改它private int maxRequestsPerHost = 5;// 等待队列,合适时机时才开始runprivate final Deque<AsyncCall> readyCalls = new ArrayDeque<>();// 运行队列,里面的request可以得到立即执行。// 被cancel但没有finish的request也在这里面。所以要注意finish request哦,别占着茅坑不那啥哈~ private final Deque<AsyncCall> runningCalls = new ArrayDeque<>();// 调度器来安排将request放入运行队列并立即run,还是放到等待队列synchronized void enqueue(AsyncCall call) {if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {// 太幸运了,request数目还有空余,可以立即执行了runningCalls.add(call);// 典型的线程池方式调用子线程来执行。getExecutorService().execute(call);} else {// Request实在太多了,抱歉只能放入等待队列了。生晚了就是后娘养的啊,泪崩~readyCalls.add(call);}}
}

线程池执行子线程的过程大家都懂,我们就不详细分析了哈。我们应该重点关注子线程执行的任务Runnable,也就是我们这儿的AsyncCall对象。AsyncCall没有提供run()方法,但它的父类NamedRunnable中提供了。我们一个个来看。

public abstract class NamedRunnable implements Runnable {// run方法,好亲切哦~@Override public final void run() {// 省略无关代码try {// 入口在这儿,由实现类自己去实现。// 父类实现了共同的方法和步骤,而将差异化的方法放在子类中,这种设计思想大家应该用腻了吧~execute();} finally {Thread.currentThread().setName(oldName);}}protected abstract void execute();
}// 重点关注它如何实现的execute()方法
final class AsyncCall extends NamedRunnable {@Override protected void execute() {boolean signalledCallback = false;try {// 发送的重点,接下来详细分析Response response = getResponseWithInterceptorChain(forWebSocket);if (canceled) {signalledCallback = true;// response失败时回调我们最开始传入的callback的onFailure(), 看到了回调的地方了吧~responseCallback.onFailure(originalRequest, new IOException("Canceled"));} else {signalledCallback = true;// 成功则回调最开始传入的callback的onResponse()responseCallback.onResponse(response);}} catch (IOException e) {if (signalledCallback) {// 发生异常且callback已经被回调了,则不能再回调callback了,否则就多回调了一次。logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);} else {// 发生异常但callback没有被回调,则我们回调callback的onFailure()Request request = engine == null ? originalRequest : engine.getRequest();responseCallback.onFailure(request, e);}} finally {// 不论如何,最终都得finish掉requestclient.getDispatcher().finished(this);}}}
}

可以看到,run()方法利用getResponseWithInterceptorChain()发送request并获取response,然后根据结果来分别回调我们最开始传入的callback的onFailure()和onResponse()。知道了callback回调的地方,总算放心了!接下来重点分析getResponseWithInterceptorChain()

public class Call {private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);// 下面分析proceedreturn chain.proceed(originalRequest);}
}class ApplicationInterceptorChain implements Interceptor.Chain {@Override public Response proceed(Request request) throws IOException {// 先执行interceptor拦截器,拦截器会做一些预处理,不用细看if (index < client.interceptors().size()) {Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);Interceptor interceptor = client.interceptors().get(index);Response interceptedResponse = interceptor.intercept(chain);if (interceptedResponse == null) {throw new NullPointerException("application interceptor " + interceptor+ " returned null");}return interceptedResponse;}// 再进行Http request和responsereturn getResponse(request, forWebSocket);}// 这篇文章最核心的方法,没有之一!
Response getResponse(Request request, boolean forWebSocket) throws IOExbodyception {// 获取request的body并根据它在header中填充Content-Type, Content-Length, Transfer-Encoding字段// 对Http规范不是很了解的童鞋可以先看我的另一篇文章 Http协议简介RequestBody body = request.body();if (body != null) {// 省略一段代码。对header进行填充,不是我们关注的重点,知道有这件事就行了。}// 创建HttpEngine,它是底层核心类,下一篇文章详细分析engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null, null);int followUpCount = 0;// 由于要处理request和它可能的后续request,故使用了while循环// 后续request举一个例子大家就明白了。// server上某个页面移到了另外一个地址后,如果client发送此页面请求,server会发送重定向(redirect)的response,其中包含了页面新地址// okHttp根据页面新地址,重新组建request,发送给server。// server这次就可以回复页面的response了。// followUp request由OKHttp自动完成,大大方便了大家。功能如此之强大,你还不快使用OKHttp?while (true) {if (canceled) {// 取消了request,则关闭Http连接。// Http是短连接,不使用keep-alive技术时,每个request和response来回之后都要关闭连接。之后再发送request则重新连接engine.releaseConnection();throw new IOException("Canceled");}try {// 发送client的request,关键方法,下一篇再分析engine.sendRequest();// 获取server的response,下一篇再分析engine.readResponse();} catch (RequestException e) {// 异常处理逻辑,不用看,代码省略}// 处理followUp request, 前面已经解释了何为followUp requestResponse response = engine.getResponse();Request followUp = engine.followUpRequest();// followUp request全都处理完,跳出while循环,并返回最终的responseif (followUp == null) {if (!forWebSocket) {engine.releaseConnection();}return response;}// followUp request不能太多,否则可能陷入无止境的循环中。例如有些server故意弄一系列的重定向,我们不能被它坑了!if (++followUpCount > MAX_FOLLOW_UPS) {throw new ProtocolException("Too many follow-up requests: " + followUpCount);}if (!engine.sameConnection(followUp.httpUrl())) {engine.releaseConnection();}// http短连接,本次request完成,则要关闭连接。下一个request再重新连接。是不是感觉效率弱爆了?keep-alive可以破此局Connection connection = engine.close();// 处理followUp request,也是利用HttpEngine,跟之前request处理流程一样request = followUp;engine = new HttpEngine(client, request, false, false, forWebSocket, connection, null, null,response);}}
}

总算分析完了request发送流程了,过程还是相当麻烦的。HttpEngine下篇文章再分析。小编此刻已经四肢瘫软,好想来个葛优躺。不过革命还未成功,OKHttp还得继续分析。
这里还得说一下,除了异步方式之外,还可以采用同步方式。使用例子如下。

// builder模式创建一个Request
Request request = new Request.Builder().url("https://baidu.com").build();
//创建okHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建call
Call call = mOkHttpClient.newCall(request);
//同步方式加入队列中
call.execute();

与异步方式唯一的不同之处在于使用的execute(),而非enqueue()方法。下面分析execute()方法

  public Response execute() throws IOException {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}try {// 直接execute,仅仅是加入ArrayDeque中,没有使用线程池利用子线程来执行,故称为同步方式client.getDispatcher().executed(this);// 同样利用getResponseWithInterceptorChain()方法// 先调用拦截器,然后利用HttpEngine sendRequest()和readResponse(),以及处理followUp requestResponse result = getResponseWithInterceptorChain(false);if (result == null) throw new IOException("Canceled");return result;} finally {client.getDispatcher().finished(this);}}private final Deque<Call> executedCalls = new ArrayDeque<>();executedCalls.add(call);}
}

4 核心类

看完了同步和异步两种方式的整体流程之后,大家是不是对使用OKHttp更加胸有成竹了呢。下面跟大家一块分析下request创建和发送过程中使用到的核心类。

1)request:数据类,代表了client发送的网络请求,它的主要字段如下

  private final HttpUrl url;   // url应该不用说了吧private final String method;  // 请求方法,有get,post,put,delete,options等private final Headers headers;  // 首部,request和response都包含三部分,start line,headers,bodyprivate final RequestBody body;  // 主体部分private final Object tag; // 与http request协议无关,OKHttp中作为Request的标签,cancel Request时经常用private volatile CacheControl cacheControl; // 控制cache的使用

2)Response:数据类,代表了server的回复。主要的字段如下

  private final Request request;private final Protocol protocol;  // start line中的协议类型,如Http1.0, Http1.1private final int code; // start line中的返回码,如404,表示文件找不到private final String message; // 位于start line中,用来解释返回码的字符串private final Handshake handshake;  // 握手,TCP连接时要三次握手private final Headers headers;  // 首部private final ResponseBody body;  // 主体private Response networkResponse;  // server的responseprivate Response cacheResponse;  // cache的responseprivate final Response priorResponse;

3)OkHttpClient:代表客户端,提供了很多client使用的方法。是我们APP中经常打交道的一个类。建议全局使用一个。如果要使用多个,建议采用clone方法创建。

4)Call:主要的控制类,包含enqueue()和execute(),同步和异步发送request的两个方法。内部类AsyncCall 实现了异步调用子线程中使用的Runnable。

5)Dispatcher:调度器,以队列方式来管理多个requests。包含readyCalls和runningCalls,一个是等待队列,一个是运行队列。类似于线程池的思想,防止过多requests一块运行。

5 总结

OKHttp整个框架还是十分复杂的,本篇文章主要分析了APP中使用OKHttp时的主要API调用流程。至于底层Http request和response,下一篇文章会重点分析。

OKHttp源码分析2 - Request的创建和发送相关推荐

  1. 【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )

    OkHttp 系列文章目录 [OkHttp]OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) [OkHttp]Android 项目导入 OkHttp ( 配置依赖 | 配置 ...

  2. 【OkHttp】OkHttp 源码分析 ( 网络框架封装 | OkHttp 4 迁移 | OkHttp 建造者模式 )

    OkHttp 系列文章目录 [OkHttp]OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) [OkHttp]Android 项目导入 OkHttp ( 配置依赖 | 配置 ...

  3. OkHttp 源码分析

    流程分析 我们从一个简单的 HTTP 请求开始: client = new OkHttpClient(); Request request = new Request.Builder().url(&q ...

  4. 【OkHttp】OkHttp 源码分析 ( OkHttpClient.Builder 构造器源码分析 )

    OkHttp 系列文章目录 [OkHttp]OkHttp 简介 ( OkHttp 框架特性 | Http 版本简介 ) [OkHttp]Android 项目导入 OkHttp ( 配置依赖 | 配置 ...

  5. 深入OKHttp源码分析(二)----OkHttp任务调度核心类Dispatcher解析

    OkHttp任务调度核心类Dispatcher解析 上一篇我们分析了okhttp的同步和异步请求的执行流程并进行了源码分析,深入OKHttp源码分析(一)----同步和异步请求流程和源码分析 那么今天 ...

  6. 【Android SDM660源码分析】- 01 - 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序

    [Android SDM660源码分析]- 01 - 如何创建 UEFI XBL Protocol DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序 一.创建DXE_DRIVER ...

  7. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  8. Retrofit跟OkHttp源码分析

    网上已经有了相等多的分析博客,但终归是别人的知识点,倒不如自己走一遍流程,如果你看到了这篇博客,最好自己跟着思路对照源码过一遍哦! Retrofit源码分析 Retrofit的构建 在我们开发工作中使 ...

  9. Okhttp源码分析以及Google Gson解析json数据实例

    Okhttp Github的Okhttp OkHttp是一个高效的HTTP客户端,它有以下默认特性: 支持HTTP/2,允许所有同一个主机地址的请求共享同一个socket连接 透明的GZIP压缩减少响 ...

最新文章

  1. 谷歌自动重建了完整果蝇大脑神经图:40万亿像素,可在线交互,用了数千块TPU...
  2. 用结点实现链表LinkedList,用数组和结点实现栈Stack,用数组和结点链表实现队列Queue
  3. 记一次ORM的权衡和取舍
  4. 地球化学图解系统GCDPlot 0.33
  5. python html压缩包,用python制作一个简单html压缩
  6. 怎么讲d 盘里的软件弄到桌面_GNOME 2 粉丝喜欢 Mate Linux 桌面的什么?
  7. docker 实战---使用oracle xe作为开发数据库(六)
  8. Serializable 接口与 Java 序列化与反序列化
  9. .Net----Remoting 激活 激活方式
  10. Google 开源机器学习数据集可视化工具 Facets
  11. 学会 配置文件+反射,走遍全球都不怕.
  12. 笨方法学python 习题41
  13. Matlab:厄米-高斯光束合成拉盖尔-高斯光束
  14. 秋无痕 Windows 7 SP1 (64位旗舰版) 集成安装增强版 V2018年春节版(整合USB3+NVMe+UEFI)
  15. Oracle11g64位发行版安装教程
  16. 【Pandas-1】十分钟入门Pandas (上)
  17. addEvent()和addEventlistner()的区别
  18. phpstorm2018版激活办法,2018/5/28实锤有效
  19. 网络流量在线分析系统的设计与实现+winpcap+vscode+mingw
  20. 研发新员工培训流程(待续)

热门文章

  1. 计算机 无法进入pe,电脑无法进入pe系统_电脑无法进入pe界面
  2. python-docx 合并单元格
  3. 7.3_minibatch-sgd
  4. 7、RH850 F1 RLIN/UART功能和配置
  5. Linux内核中的IPSEC实现(3)
  6. Dijkstra算法指定任意两点距离(邻接矩阵法)
  7. mysql语句格式化日期时间成特定格式
  8. java语言和C语言的区别
  9. Java毕业设计_基于HTML5的摄影社区的设计与实现
  10. 云服务器搭建好,出现 拒绝了我们的连接请求。