One Step By One Step 解析OkHttp3 -

作者: Jason__Ding | 来源:发表于2016-07-18 15:01 被阅读228次
    配合源码食用更佳
    

    概述


    使用过OkHttp的童鞋们都知道,同步execute(),异步enqueue()
    那么如果做到同步异步的呢,其实我们发送的同步/异步请求都会在Dispatcher中管理其状态
    其中维护了:

    • 运行中的异步请求队列 runningAsyncCalls
    • 就绪状态的异步请求队列 readyAsyncCalls
    • 运行中的同步请求队列 runningSyncCalls (注意名字的细微差别)
    • 线程池 executorService

    每当有新的同步请求到Dispatcher碗里来,直接加入到同步运行中队列。
    每当有新的异步请求到Dispatcher碗里来,那么通过maxRequests(最大请求数)和maxRequestsPerHost(相同host最大请求数)来判定,是应该进到运行中的队列并立即执行呢,还是进到就绪队列中,等待运行队列有空间了,再进到运行队列中。

    同时maxRequestsmaxRequestsPerHost都是可调整的,如果往上调了,运行队列可以进更多请求了,那么就可以将就绪状态的请求移动到运行队列中;如果往下调了,如果运行队列中的请求超过了最大请求数,那也没办法,这些超额请求执行完了,就不能再进那么多了。

    下面来看源码。

    源码


    发送同步/异步请求

    /**
    发送异步请求
    此方法为同步方法,因为runningAsyncCalls和readyAsyncCalls使用的ArrayDeque,然而ArrayDeque是非线程安全的,所以需要同步。
    如果运行中的异步请求队列的请求数小于最大请求数且当前请求对应的host下对应的请求数小于maxRequestsPerHost,那么就进队列,并且通过线程池立即执行。
    */
    synchronized void enqueue(AsyncCall call) {
      // 运行队列中的请求数小于maxRequests且相同host的运行中请求数小于maxRequestsPerHost,下面会贴runningCallsForHost()的代码
      if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        // 加入到运行中队列
        runningAsyncCalls.add(call);
        // 使用线程池执行请求,下面会贴出executorService初始化的过程。
        executorService().execute(call);
      } else {
        // 加入就绪队列中
        readyAsyncCalls.add(call);
      }
    }
    
    
    /**
      此方法也为同步方法
      直接加入到运行中同步请求队列中
    */
    synchronized void executed(RealCall call) {
      //加入到同步运行中队列
      runningSyncCalls.add(call);
    }
    

    线程池初始化

    /**
      这是一个同步的懒加载线程池的方法
      不了解线程池的童鞋请查阅相关资料
      这里大概的介绍下ThreadPoolExecutor构造器的参数,依顺序来,一个个来
     * corePoolSize 一直存在于线程池中的线程数量(除非allowCoreThreadTimeOut为true)
     * maximumPoolSize 线程池中允许存在的最大线程数量
     * keepAliveTime 除corePoolSize之外的线程,在空闲状态(执行任务完之后)能存在的最大时间
     * unit 上面那货的单位
     * workQueue 通过execute方法发送的任务,会先被缓存在这个队列中
     * threadFactory 创建线程的工厂
    */
    public synchronized ExecutorService executorService() {
      //懒加载
      if (executorService == null) {
        //corePoolSize 为 0表示,没有核心线程,所有执行请求的线程,使用完了如果过期了(keepAliveTime)就回收了。
        //maximumPoolSize 无限大的线程池空间
        executorService = new ThreadPoolExecutor(
          0,  //corePoolSize
          Integer.MAX_VALUE, //maximumPoolSize
          60,  //keepAliveTime
          TimeUnit.SECONDS,  //unit
          new SynchronousQueue<Runnable>(),  //workQueue
          Util.threadFactory("OkHttp Dispatcher", false)  //threadFactory
        );
      }
      return executorService;
    }
    

    调整请求(就绪/运行)

    /**
       根据maxRequests(最大请求数)和maxRequestsPerHost(相同host最大请求数)来调整
       runningAsyncCalls(运行中的异步请求)队列和readyAsyncCalls(就绪状态的异步请求)队列。
       使运行中的异步请求不超过两种最大值,并且如果队列有空闲,将就绪状态的请求归类为运行中。
    */
    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();
          //如果当前请求对应的host下,没有超过maxRequestsPerHost,那么将其从就绪队列中移除,并加入在运行队列。
          if (runningCallsForHost(call) < maxRequestsPerHost) {
            //移除
            i.remove();
            //加入运行队列
            runningAsyncCalls.add(call);
            //立即执行该请求
            executorService().execute(call);
          }
    
          //如果运行队列已经到达了最大请求数上限,如果还有就绪中的请求,也不管了。
          if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
      }
    

    获取同个host下的请求数

    /**
        很简单的方法,对比已有的运行中的请求和当前请求的host,相同result++,返回即可
    */
    private int runningCallsForHost(AsyncCall call) {
      int result = 0;
      for (AsyncCall c : runningAsyncCalls) {
        if (c.host().equals(call.host())) result++;
      }
      return result;
    }
    

    请求结束

    
    /**
        同步请求结束
        当该同步请求结束的时候,会调用此方法,用于将运行中的同步请求队列中的该请求移除
        今后系列中,分析RealCall的时候,会知道何时调用此方法的。
     */
    synchronized void finished(Call call) {
      if (!runningSyncCalls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    }
    
    /**
        异步请求结束
        当该异步请求结束的时候,会调用此方法,用于将运行中的异步请求队列中的该请求移除并调整请求队列,此时就绪队列中的请求可以
    
        今后系列中,分析AsyncCall的时候,会知道何时调用此方法的。
     */
    synchronized void finished(AsyncCall call) {
      if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
      promoteCalls();
    }
    
    

    调整运行队列中的最大请求数量

    /**
      以下两个方法作用类似,前者是调整总的最大请求数,后者是调整单个host下的最大请求数
      调整之后会有两种情况:
          1. 当前队列中的请求数大于最大请求数:继续执行,将已在队列中的请求执行完成
          2. 当前队列中的请求数小于最大请求数:如过就绪队列中存在请求,将其移动到运行队列中,直到运行队列的大小大于等于对大请求数
    */
    public synchronized void setMaxRequests(int maxRequests) {
      // 校验
      if (maxRequests < 1) {
        throw new IllegalArgumentException("max < 1: " + maxRequests);
      }
      // 设置
      this.maxRequests = maxRequests;
      // 调整请求
      promoteCalls();
    }
    
    public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
       if (maxRequestsPerHost < 1) {
         throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
       }
       this.maxRequestsPerHost = maxRequestsPerHost;
       promoteCalls();
     }
    
    

    剩余的方法

    剩余的方法都是获取一些数据,大家自己看看就好。
    

    总结


    Dispatcher的作用为维护请求的状态,并维护一个线程池,用于执行请求。

    欢迎交流 QQ:2424334647

    相关文章

      网友评论

        本文标题:One Step By One Step 解析OkHttp3 -

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