OkHttp框架设计-源码解析-我终于写到这里了

·  阅读 323

简介

在Android端,网络请求用的最多的是OkHttp和Retrofit,而后者实际上是基于OkHttp上封装开发的。基于前几篇文章的基础,今天就来认真分析一下这个框架。

前面的文章都在为这篇做基础知识~

深度解析Java线程池

Socket进行Http/Https请求

2020都尾声了,网络模型还不清楚吗?-OIS、TCP/IP、HTTP

1、一个简单的OkHttp请求

从客户端的角度看,一个完整的Http请求,可以简化为以下三步,

1、装配Request(请求方法,url,请求头等)

2、客户端发出request请求

3、接收服务端响应,解析数据

按照上面这几个步骤,在OkHttp框架中发起网络请求的代码如下:

//构造Request
Request req=new Request.Builder()
                        .url(url)
                        .build();
//构造一个HttpClient
OkHttpClient client=new OkHttpClient();
//发送请求
client.newCall(req)
        .enqueue(new Callback() {
            //获得服务器返回的Response数据
            @Override
            public void onResponse(Call arg0, Response arg1) throws IOException {
                
            }
            
            @Override
            public void onFailure(Call arg0, IOException arg1) {
                // TODO Auto-generated method stub
                
            }
        });
复制代码

你看,就这简单的几个步骤,和我们正常的逻辑思维是一致的。这样使用起来才足够简单。

2、OkHttp框架设计

首先给出OKHttp框架的整个处理流程图:

2.1、构建Request-构建者模式

public final class Request {
   ...省略代码...
  public static class Builder {
    private HttpUrl url;
    private String method;
    private Headers.Builder headers;
    private RequestBody body;
    private Object tag;
    }
    
}
复制代码

相信构建者模式大家都比较熟悉了,所以这里只给出构造参数。

构建Request,按常理想,应该是一个具备请求能力的对象。但实际上OkHttp为框架使用者简单使用着想,将使用者需要关心的内容(比如url,method,header等)单独封装在了Request中,而具体的网络请求内容剥离到了框架内部。

实际使用中也确实符合这样的设想,框架使用者需要关心的请求内容都在Request中了

2.2、构造OkHttpClient对象

public class OkHttpClient implements Cloneable, Call.Factory, WebSocketCall.Factory {
  ...
  public static final class Builder {
    Dispatcher dispatcher;
    Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    ProxySelector proxySelector;
    CookieJar cookieJar;
    Cache cache;
    InternalCache internalCache;
    SocketFactory socketFactory;
    SSLSocketFactory sslSocketFactory;
    CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    
    }
...
  @Override 
  public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }
  ...

}
复制代码

OkHttpClient的构建依然是使用构建者模式。

值得注意的第一个点是OkHttpClient的Builder中是有Dispatcher的,也就是说OkHttpClient中有Dispatcher对象可以做异步请求

更值得注意的是OkHttpClient实现了Call.Factory接口,创建了RealCall类的实例(Call的实现类),并将OkHttpClient对象自身的引用和request对象传递了过去。

从文章开头写的OkHttp的简单使用可以看到在发送请求之前,是调用了newCall方法,创建了一个Call对象,实际上RealCall对象持有了OkHttpClient的引用和request对象引用,使得后面能够使用这两个对象发出请求

2.3、Call接口及其作用

我们先看看RealCall实现的Call接口

public interface Call extends Cloneable {
  Request request();

  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}
复制代码

基本上我们对一个请求会调用的方法都定义在这个接口里面了。比如同步请求调用execute方法,异步请求调用enqueue方法等。可以说这个接口就是OkHttp框架的操作核心。在构建好OkHttpClient和Request,调用newCall方法创建出Call对象后,我们就是持有着Call对象在进行请求的。

我们继续按照上面的流程图查看源码,发送请求时,将请求丢入请求队列,即调用RealCall的enqueue方法

@Override 
 public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    //方便阅读,这里省略了部分代码逻辑,只展示关键步骤
   //拿到okhttpClient中的diapatcher
   Dispatcher dispatcher=client.dispatcher()
   //将异步请求Runnable丢入dispatcher分发器中
   /*
    *这个AsyncCall类实现了Runnable接口
    *responseCallback就是我们期望的response的回调
    */
   dispatcher.enqueue(new AsyncCall(responseCallback));  
  }
复制代码

2.4、Dispatcher执行请求

我们先看下Dispatcher分发器中的enqueue方法,再看AsyncCall中具体操作

...
...
  //等待异步执行的队列 
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  //正在执行的异步队列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  //正在自行的同步队列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  ...
  synchronized void enqueue(AsyncCall call) {
    //如果正在执行的请求小于设定值,默认64
    //并且请求同一个主机的request小于设定值,默认为5
    if (runningAsyncCalls.size() < maxRequests &&
            runningCallsForHost(call) < maxRequestsPerHost) {
        //添加到执行队列,开始执行请求
      runningAsyncCalls.add(call);
      //获得当前线程池,没有则创建一个
      ExecutorService mExecutorService=executorService();
      //执行线程
      mExecutorService.execute(call);
    } else {
        //添加到等待队列中
      readyAsyncCalls.add(call);
    }
  }
复制代码

现在我们再看下AsyncCall实现了Runnable接口的类

//它是RealCall的一个内部类
//NamedRunnable实现了Runnable接口,把run()方法封装成了execute()
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override 
    protected void execute() {
      boolean signalledCallback = false;
      try {
        //这里拿到了响应,那可以肯定请求是在这个方法中进行的了
        Response response = getResponseWithInterceptorChain();
        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);
      }
    }
  }
  ...
  ...
  //发出请求得到响应的地方
    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));
    //责任链模式对request进行了系列操作后发出
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }
复制代码

在getResponseWithInterceptorChain方法中,可以看到很多个Interceptor。从名称上看,有重试的,缓存的,发出请求的。前文我们也提到了Request并不是请求的全部,OkHttp为我们隐藏了部分操作。到这里就可以看到了,是在Interceptor中为我们处理的。OkHttp整体框架到这里也就讲清楚了。

关键类:OkHttpClient、Request、Call、RealCall、Dispatcher、Response、Interceptor等

这些关键类各自负责什么,你清楚了吗?

3、Interceptor拦截器详解

拦截器,顾名思义是对请求Request或者响应Response做一些处理,而OkHttp是通过责任链模式,将所有的Interceptor串联在一起,保证Interceptor按规则一个一个执行

3.1、拦截器接口设计

我们先来看下Interceptor接口

public interface Interceptor {
  //只有一个接口方法
  Response intercept(Chain chain) throws IOException;
    //责任链模式的联调
  interface Chain {
    // 包含了请求Resquest
    Request request();
    //获得Response
    Response proceed(Request request) throws IOException;
    //获得当前网络连接
    Connection connection();
  }
}
复制代码

这块的整体设计使用了责任链模式,而在OkHttp这个框架中,使用责任链模式得到的效果是对请求和响应进行了拦截,达到了将各类加工处理进行解耦的效果。为了帮助理解,下面绘制一张流程图

在OkHttp具体实现中,在各个拦截器上流转的是责任链的链条。包装了Request的链条经过各个拦截器进行了系列处理,最后响应又从各个拦截器逆序流转回来。

3.2、责任链模式实现拦截器功能

下面我们一起看下OkHttp的源码是怎么实现这个过程的

Response getResponseWithInterceptorChain() throws IOException {

    List<Interceptor> interceptors = new ArrayList<>();
    //开发者自定义的Interceptor
    interceptors.addAll(client.interceptors());
    //这个Interceptor是处理请求失败的重试,重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //这个Interceptor工作是添加一些请求的头部或其他信息
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //这个Interceptor的职责是建立客户端和服务器的连接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        //添加开发者自定义的网络层拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    //这个Interceptor的职责是具体发送请求
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //一个包裹这request的chain
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    //把chain传递到第一个Interceptor手中
    return chain.proceed(originalRequest);
  }
复制代码

从上述代码看,最后构建了链条,开始执行就结束了,那拦截器的流转就是在Chain中实现的了。我们接着看RealInterceptorChain的实现

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final Connection connection;
  private final int index;
  private final Request request;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, Connection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }
...
 @Override 
 public Response proceed(Request request) throws IOException {
            //直接调用了下面的proceed(.....)方法。
    return proceed(request, streamAllocation, httpCodec, connection);
  }

    //这个方法用来获取list中下一个Interceptor,并调用它的intercept()方法
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;
    ...

    // Call the next interceptor in the chain.
    //注意看这里的index+1了
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    //从list中获取到第一个Interceptor
    Interceptor interceptor = interceptors.get(index);
    //然后调用这个Interceptor的intercept()方法,并等待返回Response
    //注意看,这个把next传递过去了,意味着这里的interceptor执行时,可以拿到next调用proceed方法,从而走起整条链路
    Response response = interceptor.intercept(next);
    ....
    ....
    return response;
  }
复制代码

这块由于是代码逻辑层面的,大家查看的时候注意看下上面代码的注释哈。最关键点步骤就是上面的几个注意了。拦截器拿到链条Chain(此时的联调带了所有的拦截器和下一个拦截器的索引),只要在拦截器中执行链条中的下一个拦截器,依次进行,就能走通整个链条

下面我们看下RetryAndFollowUpInterceptor这个拦截器的实现,是否按照我们所想的,调用了链条的proceed方法

...
    //直接调用自身的intercept()方法
 @Override 
 public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    ....
    ....
      Response response = null;
      boolean releaseConnection = true;
      try {
        //在这里通过继续调用RealInterceptorChain.proceed()这个方法
        //在RealInterceptorChain的list中拿到下一个Interceptor
        //然后继续调用Interceptor.intercept(),并等待返回Response
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        ...
      } catch (IOException e) {
       ...
      } finally {
        ...
      }

    }
  }
...
复制代码

到这里,讲通了OkHttp拦截器部分是怎么使用责任链模式,对请求和响应进行拦截的了哈。

你看懂了吗?

但是,OkHttp到底怎么进行网络请求的,是在最后一个拦截器CallServerInterceptor中进行的,请求最终是利用Socket进行的,如果你有兴趣了解Socket如何进行请求的,Socket进行Http/Https请求

另外,本篇没有进行讲解的Dispatcher分发器,它的内部实现是使用了缓存类型的线程池进行的。简单展示下代码如下

  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;
  }
复制代码

如果你想更多的了解线程池的知识,深度解析Java线程池,里面介绍了线程池各个参数的意义,以及各类线程池的不同,看完相信你能对OkHttp的线程池参数为什么是这样设置的进行一定的思考哦!

分类:
Android
标签:
分类:
Android
标签: