您的位置:首页 > 理论基础 > 计算机网络

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,下一篇文章会重点分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息