OKHttp源码分析2 - Request的创建和发送
2016-09-03 18:19
330 查看
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() { @Override public void onFailure(Request request, IOException e) { } @Override public void onResponse(final Response response) throws IOException { } });
OKHttp使用起来还是相当简单的,大家是否已经跃跃欲试想了解下它的底层实现呢。从使用中我们看到,它大致分为两步:Request的创建和Request的发送。现在就带大家一起来分析下这两个步骤。对Http协议不熟悉的同学,可以先看看我的这篇文章Http协议简介
2 Request创建流程分析
Request创建使用了十分典型的builder模式。Request.java public 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() { // 看到了吧,少了谁都不能少了url if (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; // 等待队列,合适时机时才开始run private 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掉request client.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); // 下面分析proceed return 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和response return 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 request Response response = engine.getResponse(); Request followUp = engine.followUpRequest(); // followUp request全都处理完,跳出while循环,并返回最终的response if (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 request Response 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,body private 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.1 private 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的response private Response cacheResponse; // cache的response private 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,下一篇文章会重点分析。相关文章推荐
- Glide源码分析2 -- request创建与发送过程
- AFNetworking3.1.0源码分析(五)详解AFHTTPRequestSerializer 之创建NSMutableURLRequest
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- 第二人生的源码分析(二十八)UDP发送数据的可靠性控制
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- GEF源码分析(三) 模拟GEF设计思路,解剖GEF2 附图:包含GEF的Editor创建时序图
- 第二人生的源码分析(三十八)构造一个消息包并发送
- 第二人生的源码分析(二十七)发送数据的流量控制
- 第二人生的源码分析(二十七)发送数据的流量控制
- 第二人生的源码分析(三十八)构造一个消息包并发送
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- 第二人生的源码分析(二十七)发送数据的流量控制
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification
- .NET / Rotor源码分析4 - 修改Rotor使其发送CLR Notification