OKHttp(一)dispatcher分发器

简介

OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Android4.4以后开始将源码中的HttpURLConnection底层实现替换为OKHttp,同时现在流行的Retrofit框架底层同样是使用OKHttp的。

优点:

  • 支持Spdy、Http1.X、Http2、Quic以及WebSocket
  • 连接池复用底层TCP(Socket),减少请求延时
  • 无缝的支持GZIP减少数据流量
  • 缓存响应数据减少重复的网络请求
  • 请求失败自动重试主机的其他ip,自动重定向
    …….

tip:本文版本为OkHttp 3.10.0,最新版本为:4.0.1,逻辑与3版本并没有太大变化,但是改为kotlin实现。

   OkHttpClient client = new OkHttpClient();

   void syncGet(String url) throws IOException {
       Request request = new Request.Builder()
               .url(url)
               .build();

       // 执行同步请求
       Call call = client.newCall(request);
       Response response = call.execute();
       
       // 获得响应
       ResponseBody body = response.body();
       System.out.println(body.string());
   }

   void AsyncGet(String url) {
       Request request = new Request.Builder()
               .url(url)
               .build();

       // 执行异步请求
       Call call = client.newCall(request);
       call.enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {}

           @Override
           public void onResponse(Call call, Response response) throws IOException {
               // 获得响应
               ResponseBody body = response.body();
               System.out.println(body.string());
           }
       });
   }

使用起来很简单,我们用一张流程图描述一下

使用流程.png

在使用Okhttp发送一次请求时,对于使用者最少要用到OkHttpClientRequestCall这3个对象;
OkHttpClient 中是一些配置信息,比如代理的配置,ssl证书配置等等;
Request 是封装请求参数信息,比如请求地址、请求方法、请求头、请求体等等;
Call 本身是一个接口,实现类为RealCall,execute()是负责同步请求,enqueue()是负责异步请求,两者唯一区别在于一个会直接发起网络请求,而另一个使用OkHttp内置的线程池来进行。
请求任务是如何分配的呢?这就涉及到OkHttp的任务分发器。

Dispatcher 分发器

分发器就是来调配请求任务的,内部会包含一个线程池以及任务队列。在创建OkHttpClient时,我们也可以传递自己定义的线程池来创建分发器。我看一下Dispatcher 的成员属性:

public final class Dispatcher {  
    // 异步请求最大同时请求数量
    private int maxRequests = 64;
    // 异步请求同一域名同时存在的最大请求数量
    private int maxRequestsPerHost = 5;
    // 闲置任务(没有请求时可执行一些任务,由使用者设置) 
    private @Nullable
    Runnable idleCallback;
    //异步请求使用的线程池
    private @Nullable
    ExecutorService executorService;
    //异步请求等待执行队列
    private final Deque readyAsyncCalls = new ArrayDeque<>();
    //异步请求正在执行队列
    private final Deque runningAsyncCalls = new ArrayDeque<>();
    //同步请求正在执行队列
    private final Deque 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(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }
}

execute() 同步请求

这是我们来看一下我们使用的时候调用Call 对象的同步方法execute()做了什么(实际上调用的是Call的实现类RealCall的execute方法):

    @Override
    public Response execute() throws IOException {
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        try {
            // 我们先看这里,调用了dispatcher的executed方法
            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的executed()方法,ok再跟到dispatcher中:

    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
    }

就一句话,将call添加到正在运行的同步队列中,表示当前的请求任务已经开始执行了,等到请求完成再把它从队列中移除 (dispatcher中的finished方法)。

enqueue() 异步请求

再看一下RealCall的enqueue方法

    @Override
    public void enqueue(Callback responseCallback) {
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        eventListener.callStart(this);
        // 调用Dispatcher中的enqueue方法
        client.dispatcher().enqueue(new AsyncCall(responseCallback));
    }

同样的调用了Dispatcher中的enqueue方法,看一眼:

    synchronized void enqueue(AsyncCall call) {
        // 如果同时执行的请求数量不超过64,同时同一域名主机的请求数量不超过5个
        if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
            // 满足条件将请求加入正在运行的队列中,并且开始执行
            runningAsyncCalls.add(call);
            executorService().execute(call);
        } else {
            // 不满足条件将请求加入等待队列
            readyAsyncCalls.add(call);
        }
    }

上面的描述已经很详细了,那么等待队列的请求啥时候才能被临幸呢?有没有注意到
Dispatcher的enqueue方法的参数是AsyncCall,其实他就是一个Runnable。

    final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;

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

        .....

        @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 {
                    eventListener.callFailed(RealCall.this, e);
                    responseCallback.onFailure(RealCall.this, e);
                }
            } finally {
                // 当请求执行完成调用了Dispatcher的finished方法
                client.dispatcher().finished(this);
            }
        }
    }

和同步请求一样,当一个异步请求结束也调用了Dispatcher的finished方法,看一眼:

    void finished(AsyncCall call) {
        finished(runningAsyncCalls, call, true);
    }

调用重载的finished:

    private  void finished(Deque calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
            // 先把当前的请求移出正在执行队列
            if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
            // promoteCalls这里是true, 执行promoteCalls()
            if (promoteCalls) promoteCalls();
            runningCallsCount = runningCallsCount();
            idleCallback = this.idleCallback;
        }

        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }

先把当前的请求移出正在执行队列,然后执行了promoteCalls(),八成我们想要的就在这里了:

    private void promoteCalls() {
        // 如果同时执行的请求数量不超过64个,直接return
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        // 如果等待队列是空的,return!
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
        // 遍历等待队列
        for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall call = i.next();
            // 检查一下正在执行的同一host的请求数量是不是不满5个
            if (runningCallsForHost(call) < maxRequestsPerHost) {
                // 满足条件,移出等待队列,加入正在执行队列,直接执行请求任务!
                i.remove();
                runningAsyncCalls.add(call);
                executorService().execute(call);
            }
            // 正在执行队列满了,return 吧!
            if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
    }

这里就是我们要找的,当一个异步任务执行完成时,检查正在执行队列有没有空位置,遍历等待队列,把其中满足条件的请求任务拿出来,放到正在执行队列中去执行,执行完了再次检查,如此循环。

Dispatcher中的线程池

Dispatcher中的线程池很有意思,我们来看一下

 public synchronized ExecutorService executorService() {
        if (executorService == null) {
            // 核心线程数0,最大线程数Integer.MAX_VALUE,空闲线程闲置时间 60,        
            // 闲置时间单位 秒,线程等待队列 SynchronousQueue, 以及线程创建工厂
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }

这是Dispatcher中默认创建的线程池,注意里面几个关键的参数,第一个参数核心线程数量0,最大线程数 Integer.MAX_VALUE 和队列 SynchronousQueue。
为啥要这么配置呢?
首先核心线程为0,表示线程池就不用一直为我们缓存线程,线程池中所有线程都是在60s内没有工作就会被回收,在一些网络请求不频繁的app上可以节约一些内存。而最大线程Integer.MAX_VALUE与等待队列SynchronousQueue的组合能够得到最大的吞吐量,即当需要线程池执行任务时,如果不存在空闲线程不需要等待,马上新建线程执行任务!来分析一下:
SynchronousQueue 是一种无容量的队列,当向线程池中加入新请求时,只会有以下几种情况:

  • 由于没有核心线程,第一次进来的请求会直接创建新线程执行。
  • 此时有未被回收的空闲线程,复用该线程执行请求。
  • 没有空闲线程了,新请求需要添加到队列,但是队列的容量为0,相当于满了,此时要判断当前正在执行的线程数是否大于最大线程数,由于最大线程数为Integer.MAX_VALUE,肯定不会超过,所以要新建一个线程执行请求。

可以得出结论,我们的请求任务是可以立即执行的,其实还有另外2种阻塞队列:ArrayBlockingQueueLinkedBlockingQueue,但是它们一般都有容量,当请求任务进入等待队列后,可能会出现以下几种情况:

  • 没有空闲线程了,新请求添加到队列,队列中的请求一直在等待空闲线程,出现阻塞。
  • 没有空闲线程了,等待队列也满了,正在执行的线程数小于最大线程数,新建线程执行请求,出现后提交的任务先执行,而先提交的任务一直在等待的情况,同样阻塞了。
  • 没有空闲线程了,等待队列也满了,正在执行的线程数大于等于最大线程数,请求被拒绝了,尴尬。

总结一句话,Dispatcher的默认线程池参数配置保证了新建的请求都可以被立即执行,避免阻塞。
当然需要注意的时,进程的内存是存在限制的,而每一个线程都需要分配一定的内存。所以线程并不能无限个数。那么当设置最大线程数为Integer.MAX_VALUE时,OkHttp同时还有最大请求任务执行个数: 64的限制,这样既解决了这个问题同时也能获得最大吞吐。

你可能感兴趣的:(OKHttp(一)dispatcher分发器)