拆分Okhttp

okhttp可以说是面试中被问到的频率最高的第三方框架了,应该说没有之一吧。那正好趁着找工作,复习拆分一下这个第三方库。

第一部分:用到的设计模式

1.建造者模式:比如 OkHttpClient、Request。为什么要用建造者模式?建造者模式使用特点就是,如果一个类有多个(三个吧)构造器,并且有些参数是非必填的,那么就适合用建造者模式。刚好这两个类所有的参数都是有默认值得(至少是非必填的);

2.静态工厂模式:RequestBody.create();啥也不用说,就是简单。

3.责任链模式:啥是责任链模式?首先要是链式调用,就是我要可以调用下一个形成链子,所以说拦截器最后一个必须是调用由系统默认写好的,我们可以在中间按照模板写自己的拦截器插入节点。

4.生产者消费者模式:Dispatcher类职责。我们创建请求就是生产者,调用器从线程池中取出请求来联网处理就是消费者,而线程池(队列)则是缓冲池。

第二部分:如何使用

        1.创建OkHttpClient 
        OkHttpClient client = new OkHttpClient.Builder()
                .readTimeout(50000, TimeUnit.MILLISECONDS)
                .readTimeout(50000, TimeUnit.MILLISECONDS)
                .build();
        2.创建Request 
        Request request = new Request.Builder()
                .url("https://baidu.com")
                .post(RequestBody.create(MediaType.parse(""), ""))
                .tag("TAG")
                .build();
        3.发送请求
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });

第三部分:逐个分析拆分源码

1.创建 OkHttpClient,建造者模式创建,可以随意配置所需参数,并且官方说建议全局只有一个这个类对象,就是建议设置成静态的。后面分析。

 * <p>OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
 * all of your HTTP calls. This is because each client holds its own connection pool and thread
 * pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
 * client for each request wastes resources on idle pools.

2.创建Request,创建请求,建造者模式,就是设置url啊header啊什么的,没什么可说的。

3.发送请求,需要一点点拆分了。

client.newCall(request)

进去看看

  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

创建了一个真正的请求类realcall,就是说后面的client.newCall(request).enqueue()中后半部分,实际上是realCall.enqueue(),我们看看

//realcall中  
@Override public void enqueue(Callback responseCallback) {
    //省略...
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

核心代码就2点:第一client登场了,用他的调度器插入了任务,调度器我们后面说,先知道他是一个线程池就可以了。第二,这里又创建了一个AsyncCall类,这个类是RealCall的内部类,内部类特点就是持有外部类的引用,所以RealCall中所有参数都可以直接使用。看一眼这个类:

final class AsyncCall extends NamedRunnable {
    //省略...
    @Override protected void execute() {
      //暂时不需要...
    }
  }

暂时只需要看2点,第一点是RealCall内部类,且是NamedRunnable的子类,也就是是一个任务。那么既然是一个任务,我们把这个任务放到上面那个线程池中,就会调用run方法,而run方法啥也没干,就调用了execute方法.下面重点看这个方法:

    @Override protected void execute() {
      try {
        Response response = getResponseWithInterceptorChain();//请求
        if (retryAndFollowUpInterceptor.isCanceled()) {//失败(取消了)
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          responseCallback.onResponse(RealCall.this, response);//成功
        }
      } catch (IOException e) {
          responseCallback.onFailure(RealCall.this, e);//失败
      } finally {
        client.dispatcher().finished(this);//结束任务
      }
    }

发送请求(这里就有各种拦截器)---成功、失败、取消----结束任务(client.dispatcher().finished(this) 方法,后面讲client会用到)。没了,就这么简单。下面就是重点看看发送请求和拿到数据怎么来的:

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

先添加了一大堆拦截器,关注点,第一,我们可以在最开始加入自己的拦截器,也可以在后面发送网络前加入拦截器。第二就是最后执行的方法 RealInterceptorChain 的proceed方法:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
    //。。。
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    //...

    return response;
  }

省略代码做了一些校验,然后就是开始执行责任链中逐个拦截器...最后再分析拦截器。到此,我们就已经联网成功拿到了数据。

4.上面把联网的流程讲完了,该重点说两处忽略的细节。

   4.1)client.dispatcher() 也就是 Dispatcher类是如何执行我们的联网请求的。这里就是生产者消费者模式。

public final class Dispatcher {
  //最大请求数64个
  private int maxRequests = 64;
  //最大域名数5个
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private 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() {
  }

  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;
  }
//...先省略
}

这类中有写参数很关键,说明一下:

     maxRequests 最大请求数64个

     maxRequestsPerHost 最大请求域名是5个

     readyAsyncCalls 等待异步执行的队列,没有大小限制

     runningAsyncCalls 正在执行的异步请求队列,没有大小限制

     runningSyncCalls  正在执行的同步请求队列,没有大小限制

     executorService  线程池,懒加载,核心线程数为0,非核心线程数为最大值,非核心线程空闲存活最长时间为60s,没有缓冲队列(来了请求就执行)。

     通过上面这些参数配置,我们可以得知,线程池大小不限制,请求来了就处理,那就是maxRequests 变量控制着最大联网数量,64个,如果超了则将请求放到上面的异步等待队列中。当然这些参数都可以修改。默认值最大为64。下面可以看看

  client.dispatcher().enqueue(new AsyncCall(responseCallback));中enqueue方法
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

如果说正在执行的网络请求小于64个且域名总数小于5个,则直接执行,否则添加到等待队列中。那等待队列什么时候执行呢?当然是RealCall联网结束的时候就用到了,上面有一行 client.dispatcher().finished(this)  代码,就是realcall中结束后调用的

  /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

  //第二个参数为true
  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) 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.
    }
  }

其实源码里已经把调用关系做了注释了,finished方法调用重载方法,先是将当前请求从正在请求队列中移除掉,然后执行promoteCalls 方法,这个方法做一些比如还有没有请求啊,域名多少个限制啊判断,然后从等待队列里取出最后一个请求,交给线程池执行,同时添加到正在执行队列中。至此调度器分析完毕。

   4.2)上面还有一个地方没有仔细分析,那就是经过一系列拦截器之后,我们就拿到了数据,到底怎么联网的?我们来分析一下ConnectInterceptor这个拦截器,其他拦截器大同小异,只是职责不同。

     

public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

这个类中intercept方法中,首先获取到了realChain,但是这个类在RealCall的责任链刚开始调用的构造地方所有参数基本上都是null

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));
    //看这里,创建RealInterceptorChain的参数中中间三个全是StreamAllocation HttpCodec 
    //Connection 三个参数都是空
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

这就尴尬了,也就是说我们上面拦截器中realChain.streamAllocation();获取到的值为null,那么肯定在这个拦截器前面的拦截器中对他进行赋值操作了,最终在RetryAndFollowUpInterceptor 这个拦截器中找到了:

public final class RetryAndFollowUpInterceptor implements Interceptor {
  
  private StreamAllocation streamAllocation;
  
  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    //创建实例
    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);
       //...
      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        
      } catch (RouteException e) {
      //。。。
       
    }
  }
}

下面看看streamAllocation.newStream()方法吧:

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
    int connectTimeout = client.connectTimeoutMillis();
    int readTimeout = client.readTimeoutMillis();
    int writeTimeout = client.writeTimeoutMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

调用了查找可以复用的健康链接findHealthyConnection方法:

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    while (true) {
       //调用查找方法
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);
       //找到了,返回
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      // 没找到,去创建一个
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }

      return candidate;
    }
  }

这里调用了查找链接方法,如果找到了直接返回,没找到就创建一个,先看找的方法:

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    Route selectedRoute;
    synchronized (connectionPool) {
      //三种情况不行
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      // 难点一:这里开始看看是否有链接可以复用,但是this.connection什么时候赋的值呢?
      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
        return allocatedConnection;
      }

      // 难点二:如果没有可以复用的就去链接池里找,但是这个方法有点奇怪呢?咋给connection 赋的值?
      Internal.instance.get(connectionPool, address, this);
      if (connection != null) {
        return connection;
      }

      selectedRoute = route;
    }

    // 如果没有找到,则去创建一个链接,这是一个阻塞耗时操作
    RealConnection result;
    synchronized (connectionPool) {
      //新建
      result = new RealConnection(connectionPool, selectedRoute);
      //同步数据 this.connection 赋值
      acquire(result);
      //取消了就退出
      if (canceled) throw new IOException("Canceled");
    }

    // Do TCP + TLS handshakes. This is a blocking operation.三次握手和验证
    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      // 将创建的链接放到缓存池中
      Internal.instance.put(connectionPool, result);
      //
    }
    return result;
  }

上面逻辑依旧很清晰,如果说上一个联网还可以用,就复用,如果不能用就去连接池中找看看还有能用的吗,如果有就返回,没有就去创建一个新的链接,并放到缓存池中。

难点一:this.connection 表示上一个刚结束的链接是否可以复用,为什么是上一个呢?因为,第一个链接这个值肯定是个null,而从第二个开始,这个值就还是null,从第三个开始的时候就不是null了,那什么时候赋值的呢?肯定是上一个链接结束的地方或者说是第二个请求开始的时候,答案是从第二个请求开始的时候,这就是为什么前两个请求拿到的都是null,从第三个开始的时候才不是null了。看源码:还是StreamAllocation中方法

  public void acquire(RealConnection connection) {
    //赋值
    this.connection = connection;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

那 什么时候调用的这个方法呢?在ConnectionPool链接池中调用的, 

/** Returns a recycled connection to {@code address}, or null if no such connection exists. */
  RealConnection get(Address address, StreamAllocation streamAllocation) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address)) {
        streamAllocation.acquire(connection);
        return connection;
      }
    }
    return null;
  }

链接池中这个方法在哪调用呢?在OkhttpClient 中静态代码块中 Internal.instance实例中的get()方法调用:

static {
    Internal.instance = new Internal() {
      //这里调用了
      @Override public RealConnection get(
          ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
        return pool.get(address, streamAllocation);
      }
    };
  }

那这个get方法啥时候调用呢?回头看看上面的难点一下面的难点二处代码,就是调用的这个方法。这就是为什么第一次、第二次都没办法复用,只有到了第三次才可以复用原因。难点二也就一起解决了。

好了,至此复用链接就讲完了,至于联网细节就不看了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值