OkHttp 源码解析(一)设计模式层面

分析原因

最近被问到okhttp 在性能上面 和 HttpUrlConnection ,volley 等框架有什么优势,回答不上来,其实之前看过 okhttp源码,一知半解,也没有做记录,现在知道后悔了

OkHttp 使用方式

OkHttp官网地址:http://square.github.io/okhttp/
OkHttp GitHub地址:https://github.com/square/okhttp

implementation 'com.squareup.okhttp3:okhttp:3.14.2'

需要在清单文件声明访问Internet的权限,如果使用缓存,那还得声明写外存的权限

异步Get请求

 /**
     * 异步GET请求:
     * new OkHttpClient;
     * 构造Request对象;
     * 通过前两步中的对象构建Call对象;
     * 通过Call#enqueue(Callback)方法来提交异步请求;
     */
    private void asyncGetRequests() {
        String url = "https://wwww.baidu.com";
        OkHttpClient okHttpClient = new OkHttpClient();
        final Request request = new Request.Builder()
                .url(url)
                .get()//默认就是GET请求
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
              
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String result = response.body().string();
            }
        });
    }

这一连串的接口调用,看看使用了多少设计模式

1.Builder 模式

获取一个自定义的 OkHttpClient 对象

  OkHttpClient okHttpClient=new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
                .build();

OkHttpClient 有两个构造方法

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

第一个 是 public 的,外部可以使用
第二个 只能相同包名的类才可以使用

不管使用哪个构造方法,都是通过builder 进行初始化

看看OkHttpClient.Builder 的构造函数

 public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      ......
      writeTimeout = 10_000;
      pingInterval = 0;
 }

Builder(OkHttpClient okHttpClient) {
      this.dispatcher = okHttpClient.dispatcher;
      this.proxy = okHttpClient.proxy;
      ......
      this.writeTimeout = okHttpClient.writeTimeout;
      this.pingInterval = okHttpClient.pingInterval;
}

第一个 自己对所有配置进行初始化
第二个 通过 另外一个OkHttpClient 对自己进行初始化

看看OkHttpClient.Builder.build 方法

public OkHttpClient build() {
     return new OkHttpClient(this);
}

实际上就是 调用了 OkHttpClient的第二个构造方法
OkHttpClient初始化的流程就是 这样

2.Builder 模式 使用的原因

Builder 模式的优势在哪
我们初始化一个类,并初始化他的配置,可以有几种方式

(1)直接写一个大的构造方法,所有参数都传进去

OkHttpClient(Dispatcher dispatcher,Proxy proxy,......,int writeTimeout) {
	 this.dispatcher = dispatcher;
     this.proxy = proxy;
	 ......
}

这种方式的缺点是 代码可读性,和可维护性 并不强

(2) 另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参的构造方法来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数

OkHttpClient okHttpClient=new OkHttpClient();
okHttpClient.setDispatcher(dispatcher);
okHttpClient.setProxy(proxy);
......
okHttpClient.setPingInterval(30, TimeUnit.SECONDS);

不幸的是,JavaBeans 模式本身有严重的缺陷。由于构造方法被分割成了多次调用,所以在构造过程中 JavaBean 可能处于不一致的状态。也就是 线程安全问题。

(3)builder 设计模式
把初始化 参数的任务合成为一步,方便做线程安全操作,
同时自定义配置参数很方便,可变性高,
按照同一约定,维护代码成本小

这个不是我总结出来的,是《effctive-java》这本书里面看到的

3.工厂 模式

OkHttpClient和Request 的实例化都是通过builder模式
Call 的实例化则是通过 工厂模式

Call call= okHttpClient.newCall(request);

public interface Call extends Cloneable {
	......
	interface Factory {
    	Call newCall(Request request);
    }
	......
}

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.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 */);
	  }
	  
	 ......
}

可以看到 okHttpClient 同时也是 实现了Call.Factory 接口的一个工厂类,生产标准的 Call 对象用于处理请求

优点是 :可以生产出 相同标准的产品
缺点是 :每扩展一个产品就需要 新建一个产品类,产品复杂多样的情况下,代码难以维护

4.模板 模式

不管是同步请求还是 异步请求,都是调用 Call这个对象 的接口

public interface Call extends Cloneable {
	Request request();

	Response execute() throws IOException;

	void enqueue(Callback responseCallback);
	......
}

Call 只是一个接口文件,具体的实现类为RealCall

不管具体的实现方式是什么,OkHttp都会给你这几个接口,request,execute,enqueue等

调用接口时也不用管 具体的实现类到底是谁,这就是模板设计模式的好处

5.责任链 模式

来看看 同步请求的具体实现方法

@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);
    }
 }

dispatcher就不分析了,就是直接把任务加到 线程池执行队列里面,注意,是队列数据结构

Response result = getResponseWithInterceptorChain();
来看看getResponseWithInterceptorChain 这个方法做了什么

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, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
}

会看到 OkHttp 的所有的一堆 拦截器,不管是 默认的还是自定义添加的(要注意拦截器的顺序),都放到 Interceptor.Chain 这个数据结构中,并调用了 chain.proceed() 方法

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;
    ......
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
	......
    return response;
  }

最主要的就是这段代码,从第一层开始分析调用 顺序中的第一个拦截器,把index递增之后的RealInterceptorChain对象传给它,如果没有设置自定义的拦截器,第一个就是RetryAndFollowUpInterceptor,看看它的intercept 方法

@Override public Response intercept(Chain chain) throws IOException {

    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
	......
	 try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
     ......
     
}

其实就是 调用了 刚才传过来的RealInterceptorChain对象的proceed方法,只是index自增了,再往下分析,其实就是调用下一个 拦截器的intercept 方法,这样就是一层一层的向下调用了

时序图如下:
更多的拦截器层层调用
所以自定义拦截器的时候 要注意一点,必须要调用chain.proceed 这个方法,不然下层的拦截器调用不到了,也就没有网络请求,毕竟具体的网络请求在接下来的拦截器里面 实现

总结来说,okhttp的拦截器实现 ,就是一个有上下文 的责任链模式

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值