探究OkHttpClient的运行原理(1)

2020-03-08  本文已影响0人  零星瓢虫

网络连接是 app 最重要的功能,而 Okhttp 的使用在 android 开发中十分广泛。本篇文章将对整个OkHttp进行相关源码解析,以此能够对 Okhttp 框架有个整体的认识。

前言
OkHttp 是 Suqare 推出的在 Android 中使用的网络连接库。可以在 github 中搜索 okhttp 第一个选项即为相关内容:
https://github.com/square/okhttp/pulse

git 中对 okhhtp 的简介我们可以看到:


1583571724(1).png

HTTP 是现代应用常用的一种交换数据和媒体的网络方式,高效地使用 HTTP 能让资源加载更快,节省带宽。OkHttp 是一个高效的 HTTP 客户端,它有以下默认特性:
支持 HTTP/2,允许所有同一个主机地址的请求共享同一个 socket 连接
连接池减少请求延时
透明的 GZIP 压缩减少响应数据的大小
缓存响应内容,避免一些完全重复的请求
当网络出现问题的时候 OkHttp 依然坚守自己的职责,它会自动恢复一般的连接问题,如果你的服务有多个 IP 地址,当第一个IP请求失败时, OkHttp 会交替尝试你配置的其他 IP, OkHttp 使用现代TLS技术 (SNI, ALPN) 初始化新的连接,当握手失败时会回退到TLS 1.0。

目前最新的 okhhtp 已经更新到4.X版本,而4.x版本则用了 kotlin 语言,为(因)了(为)方(不)便(会)我这边用了 3.10.0 的 okhttp 版本进行源码查看。

如何去使用okhttp呢
这里先列举一个异步请求简单的例子:

 OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url("http://baidu.com")
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure( Call call,  IOException e) {

            }

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

            }
        });

按照上面的请求代码,我们一步步进行解析。

1 构造函数OkHttpClient okHttpClient = new OkHttpClient(),点进构造方法去看看:

public OkHttpClient() {
    this(new Builder());
  }

  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }

可以看到上述代码 Okhttpclinet 的构造方法初始化了一些参数,以及一些 builder 的变量信息。

2 继续顺着代码往下看,Request request = new Request.Builder(),Request 的构造方法:

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }

这里可以看到 Request 的构造办法也是主要去初始化了 http 请求的一些属性参数。包括url,方法,请求头,请求体等。

3 继续往下生成 Call 的方法,Call call = okHttpClient.newCall(request):
OkHttpClinet中:

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

调用了Recall的newRealCall方法:

 static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }
final class RealCall implements Call {
  final OkHttpClient client;
  final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;

Okhttpclient 通过调用 newCall 方法,而生成了 newCall 类进行返回,返回的 RealCall 类是 Call 的实现类。

4 call 执行异步请求方法,call.enqueue(new Callback() {},我们来看Call的具体实现类RealCall中的:
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));
  }

这里主要做了以下操作:
1 判断是否执行了异步请求,如果已经执行过则抛出异常
2 回调 callStart 方法
3 clien t的 diapatcher 调用 enequeue 方法,分发请求。

上面的 diapatcher 好像在哪里看到过了,我们回到 OkhttpClient 构造方法中,发现也有个 diapathcher 的初始化方法。这里就是这个 diapatcher 复制可以到 Okhttpclinet 初始化中查看。

那么接下来,我们就去看 Diapathcer 这个类和 enqueue 方法:

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

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

结合上述几段代码进行分析,可以得出一下几点重要信息:
1 Dipatcher 维护了几个队列 ,readyAsyncCalls --准备中的队列 , runningAsyncCalls --正在运行的异步队列,runningSyncCalls --运行的同步队列(用于同步请求)
2 当正在运行的队列小于 maxRequests(可以看到是64)并且对此 Host 的请求小于 maxRequestsPerHost (可以看到是5)的时候,则将当前 call 请求放入正在运行的队列之中,否则放入到准备队列之中。
3 线程池去执行请求。

现在我们看到队列里面加入了请求,并去线程池会去执行请求。既然有加入运行队列,那么是在什么时候把
readyAsyncCalls 队列中转移?或者 runningAsyncCalls 中删除呢?

接下来我们就要去看看 Call 类中的 Runable 方法
runningAsyncCalls 集合类是 AsyncCall 对象类的集合,进入到AsyncCall 类,它是 RealCall 的内部类:


  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

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

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

可以看到 AsyncCall 本质就是一个 Runnable 的实现类,实际下它最终会执行 AsyncCall 类中的 execute 方法,在execute 方法中我们可以发现:
1 根据执行流程序列会依次调用相关的回调。
2 Response response = getResponseWithInterceptorChain();这个方法是主要的请求获取到 Response 方法,这里先放着,后面会对此方法进行详细分析。
3 client.dispatcher().finished(this);对异步的队列进行操作。

进入到 Dispatcher 类中查看 finish 方法:

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

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

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

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

按照代码执行顺序依次查看可以看到 finish 方法中 promoteCalls 方法中会把当前 Call 从 runningAsyncCalls 中集合中移除,同时readyAsyncCalls 的数据转移到 runningAsyncCalls 中去执行。

至此,我们对Okhttp异步请求的数据以及队列的操作已经有了初步的认识。

其中主要涉及到 OkhttpClinet,Request,Call,Diapatcher相关类起到重要作用。


火星瓢虫_001

本篇文章先分析到这里,下一篇文章我们继续详细分析getResponseWithInterceptorChain() 方法。

探究Okhttp的运行原理(2)

上一篇下一篇

猜你喜欢

热点阅读