OKHttp源码分析2 - Request的创建和发送

来源:互联网 发布:linux tomcat自动关闭 编辑:程序博客网 时间:2024/06/11 15:48

1 概述

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

//builder模式创建一个RequestRequest request = new Request.Builder()                .url("https://baidu.com")                .build();//创建okHttpClient对象OkHttpClient mOkHttpClient = new OkHttpClient();//创建callCall 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.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() {      // 看到了吧,少了谁都不能少了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发送流程分析

Markdown

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模式创建一个RequestRequest request = new Request.Builder()                .url("https://baidu.com")                .build();//创建okHttpClient对象OkHttpClient mOkHttpClient = new OkHttpClient();//创建callCall 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,下一篇文章会重点分析。

4 0
原创粉丝点击