OkHTTP源码解析

148 阅读11分钟

使用

框架的具体使用详解这里就不展开了, 可以大概看一下这些文章或者之前总结的博客:



同步请求例程

OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
    //同步请求
    public void synRequest() {
        Request request = new Request.Builder().url("http://www.baidu/com").get().build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

OkHttpClient.Builder()

  • BuilderOkHttpClient中的一个内部类; 通过这个类我们可以配置请求的各种参数;
  • 注意第一行,是一个Dispatcher; 分发器类,OKHttp的核心类 ; 管理同步或者异步请求的分发;
  • Call对象——连接Request和Response的桥梁; 同步请求和异步请求的分水岭; Call对象调用execute();为同步请求, 调用enqueue()为异步请求;



OkHTTP请求流程图

  • 不管是同步请求还是异步请求, 最终都会走到getResponseWithInterceptorChain(), 这个方法也是OKHttp的核心, 其内部是构建了一个拦截器的链, 然后通过依次执行拦截器链中的每一个拦截器,来获取服务器数据返回

异步请求例程

  • 注意:onFailureonResponse都是在工作线程(即子线程中)去执行的;
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
public void asyncRequeset() {
        Request request = new Request.Builder().url("http://www.baidu/com").get().build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("Fail");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println(response.body().string());
            }
        });
    }

同步请求和异步请求的区别

  • 都需要创建OkHttpClient实例,调用newCall()得到Call实例, 随后Call对象调用execute()同步请求, 调用enqueue()异步请求

  • 同步请求阻塞主线程,异步请求不阻塞; 同步发送请求之后,【发送请求的进程】就会进入阻塞状态,直到收到响应为止; 异步请求则是开一个子线程去处理数据;

OkHttpClient.Builder()源码解析

OkHttpClient.Builder()使用了建造者模式

下面是Builder的一个构造函数:

public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      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;
    }
  • 注意第三行,connectionSpecs = DEFAULT_CONNECTION_SPECS;是重点, connectionSpecs是OkHTTP中,HTTP请求的分发器, 异步请求由他决定是直接请求还是缓存等待同步请求就没有太多的操作,是直接放在任务队列当中执行;

  • 接着看 connectionPool = new ConnectionPool();, 这是一个连接池,同样是OkHTTP中的重要概念;
    理解连接池 —— 把服务端客户端之间的一个连接,抽象为一个Connection, 而每一个Connection,OkHTTP都把它放在连接池ConnectionPool当中, 由ConnectionPool来对这些Connection进行统一的管理;

ConnectionPool作用: 作用一:当用户请求的Url是相同的时候,可以在ConnectionPool中选择复用【类>似线程池】; 作用二:ConnectionPool实现了链接分类等相关策略的配置的管理, 如哪一些网络连接 可以保持打开状态哪一些是用来以后复用的这些策略配置 都是由ConnectionPool进行管理的;

Request.Builder()源码分析

  • Request同样使用了建造者模式
public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }
  • build() 把使用Builder模式配置好的Builder对象, 赋值给Request的构造方法, 创建了一个Request对象; (思路讲解如《建造者模式》
public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
  • Request的构造方法
Request(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;
  } 

同步请求流程 源码分析

请求步骤例程回顾
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
    //同步请求
    public void synRequest() {
        Request request = new Request.Builder().url("http://www.baidu/com").get().build();
        Call call = client.newCall(request);
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

1.创建一个OkHttpClient对象; 2.构建携带请求信息的Request对象; 3.将Request实例赋给newCall()得到Call对象; 4.使用Call对象调用execute或者enqueue方法;



则接着看 newCall()源码

  • newCall() 实际上是 Call接口中 Factory接口 的接口方法,而OkHttpClient实现了 Factory接口:
  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
  • newCall() 实际操作逻辑都是在newRealCall()中: newRealCall()中创建了一个RealCall实例, 再赋值了一个ListenerRealCall的构造方法,则是初始化各个属性:
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

注意这里,传进来的Request实例, 最终是作为一个RealCall实例的成员变量, 而这个RealCall实例, 就是我们应用时要拿去进行同步(execute())异步请求(enqueue())的句柄; 【也就是 请求信息配置类(Request) 跟 请求启动的句柄(RealCall)绑定在一起了, 后续要进行相关的控制(如同步请求同步判断控制)是很方便的;】

  • 然后newRealCall()返回了一个RealCall实例, 这个RealCall实例又作为newCall返回值, 返回到我们使用框架的层次,向上转型为Call实例去应用:
Call call = client.newCall(request);
        try {
            Response response = call.execute();
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
  • 所以后续我们使用Call对象【实际上是一个向上转型的RealCall实例】调用execute()或者enqueue()的时候, 这里的execute()或者enqueue()的逻辑, 即RealCall实现好的execute()或者enqueue()中的逻辑;
  • 因为execute()enqueue()Call中只是作为接口方法被定义, 而RealCall类实现了Call接口以及Call接口中的方法,包括execute()enqueue()

接着是execute()的源码

  • 接口定义
  • 具体实现:
  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }
  • 首先是synchronized 代码块, 首先判断executed是否为true, 也就是说同一个HTTP请求,只能执行一次; 如果请求执行过,就会抛出"Already Executed"异常; 没有执行过,则执行,同时executed置位;

  • captureCallStackTrace(); 捕捉HTTP请求的异常堆栈信息;

  • eventListener.callStart(this);开启一个监听事件; 【每当call实例调用execute()或者enqueue()的时候,就会调用这个方法,后面细讲】

  • 接下来的代码划重点, try中:client.dispatcher().executed(this);dispatcher()方法放回一个Dispatcher对象,它是请求的分发器;
    executed()方法——重点 同步请求,call 调用executed(),就是把RealCall(Call)对象调用实例本身, 加到(add(this))同步队列runningAsyncCalls中,就完成了这个操作; 这个是定义在类中的同步执行队列Dispatcher中还定义了其他两个队列——异步执行队列、就绪队列

Dispatcher简要介绍

维护了Call请求的一些状态, 同时也维护了一个线程池 用于进行网络请求; Call请求 通过 Dispatcher 推到 执行队列中 进行操作

  • 执行完executed()之后,紧接着是, Response result = getResponseWithInterceptorChain(); getResponseWithInterceptorChain()用来获取Response; 其内部是构建了一个拦截器, 然后通过依次执行拦截器链中的每一个拦截器,来获取服务器数据返回

  • 最后是execute()源码末尾的finally中的, client.dispatcher().finished(this);, 会主动回收当前同步请求一参传了【runningAsyncCalls】,二参是【当前同步请求】对应的【Call 实例】,这里的第三个参数,到时候异步请求的时候会用到,本质作用是接下来调用【promoteCalls()】的开关,这里同步请求传的时候false,当前同步请求对应的Call 实例传进来, 然后202行,则是把该Call 实例同步请求队列 runningAsyncCalls中 移除; 如果不能移除,就抛出异常 ——throw new AssertionError("Call wasn't in-flight!");; 当然这里注意是同步请求操作【execute()源码】,才是移除; 203行这里,由于同步请求这里 promoteCalls位传 false,这里不会走 promoteCalls()方法; 204行,调用runningCallsCount()计算目前还在运行的请求,其实现如下:没什么特别的,就是直接返回正在执行的异步请求同步请求的总和;
    到208行,有一个判断, 当正在执行的请求数为0的时候(表示当前整个Dispatcher当中,没有可运行的请求了), 这个时候再满足idleCallback != null,则调用 idleCallback.run();

至此,同步请求的流程源码就告一段落了。 小结注意: 在同步请求当中,Dispatcher类的作用,非常简单: 添加同步请求同步请求列表, 从同步请求列表中移除同步请求





异步请求流程 源码分析

  • 异步请求例程
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
public void asyncRequeset() {
        Request request = new Request.Builder().url("http://www.baidu/com").get().build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                System.out.println("Fail");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println(response.body().string());
            }
        });
    }

RealCall.enqueue()源码分析

Call --- 定义RealCall中 --- 继承与实现

  • 94行,首先,锁住当前RealCall对象; 95、96行,同上,用于控制 --- 同一个HTTP请求,只能执行一次; 首先,判断executed是否为true, 如果为true(请求执行过),就会抛出"Already Executed"异常; 没有执行过,则执行,同时executed置位;

  • 98行 --- captureCallStackTrace(); 捕捉HTTP请求的异常堆栈信息;

  • 接下来重点看一下100行这里, 传进来的用户自定义的Callback匿名内部类实例, 在这里被封装成一个AsyncCall实例;

!!!!!AsyncCall源码:!!!!!RealCall中的execute()首先AsyncCall继承了NameRunnable, 然后AsyncCall中的execute()是实现了NameRunnable中定义的方法,跟RealCall中的execute()毫无关系,同名不同人;
再看NameRunnable,其实就是一个实现了RunnableRunnable子类; 所以其实AsyncCall类就是Runnable子类;

  • 所以enqueue()源码中,100行这里, client.dispatcher().enqueue(new AsyncCall(responseCallback)); 就是通过传进来的Callback实例,封装好了一个Runnable实例AsyncCall实例】, 接着通过client.dispatcher()获取Dispatcher实例 并调用enqueue()将这个Runnable实例送进执行流程;
  • OkHttp.Builder构造方法第一行,已经实例化好Dispatcher对象:

接着看Dispatcher.enqueue() 源码分析

  • 首先,方法是同步方法(synchronized), 然后参数是AsyncCall实例,刚刚提到过,是一个Runnable实例;

  • 接着130行, 首先判断runningAsyncCalls.size() < maxRequests, 【即 异步执行队列的元素数量 是否小于 最大请求数(64);】 以及runningCallsForHost(call) < maxRequestsPerHost, 【即 正在运行的主机请求 是否小于 设定的最大值(5); 】OkHttp中,对这两个阈值的设定如果 满足以上条件, 则把传递进来的AsyncCall/Runnable实例 添加到runningAsyncCalls中【131行】, 然后会使用一个线程池去执行它;【132行executorService()会返回一个线程池对象注意这里的synchronized关键字,保证executorService的同步特性;如果不满足, 则把这个传递进来的AsyncCall/Runnable实例 添加到 等待序列readyAsyncCalls中;

  • 注意这里runningAsyncCalls存储正在运行的异步任务,主要作用是判断并发请求的数量, readyAsyncCalls表示缓存等待中的请求的队列:
  • executorService().execute(call);源码分析【132行切入】: executorService()会返回一个线程池对象, 接着调用execute(call);会执行call实例run(); 这里的call实例即是AsyncCall实例接下来我们要看一下run()的内容,AsyncCall源码如上,可以发现这里没有run()的内容, 而上面也说过了,AsyncCall继承自NameRunnable, 接着可以到NameRunnable中去找:到这就找到call实例run()了; run()中主要是调用了execute()方法,即【38行】处定义的execute(), 接着跟踪其实现,到RealCall.AsyncCall # execute()这里:【147行】getResponseWithInterceptorChain(),构建了一个拦截器链【后续细讲】; 【148行】判断拦截器链中的 这个重试重定向拦截器 -- retryAndFollowUpInterceptor, 判断其是否取消了, 【149 - 150行】如果取消了,就会调用responseCallback.onFailure(), 【152 - 153行】如果没有取消,则走到responseCallback.onResponse(RealCall.this, response);; 【155行】处理异常情况; 【164行】client.dispatcher().finished(this);调用到(其实类同前面的同步请求)【201行】因为这边的几个队列都是不安全的队列, 所以这里201行需要加synchronized关键词, 把remote()promoteCalls()操作给锁住,保证线程安全;【202行】将当前call实例移出对应异步请求任务队列; 【203行】调用promoteCalls()调整整个异步请求任务队列; 【204行】重新计算正在执行的线程数量,刷新赋值;用于后续判断操作;【208行】

####- Callback 引用的 来龙去脉 这里的responseCallback实例 就是我们一开始调用框架时 传进来的自定义的 CallBack实例

- 从源码角度 理解【onFailure()】、【onResponse()】执行与子线程

到这里可见,与一开始相呼应的一个点: 无论这里是responseCallback.onFailure() 或者是responseCallback.onResponse(RealCall.this, response);, 都是在子线程中运行的!!!!!!!!!!!!! 因为它们都是编写在execute()方法中的, 而execute()方法在一个Runnablerun()中, 然后这个Runnable实例正式用线程池开启的子线程来执行的。
所以更新UI等任务,注意切换到主线程操作!

- 这里AsyncCall中的execute()是实现了NameRunnable中定义的方法,跟RealCall中的execute()毫无关系,同名不同人;RealCall中的execute()是对Call中定义的execute()实现;

从OkHttpClient的创建,到最后的Dispatcher# finished(),一次完整的OkHttp异步请求就完成了;





OkHttp的任务调用 —— 主要基于Dispatcher类实现

Dispatcher的概述与源码分析
  • OkHttp中,发送请求的 同步、异步 的状态 都会在Dispatcher中 被管理, 也由此区分 请求的 同步、异步;

  • Dispatcher用于维护( 同步、异步) 请求的 状态, 并维护一个线程池,用于(更加高效地)执行请求(尤其异步请求); 维护任务队列;

  • 图解Dispatcher作用、流程:

  • 每当有网络请求的时候,通过Call实例封装, 接着通过DispatcherCall请求实例 推到readyAsyncCalls就绪请求队列中;

Dispatcher源码分析
  • 首先是这三个请求队列:(前面讲过好几次了) runningAsyncCalls--- 正在执行的异步请求; 注意看注释:列表还包含了已经取消、但没有执行完成(Finished)的异步请求; readyAsyncCalls --- 就绪状态的异步请求队列; 当某个异步请求 Call实例不满足条件时,则把这个异步请求 Call实例