美文网首页
探究OkHttpClient的运行原理(1)

探究OkHttpClient的运行原理(1)

作者: 零星瓢虫 | 来源:发表于2020-03-08 17:42 被阅读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)

    相关文章

      网友评论

          本文标题:探究OkHttpClient的运行原理(1)

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