OkHttp3源码解析

作者: luweicheng24 | 来源:发表于2018-06-19 15:40 被阅读0次

    引用

    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的花销等等。

    相关文章

      网友评论

        本文标题:OkHttp3源码解析

        本文链接:https://www.haomeiwen.com/subject/dsgaeftx.html