OkHttp3源码解析

引用

okhttp问世以来,以其高度封装、定制、简洁的api调用获得广大使用者的喜爱,目前最流行的网络请求框架莫过于rxjava+retrofit+okhttp,如果你一直停留在使用的地步,那你永远可能只是大自然的搬运工了,为了了解这些架构设计的巧妙以及为何会如此受欢迎,只有通过源码来了解设计精髓,学习square出品,必属精品的代码设计思路,本篇先来了解一下okhttp3,本片所有源码是基于okhttp3.8.1,为了了解设计思路,所贴源码可能会有部分省略。

简单的使用

  1. 同步请求
OkHttpClient client = new OkHttpClient();  // 1.1 构建HttpClient对象,okhttp的门面或者外观对象
                Request request = new Request.Builder().url("http://www.baidu.com")  
                        .build();  //1.2 使用构建者模式创建一个包含请求参数的Request对象
                try {  
                    Call call = client.newCall(request);//1.3 call对象表示一个执行请求的实体对象,一个call代表一个请求
                    Response response = call.execute();  //1.4 执行同步请求
                     
                    if (response.isSuccessful()) { // 根据服务器返回数据封装的Response对象,包含响应码、响应体等
                        System.out.println("成功");  
                    }  
                } catch (IOException e) {  
                    e.printStackTrace();  
                }  

接下来对上面的步骤,一步一步解释然后跟踪源码:

*1.1 okhttpclient采用外观者模式、构建者模式,创建一个http请求的client,该对象包含一个Build对象,用来定制化创建你所需要的client对象;

  public OkHttpClient() {
    this(new Builder());
  }  

      public Builder() {
      dispatcher = new Dispatcher(); // 由call代表的请求的分发器
      protocols = DEFAULT_PROTOCOLS; // 默认的协议 http2 http1.1
      connectionSpecs = DEFAULT_CONNECTION_SPECS; // 设置连接时支持的tls层协议以及不进行数据加密
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      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其实就是在配置全局发送请求中所需要的各种定制化的参数,并且持有各个参数引用对象。
1.2 根据Http协议创建的Request请求,Request这个类总共还没300行代码,内部使用好了嵌套的构建者模式来对请求参数进行设置,主要就是Header、url、method、requestbody等一些在进行http请求中符合http协议的参数:

Request类结构图

1.3 client.newCall(request);client根据request中的参数创建一个执行该请求的执行体call对象,
一个call就代表一个请求** Call是一个接口规定了需要执行的几个行为,具体的实现类有RealCall和AyncCall:

public interface Call<T> extends Cloneable {
  // 同步执行网络请求
  Response<T> execute() throws IOException;

  // 异步执行网络请求
  void enqueue(Callback<T> callback);

 // 判断该请求一否已经执行完成
  boolean isExecuted();

 // 取消该请求
  void cancel();

  // 克隆一个一模一样的call对象也就是一个请求
  Call<T> clone();

  // 获取封装该请求参数的request对象
  Request request();
}

既然Call只是规定了这些执行的行为,那一个请求的执行必然是由其由其实现类来执行,这就是面向接口编程。client.newCall(request) 这一步就是创建一个call对象:

 /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
     // 包含三个参数 1 当前client 2 request对象 3 是不是web socket http支持使用socket实现长连接
    return new RealCall(this, request, false /* for web socket */);
  }

可以看到,这一步是创建了RealCall对象,该对象就是执行同步请求的真正对象。
*1.4 Response response = call.execute() 这一步就是由请求的执行体realCall来发起同步请求:

@Override public Response execute() throws IOException {
    synchronized (this) {// 添加同步锁,判断该RealCall是否已经执行
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace(); 
    try {
      client.dispatcher().executed(this);//1.5
      Response result = getResponseWithInterceptorChain();//1.6
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

*1.5 调用在创建client的时候就初始化的Dispatcher,Dispatcher是一个请求分发器,内部包含了三个队列数组和一个线程池:


Dispatcher结构
  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService; // 线程池

  /** 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<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */ 正在执行的同步任务队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }
  // 创建一个线程池,核心线程为0,最大为Integer的最大值,空闲线程60s没任务线程自动销毁
  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

而Dispatcher的execute的的方法是将realCall对象加入到runningSyncCalls的队列中,

 /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

*1.6 这一步执行前,先来说一下拦截器,okhttp对于网络请求采用用了一个类似AOP的的拦截器链,链式调用所有拦截器,最后执行请求返回response,而okhttp内置了5个拦截器。

  • RetryAndFollowUpInterceptor
    在网络请求失败后进行重试
    当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连 接
  • BridgeInteceptor
    设置内容长度,内容编码
    设置gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
    添加cookie
    设置其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤
  • CacheInterceptor
    当网络请求有符合要求的Cache时直接返回Cache
    当服务器返回内容有改变时更新当前cache
    如果当前cache失效,删除
  • ConnectInterceptor
    为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接,返回的连接由连接池负责决定。
  • CallServerInterceptor
    负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回。

拦截器的总体执行流程如下:

拦截器执行流程

这一步Response result = getResponseWithInterceptorChain(); 就是执行拦截器链,直到返回Response:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

通过责任链模式循环调用所有拦截器,每个拦截器可以根据Request和Reponse前后执行相应的逻辑。
以上分析的是同步请求,异步请求也是大同小异:

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

跟踪到Dispatcher中:

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

以上就是Dispatcher的enqueue函数,先判断是否异步请求队列长度大于线程池最大请求数,以及当前主机的请求数超过5个。如果没有将给异步call加入到异步线程队列,调用线程池执行该call,如果超了,将该异步call加入到异步等待队列,

AsynCall是RealCall内部类,继承于NameRunnable,NameRunable其实就是Runnable的子类,定义了一个execute方法,执行在run()方法中:

/**
 * Runnable implementation which always sets its thread name.
 */
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

将AsyncCall加入到线程池,既然AsyncCall是一个Runnnable,那么就是执行Async的execute方法:

   @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();  // 调用拦截器链,执行请求 返回response
        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 {
          responseCallback.onFailure(RealCall.this, e);// 失败回调
        }
      } finally {
        client.dispatcher().finished(this); // 完成了请求
      }
    }

在一次网络请求不管成功失败,都会调用finally中的这行代码client.dispatcher().finished(this); 别问我为啥?跟踪一下这行代码:

/** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }
  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();
    }
  }

将异步call熊runningAsyncCalls队列中移除,然后 如果是异步请求就会执行promoteCalls() 这个方法:

  private void promoteCalls() {
    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.
    }
  }

其实就是查看线程池情况,然后从readAsyncCall是中获取等待的异步call执行,如此循环,直到所有的异步call执行完成,大体流程如下:


网络同步与异步请求

结语
本篇只是从一次简单的网络请求来跟踪源码,梳理的一个大致流程,其中对于每个拦截器只是说明作用,其实每个拦截器设计也很巧妙,比如CacheInterceptor采用了策略模式来对网络缓存和本地缓存进行相应的处理,缓存采用DiskLruCache来缓存。ConnnectInteceptor内置连接池采用多路复用技术减少了创建connection的花销等等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容