一起了解Android开源框架&&解析okhttp框架任务核心类dispatcher

Posted 冬天的毛毛雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起了解Android开源框架&&解析okhttp框架任务核心类dispatcher相关的知识,希望对你有一定的参考价值。

OkHTTP的任务调度(Dispatcher)

简单描述

我们都知道同步和异步在外部方法调用区别是非常明显的,同步使用execute(),异步使用enqueue,在这之前我们可以考虑下两个问题

  • okhttp如何实现同步异步请求?

主要通过dispatcher类来完成的,发送的同步/异步请求都会在dispatcher中管理其状态

  • 到底什么是dispatcher?

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

dispatcher源码实现

回答完上述问题相信就会有个非常直观的解释,Dispatcher就是维护我们的请求状态(包括了同步和异步),所以每当有网络请求,都会通过Call封装的dispatcher推送到就绪请求队列。而OkhTTP相对与别的网络框架来说,就优于在内部维护了一个线程池,能够更高效的

定义队列

这几个队列的定义前面已经提及了,这里有必要重新回顾一下,因为比较重要

/** Executes calls. Created lazily. */
  private @Nullable 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<>();
  • 首先看readyAsyncCall,它是就绪状态的异步请求队列,当我们整个异步请求不满足条件的时候,就会进入到就绪异步请求队列中,进行缓存,当条件再满足的时候就会放到正在执行的异步请求队列中进行执行

  • 然后是runningAyncCalls,表示的是正在执行的异步请求队列,看下它的注释,需要注意的是它包含了已经取消但没有执行完的请求

还有个比较重要的线程池executorService,因为dispatcher正是通过这个线程池来维护了整个异步请求,进行高效的网络请求

说到这里,可以思考下,为什么异步请求需要两个队列呢?

其实这里可以理解成为生产者/消费者模型,Dispatcher作为生产者,ExecutorService作为消费者池,既然是消费者/生产者模型,那必然需要两个队列来存放执行异步请求和等待的异步请求,一个Deque缓存,一个Deque正在执行的任务;

看下同步和异步请求中Dispatcher的使用

同步请求(call.execute())

/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}
  • 这里我们可以看到同步请求dispatcher做的非常简单,就是集合的添加操作
  • 每当有新的同步请求的时候,dispatcher会直接将它加入到同步的正在执行的队列中

异步请求(call.enqueue())
异步请求对于dispatcher的使用要比同步请求复杂一些

  • 前面我们已经请求了异步请求也是通过dispatcher的enqueue()方法进行的,可以看下它的源码
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
  • 首先满足两个条件(一个是正在运行异步请求队列个数要小于最大可请求个数,一个是当前请求队列Host下的请求数一定要小于我们给它设定的最大值),这个时候就可以把封装好的AsyncCall添加到我们正在执行的异步队列当中,然后交给线程池执行,这里的线程池会负责我们自动创建,销毁,以及一些线程的管理.

  • 如果不满足上述条件,直接添加到就绪异步等待队列当中,进行缓存等待操作.

  • 其实异步请求也不算复杂,但是它通过一个dispatcher来封装好了请求管理,帮助我们做了相应的工作

  • 接下来我们来看下executorService()线程池的执行

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

其实这个和一般线程池都是类似的,需要注意的是它的前三个参数的设定

  • 第一个代表核心线程池的数量,把它设定为0,因为空闲一段时间后,需要把我们所用到的线程全部销毁
  • 第二个参数表示最大的线程池,我们把它设定为整型的最大值,这是因为当我们的请求过来的时候,可以无限的扩充线程最大值,当然这是理论上这么说,但是okhttp的请求受到maxRequest变量的限制,所以也不是* 无限的创建线程池的
  • 第三个参数表示的是当我们的线程数大于核心线程数的时候,那么空闲线程最大的存活时间就是60秒

说到这里,可能很多小伙伴不太理解为什么线程池要设定这三个参数,到底意味着什么,这里我简单举个例子

在我们实际运行中,我们需要开启20个并发请求,这时候线程池也会创建20个线程,那么当工作完成之后,线程池就会在60秒之后相继关闭所有无用的线程,这就是它线程池所设定这三个参数的意义所在

  • 综上所诉,Call执行完肯定需要在runningAsyncCalls队列中移除这个线程的,那么就需要思考一个问题了:那么readySsyncCalls队列中的线程在什么时候才会执行呢?

还是要回到源码当中,可以看下enqueue方法传入的封装请求的AsyncCall,毋庸置疑,前面我们已经知道它是个Runnable,这样的话它执行的时候一定是放在线程池中的,可以追溯下AsyncCall这个线程执行方法

@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 {
        client.dispatcher().finished(this);
      }
    }
  }

dispatcher里的finish方法

上述代码前面部分已经在之前的文章中解释过来,这里我站在dispatcher的角度来看的话,可以看到有个finally,无论有没有异常它都会执行,而里面主要是执行dispatcher里的finish方法

 private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

第一步就是调用了calls.remove(),正是去删除我们正在执行的异步请求
第二步就是promoteCalls(),这个方法其实就是调整我们的任务队列,无论是异步还是同步的请求队列它都是线程不安全;所以我们调用这个方法的时候都是在synchronized代码块中执行
判断正在执行的请求数量是否等于0和idleCallback不允许为空,满足这个条件后才继续执行

看下finished方法中的promoteCalls()实现
我们上面知道了finish做了主要的操作,看下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.
    }
  }

可以看到它会先对原来的异步请求队列进行遍历,调用它的remove()删除最后一个元素之后,然后加入到正在执行的异步请求队列当中,这就是promoteCalls方法的作用

尾言

至此,以上就是我对于Okhttp中的任务调度Dispatcher做出的总结,接下来我将会分析Okhttp中的拦截器,作为okhttp核心的部分,也是okhttp中提供的一种
强大机制

最后小编分享一份,我在学习提升时,从网上收集整理了一些 android 开发相关的学习文档、面试题、Android 核心笔记等等文档,希望能帮助到大家学习提升,如有需要参考的可以直接去我 CodeChina地址:https://codechina.csdn.net/u012165769/Android-T3 访问查阅。

以上是关于一起了解Android开源框架&&解析okhttp框架任务核心类dispatcher的主要内容,如果未能解决你的问题,请参考以下文章

wfb-ng 开源工程结构&代码框架简明介绍

Android开源实战:手把手带你实现一个简单好用的搜索框(含历史搜索记录)

[android] smartimageview&常见的开源代码

Rockcip Android多媒体框架 & Codec2

#冲刺创作新星# #跟着小白一起学鸿蒙# [十三]简析蓝牙协议栈

Spring boot 项目脚手架&amp;开源框架