Android开源框架&Okhttp网络框架解析

Posted 冬天的毛毛雨

tags:

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

好文推荐
作者:RainyJiang

异步请求流程源码解析

前面我们已经介绍过了,异步请求有三个步骤

  • 创建OkHttpClient和Request对象
  • 将Request封装成Call对象
  • 调用Call的enqueue方法进行异步请求,然后在Callback中的onResponse和onFailure中进行相应的处理

异步请求流程

1. 前面的步骤基本和同步请求流程一致

详情可以查看上一篇文章(OkHttp网络框架解析(第一部分)

2. 异步请求最重要的还是调用Call的enqueue的方法来把实际的Http请求加入到调度中,

我们可以重点看下enqueue方法的具体实现:

  void enqueue(Callback responseCallback);

由于这个enqueue是个接口,我们需要看下它的实现类RealCall中是怎么实现的:

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

简单理解下,这个方法使用synchronized关键字锁住了当前RealCall的对象,然后需要对executed字段进行判断,这里就有疑问了,这个executed代表了什么呢? 其实表示这个实例有没有执行过,如果表示true的时候代表已经执行过了,就会抛出异常,显而易见,这个call对象只能被执行一次。 然后我们先看最后一行代码,通过我们传递进来的responseCallback这个对象,封装成了AsyncCall这个类,但是并不清楚它的作用是什么

3. 接下来我们来看下AsyncCall具体实现

 final class AsyncCall extends NamedRunnable {
   ....
 }

这样才发现这个类继承了NammedRunnable,不妨大胆猜测下,其实这个AsyncCall就是个Runnable,到这里豁然开朗,回到之前的代码,它其实就是构建了一个Runnable实现类的AsyncCall之后,调用了client.dispatcher分发器类,然后调用enqueue方法来完成实际的异步请求。

4. 看看dispatcher实现

  public Dispatcher dispatcher() {
    return dispatcher;
  }

这里只是单纯的返回了个dispatcher对象,那么问题来了,这个dispatcher是在哪里赋值的呢?

  • 回顾之前同步请求的解析,可以看到**OkhttpClient.Builder()**的构造方法中就已经实例化好了Dispatcher()对象

5. 调用dispatcher().enqueue方法实现

当我们实例化dispatcher对象,这里就可以调用它的enqueue方法,具体实现如下:

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
  • 首先看到这个方法头使用synchronized关键字,加了个同步锁,并且传递进来了一个Runnable的实现类AsyncCall对象
  • 接着判断runningAsyncCalls的数量是否小于最大的请求数,以及正在运行的每个主机的请求是否小于我们设定好的主机的最大请求数的设定值,如果满足这个条件,就把传递进来的AsyncCall添加到我们现在正在运行的请求队列当中,并且通过一个线程池进行请求,如果不满足就加入到缓存等待的请求队列中
  • 这时候还是要说明下runningAsyncCall,它其实是个队列,主要作用判断并发请求的数量

//缓存等待的请求队列
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<>();

Enqueue方法总结

  • 判断当前Call(实际的http请求是否仅仅只被执行了一次)
  • 封装成了一个AsyncCall对象
  • 调用client.dispatcher().enqueue,满足条件后添加到我们现在正在运行的请求队列当中,不满足加入到缓存等待的请求队列中

异步请求中的线程池

上文已经说明了如果满足了相关条件后,要将AsyncCall加入到正在运行的请求队列中,做好这个工作后,我们调用线程池去执行请求

1.看下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;
  }
  • 看到这个方法使用了synchronized关键字,保证了整个线程池是个单例,然后这个方法返回值就是线程池对象EXecutorService()
  • 实例化线程池对象ThreadPoolExecutor,传入两个参数,一个corePoolSize设置为0,另一个是最大线程池的数量,设置为Integer的最大值
  • 这里有个疑问,如果线程池数量很多很多,这时候整个性能消耗会不会特别多?其实答案是不会的,在之前Dispatcher中已经定义了一个maxRequest的值,这里限定了整个okhttp异步请求数量的最大值,所以就算在线程池中设置了整数型的最大值,也对这个行为是没啥影响的。

2.调用excute()方法

当创建好线程池之后,就要调用excute()方法,由于这个是个线程池,所以它就会调用每个子线程的Run方法,就会调用AsyncCall的Run方法

  • 之前也看到了AysncCall继承的是NamedRunnable,可以看下NamedRunnable的实现:
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

可以看到这里的Run方法其实也只是包装了一层,没有做什么实际操作,主要还是在execute()方法里,但是excute()在这里是个抽象类,所以我们需要到它的RealCall里找到它的excute()实现方法

  • 在AsyncCall的RealCall中,这里的execute()方法才是真正操作的地方,下面我们来分析下
@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);
      }
    }
  1. 首先构建了拦截器的链,后续会重点分析下这个地方,通过这个方法返回给我们一个Response对象
  2. 接着判断了这个拦截器(重定向和重试拦截器)是否取消了,如果取消了就会调用onFailure(),如果没有取消就会调用onResponse(),毋庸置疑,这里的操作都是在子线程中进行的,前面也介绍过。
  3. 在catch模块主要是做了网络失败的一些操作,打印相关日志
  4. 最后很重要的地方是调用了finish()方法
  • 我们可以看下finished()方法
 void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

这里发现调用了另一个带了三个参数的finished方法,继续分析下:

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

简单看下,在我的理解中,这里finish大致做了三件事

  1. 第一件事,把请求在正在请求的队列中删除,就是**calls.remove()**方法
  2. 第二件事,调用了promoteCalls,这个方法作用调整整个异步请求的任务队列
  3. 第三件事,需要重新计算正在执行的线程数量并且赋值,后续会有判断

异步总结

  1. 创建一个OkhttpClient对象
  2. 构建一个Request对象(Builder模式),通过OkHttpClient和Request对象,构建出Call对象
  3. 执行call的enqueue方法,把整个http请求添加到调度器中,由于是异步,需要注入一个Callback对象,用来网络请求的分发过程。

ps:对于异步请求,它的dispatcher需要特别重视,它有两个队列,一个是执行任务使用的队列,还有一个是等待执行的队列,还有一个线程池,这三个来完整的处理异步网络请求

尾言

在我看来,无论是异步请求还是同步请求,真正的网络请求还是通过拦截器链来进行操作的,分析到这,其实发现Okhttp有挺多核心,但最重要的两个任务调度核心类便是dispatcher和拦截器链

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

以上是关于Android开源框架&Okhttp网络框架解析的主要内容,如果未能解决你的问题,请参考以下文章

Android编程入门--开源框架OKHttp

Android 10大开源常用框架源码解析 系列 网络框架之一 OkHttp杂题

Android 开源框架 ( 二 ) 基于OkHttp进一步封装的okhttp-utils介绍

BAT大牛 带你深度剖析Android 10大开源框架

Android开发学习之OkHttp框架

Android开发学习之OkHttp框架