OKHttp3 源码解析之整体流程(一)

最近抽时间分析一下 OKHttp3 的源码,关于 OKHttp 源码解析的资料已经有很多了,但是关于 OKHttp3 源码解析的文章似乎不太多,这一系列文章主要是针对 OKHttp3 的源码进行解析。首先,本篇文章将分析一下 OKHttp3 的整体流程,细节相关的知识会在后续的文章中进行介绍。好了,那咱就发车吧~ 本文中涉及到的自定义类的源码都在 Github 上的 OkHttpPractice 工程中。

  1. OKHttp 的简单使用
  2. 请求的整体流程

1. OKHttp 的简单使用

OKHttp 支持异步请求和同步请求,下面简单介绍一下异步请求和同步请求。

1.1 异步请求

因为是异步的网络请求,不会阻塞 UI 主线程,所以异步请求可以直接写在 UI 主线程中。下面是异步的 GET 请求:

       String url = "https://www.baidu.com";
       OkHttpClient client = new OkHttpClient.Builder()
               .build();
       Request request = new Request.Builder()
               .method("GET", null)
               .url(url)
               .build();
       client.newCall(request).enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {
               Log.i("lijk", "onFailure " + e.toString());
           }

           @Override
           public void onResponse(Call call, Response response) throws IOException {
               Log.i("lijk", "onResponse ");
           }
       });

1.2 同步请求

相对于异步请求,不可以在 UI 主线程中直接使用异步请求,必须子线程中进行该请求。
下面是同步的 GET 请求:

      try {
          String url = "https://www.baidu.com";
          OkHttpClient client = new OkHttpClient.Builder()
                .build();
          Request request = new Request.Builder()
                .method("GET", null)
                .url(url)
                .build();
          client.newCall(request).execute();
      } catch (IOException e) {
          e.printStackTrace();
      }

2. 请求的整体流程

从上一小节中可以大概知道 OKHttp 简单的使用,我们就从这儿开始分析 OKHttp 请求的整体流程。先分析异步请求,了解异步请求的流程之后,自然会明白同步请求。

2.1 异步请求

2.1.1 OkHttpClient

首先是创建一个 OkHttpClient 对象,可以通过下面两种方式创建 OKHttpClient 的对象,如下所示:

  // 方式一
  OkHttpClient client = new OkHttpClient.Builder()
                    .build();

  // 方式二
  OkHttpClient client = new OkHttpClient();

OKHttpClient 的源码如下所示:

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {

  ......

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

    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
  }

  ......

  public static final class Builder {
    Dispatcher dispatcher;
    @Nullable Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    ProxySelector proxySelector;
    CookieJar cookieJar;
    @Nullable Cache cache;
    @Nullable InternalCache internalCache;
    SocketFactory socketFactory;
    @Nullable SSLSocketFactory sslSocketFactory;
    @Nullable CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

    ......
  }

  ......

}

可以看到,OKHttpClient 对象是通过 Builder 模式创建的,在 OKHttpClient.Builder() 类的构造方法中,对它的属性都有默认值和默认对象。

2.1.2 RealCall

创建好 OkHttpClient 对象之后,通过调用 OkHttpClient 对象的 newCall(Request request) 方法即可创建一个 Call 对象,newCall(Request request) 方法如下所示:

/**
 * Prepares the {@code request} to be executed at some point in the future.
 */
@Override public Call newCall(Request request) {
  return new RealCall(this, request, false /* for web socket */);
}

可以看到,newCall(Request request) 方法生成的是 RealCall 类型的对象,RealCall 的源码并不算长,如下所示:


final class RealCall implements Call {
  final OkHttpClient client;
  final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
  final EventListener eventListener;

  /** The application's original request unadulterated by redirects or auth headers. */
  final Request originalRequest;
  final boolean forWebSocket;

  // Guarded by this.
  private boolean executed;

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    final EventListener.Factory eventListenerFactory = client.eventListenerFactory();

    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

    // TODO(jwilson): this is unsafe publication and not threadsafe.
    this.eventListener = eventListenerFactory.create(this);
  }

  ......

}

对于异步请求,会调用 RealCall 对象的 enqueue(Callback responseCallback) 方法,如下所示:

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

enqueue(Callback responseCallback) 方法中,会调用 Dispatcherenqueue(AsyncCall call) 方法,生成一个 AsyncCall 对象,并将其传入到 Dispatcher对象中去。

2.1.3 Dispatcher

接着分析一下 Dispatcher 的源码,如下所示:

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 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<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

  ......

  synchronized void enqueue(AsyncCall call) {
    // runningAsyncCalls异步运行任务的队列个数如果小于 64
    // 每个主机同时被请求的个数小于 5
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      // 将异步任务加入到异步运行队列中
      runningAsyncCalls.add(call);
      // 通过线程池运行该异步任务
      executorService().execute(call);
    } else {
      // 如果不满足上面两个条件,则将异步任务加入到预备异步任务队列中
      readyAsyncCalls.add(call);
    }
  }

  ......

}

Dispatcherenqueue(AsyncCall call) 方法中可以看到,得到异步任务之后,如果异步任务运行队列中的个数小于 64 并且每个主机正在运行的异步任务小于 5,则将该异步任务加入到异步运行队列中,并通过线程池执行该异步任务,若不满足以上两个条件,则将该异步任务加入到预备异步任务队列中。

2.1.4 AsyncCall

AsyncCallRealCall 的内部类,源码如下:

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 {
        // 抛出异常时,回调失败方法
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      // 从 Dispatcher 对象的异步请求队列中移除该任务
      client.dispatcher().finished(this);
    }
  }
}

可以先看一下在 finally 代码块中的部分,调用 Dispatcher 对象的 finished(AsyncCall call) 方法,源码如下:

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

最终会调用 finished(Deque<T> calls, T call, boolean promoteCalls) 方法,会将传入的 Call 对象从正在运行的异步任务队列中移除,并将符合条件的预备异步任务队列中的任务加入到正在运行的异步任务队列中,并将其放入线程池中执行。

AsyncCall 中最重要的就是 execute() 方法,其中最重要的就是下面一行:

Response response = getResponseWithInterceptorChain();

可以看到从 getResponseWithInterceptorChain() 方法得到响应对象,源码如下:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    // 用户自定义拦截器
    interceptors.addAll(client.interceptors());
    // 重试重定向拦截器
    interceptors.add(retryAndFollowUpInterceptor);
    // 桥接拦截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // 缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 连接服务器拦截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      // 用户定义的网络拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    // 请求服务器拦截器
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    // 通过 RealInterceptorChain 对象链式的调用拦截器,从而得到响应。
    return chain.proceed(originalRequest);
  }

对 OKHttp 有所了解的人应该都知道,拦截器 (interceptor) 是 OKHttp 中非常重要的思想,在源码中拦截器应用的也非常广泛和重要,上面源码中的拦截器串联起来并执行,就完成了本次的网络请求,在下一篇文章中会重点介绍拦截器的知识。

通过对 getResponseWithInterceptorChain() 方法的分析,可以看到将所有的拦截器都聚合之后,生成一个 RealInterceptorChain 拦截器调用链对象 chain,在 chain 中递归的调用所有的拦截器,最后将得到的结果返回。

这里使用的是责任链模式,拦截器分层的思想也是借鉴了网络协议中的分层思想,请求从最上层到最下层,响应是从最下层到最上层。

2.1.5 RealInterceptorChain

public final class RealInterceptorChain implements Interceptor.Chain {

  ......

  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }
}

RealInterceptorChain 中最重要的就是 proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) 方法,将之前的拦截器列表串联起来并执行,得到响应结果之后,再将结果向上一层层返回。

2.2 同步请求

分析完异步请求的流程之后,同步请求的流程就比较容易理解。

和异步请求一样,会先调用 OkHttpClient 中的 newCall(Request request) 方法生成一个 RealCall 对象

/**
 * Prepares the {@code request} to be executed at some point in the future.
 */
@Override public Call newCall(Request request) {
  return new RealCall(this, request, false /* for web socket */);
}

接下来和异步请求就不一样了,会调用 RealCall 对象的 execute() 方法,源码如下所示:

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  try {
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    client.dispatcher().finished(this);
  }
}
  • 会调用 Dispatcher 对象的 executed(RealCall call) 将该同步任务加入到 Dispatcher 对象的同步任务队列中去
  • 接着会调用 getResponseWithInterceptorChain() 方法获取到请求的响应对象。对,就是之前异步任务请求获取响应对象的同一个方法。
  • 最终,会走 finally 代码块,将此同步任务从 Dispatcher 对象的同步任务队列中移除。

如果理解之前异步任务请求的流程,那么同步请求的流程就非常容易理解了。


本文中涉及到的自定义类的源码都在 Github 上的 OkHttpPractice 工程中。


参考资料:

OkHttp源码解析 -- 俞其荣

OkHttp源码解析 -- 高沛

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容