Android网络编程(十) 之 OkHttp3原理分析

1 使用回顾

我们在前面博文《Android网络编程(九) 之 OkHttp3框架的使用》中已经对OkHttp的使用进行过介绍。今天我们接着往下来阅读OkHttp的关键源码从而它进行更加深入的理解。开始前,先来回顾一下简单的使用,通过使用步骤来深入分析每行代码背后的原理,代码如:

private void test() {
        // 1 创建 OkHttpClient 对象,并可设置连接超时时间
//        OkHttpClient client = new OkHttpClient.Builder()
//                .connectTimeout(10, TimeUnit.SECONDS)
//                .readTimeout(10,TimeUnit.SECONDS)
//                .writeTimeout(10,TimeUnit.SECONDS)
//                .cache(new Cache(cacheDirectory, cacheSize))
//                .build();
        OkHttpClient client = new OkHttpClient();

        // 2 创建一个Request请求
        final Request request = new Request.Builder()
                .url("http://wwww.baidu.com")
                .get() //默认就是GET请求,可以不写
//                .post(RequestBody.create(MediaType.parse("text/x-markdown; charset=utf-8"), "Hello world!"))
                .build();

        // 3 创建一个call对象
        Call call = client.newCall(request);

        // 4 发起请求
//        Response response = call.execute();
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                // 这里是子线程
//                final String res = response.body().string();
//                runOnUiThread(new Runnable() {
//                    public void run() {
//                        TextView控件对象.setText(res);
//                    }
//                });
            }
        });
    }

可见其步骤大概是:

1 创建 OkHttpClient 对象,并可设置其连接超时时间和缓存信息;

2 创建一个Request请求,指定Url和请求方式,请求方式get是默认可不写,而Post的话如上述注释代码代表post一个字符串;
3 使用OkHttpClient对象的newCall方法传入Request对象来创建一个Call对象;
4 Call对象发起请求,如果同步请求就使用:Response response = call.execute();。

2 原理分析

因为OkHttp 从4.0.0 RC 3版本后它的实现语言从Java变成了Kotlin来实现。所以我们在开始之前,如果你想还是针对Java代码进行分析,可先将版本回退到3.14.4版本。

2.1 创建OkHttpClient对象

OkHttpClient的创建是通过一个构造器模式进行的初始化,其中包括:任务分发器、支持协议、TLS版本、连接池、超时时长等信息。如以下注释,我们下面将重要来关注Dispatcher任务分发器和ConnectionPool连接池。

OkHttpClient.java

public Builder() {
  dispatcher = new Dispatcher();						// **任务分发器**
  protocols = DEFAULT_PROTOCOLS;						// 支持的协议
  connectionSpecs = DEFAULT_CONNECTION_SPECS;					// TLS版本和连接协议
  eventListenerFactory = EventListener.factory(EventListener.NONE);	        // 事件列表监听器
  proxySelector = ProxySelector.getDefault();					// 代理选择器
  if (proxySelector == null) {
    proxySelector = new NullProxySelector();
  }
  cookieJar = CookieJar.NO_COOKIES;						// Cookie
  socketFactory = SocketFactory.getDefault();					// Socket工厂
  hostnameVerifier = OkHostnameVerifier.INSTANCE;				// 主机名
  certificatePinner = CertificatePinner.DEFAULT;				// 证书链
  proxyAuthenticator = Authenticator.NONE;					// 代理身份验证
  authenticator = Authenticator.NONE;						// 本地验证
  connectionPool = new ConnectionPool();					// **连接池**
  dns = Dns.SYSTEM;								// 基础域名
  followSslRedirects = true;							// SSL重定向
  followRedirects = true;							// 本地重定向
  retryOnConnectionFailure = true;						// 连接失败重试
  callTimeout = 0;								// 请求超时时间
  connectTimeout = 10_000;							// 连接超时时间
  readTimeout = 10_000;								// 读取超时时间
  writeTimeout = 10_000;							// 写入超时时间
  pingInterval = 0;								// 命令间隔
}

2.1.1 Dispatcher任务分发器

Dispatcher.java

public final class Dispatcher {
/** 总的最大请求数 */
  private int maxRequests = 64;
/** 每台主机最大请求数 */
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** 执行调用的线程池 */
  private @Nullable ExecutorService executorService;

  /** 准备执行的异步请求队列 */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** 正在运行的异步请求队列,包括尚未完成的已取消呼叫 */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** 正在运行的同步请求队列,包括尚未完成的已取消呼叫 */
  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<>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }
  // ……
}

Dispatcher类虽然它的构造函数是空的,但实际上从它的成员变量能发现信息量很多,如三个双向的任务队列和一个线程池。其中三个队列分别是:准备执行的异步请求队列 readyAsyncCalls、正在运行的异步请求队列 runningAsyncCalls 和 正在运行的同步请求队列 runningSyncCalls。而线程池是一个没有核心线程,线程数量无上限,空闲60秒回收,适用于大量耗时较短的异常任务的线程池,它用于任务工作。

2.1.2 ConnectionPool连接池

ConnectionPool.java

public final class ConnectionPool {
  final RealConnectionPool delegate;

  /**
   * Create a new connection pool with tuning parameters appropriate for a single-user application.
   * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
   * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity.
   */
  public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.delegate = new RealConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit);
  }

  /** Returns the number of idle connections in the pool. */
  public int idleConnectionCount() {
    return delegate.idleConnectionCount();
  }

  /** Returns total number of connections in the pool. */
  public int connectionCount() {
    return delegate.connectionCount();
  }

  /** Close and remove all idle connections in the pool. */
  public void evictAll() {
    delegate.evictAll();
  }
}

RealConnectionPool.java

public final class RealConnectionPool {
  /**
   * Background threads are used to cleanup expired connections. There will be at most a single
   * thread running per connection pool. The thread pool executor permits the pool itself to be
   * garbage collected.
   */
  private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));
  // ……
  private final Deque<RealConnection> connections = new ArrayDeque<>();
  final RouteDatabase routeDatabase = new RouteDatabase();
  boolean cleanupRunning;
  // ……
  public RealConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // ……
  }
  // ……
}

ConnectionPool类构造函数中也是创建了一个RealConnectionPool对象,传入了两个参数值:最大空闲连接数5个 和 连接保活时间是5分钟。

而RealConnectionPool类里头存在着一个线程池,该线程池没有核心线程,线程数量无上限,空闲60秒回收,用于清理长时间闲置的和泄漏的连接。类里还有一个用于管理所有的连接的双端队列Deque<RealConnection>。

2.2创建一个Request请求对象

Request.java

public Builder() {
  this.method = "GET";
  this.headers = new Headers.Builder();
}

Builder(Request request) {
  this.url = request.url;
  this.method = request.method;
  this.body = request.body;
  this.tags = request.tags.isEmpty()
      ? Collections.emptyMap()
      : new LinkedHashMap<>(request.tags);
  this.headers = request.headers.newBuilder();
}

Request类比较简单,就是做一些传入url、get/post、body等信息

3.3创建Call对象

通过前面步骤创建了OkHttpClient和Request对象后,便可使用OkHttpClient对象的newCall方法传入Request对象,来看看newCall方法的代码:

Dispatcher.java

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

RealCall.java

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.transmitter = new Transmitter(client, call);
  return call;
}
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  this.client = client;
  this.originalRequest = originalRequest;
  this.forWebSocket = forWebSocket;
}

3.3.1 Transmitter发射器

Transmitter.java

public Transmitter(OkHttpClient client, Call call) {
  this.client = client;
  this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
  this.call = call;
  this.eventListener = client.eventListenerFactory().create(call);
  this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}

在上面newRealCall方法内创建了一个RealCall对象后再创建了一个Transmitter对象,并传入OkHttpClient对象和RealCall对象。Transmitter构造函数除了获得传入的两个参数对象外,还获得了OkHttpClent中的连接池和事件列表监听器。Transmitter主要是用于判断是否有可以重用的连接或者对没用的连接需释放。

3.4 发起异步请求

异步请求调用了call对象的enqueue方法,并传入回调接口,继续看代码:

RealCall.java

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

方法中可见,短短几行代码做了三件事情:

  1. 先进行了判断是否已执行过,如果已经执行过便抛出异常;
  2. 开始调用了Transmitter对象的callStart方法,通知网络请求开始;
  3. 创建一个AsyncCall异步任务对象并传递给任务分发器。

任务调度器Dispatcher#enqueue方法

Dispatcher.java

void enqueue(AsyncCall call) {
  synchronized (this) {
    // 关键代码1,将任务添加到准备运行的异步请求队列中
    readyAsyncCalls.add(call);

    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
    // the same host.
    if (!call.get().forWebSocket) {
      // 关键代码2,查找是否存在同一主机的任务
      AsyncCall existingCall = findExistingCallWithHost(call.host());
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
    }
  }
  // 关键代码3,分配任务并执行
  promoteAndExecute();
}

enqueue方法主要做了3件事情:

  1. 将任务添加到准备运行的异步请求队列中;
  2. 查找同一主机的任务;
  3. 分配任务并执行。

findExistingCallWithHost方法查找同一主机的任务

Dispatcher.java

@Nullable private AsyncCall findExistingCallWithHost(String host) {
  for (AsyncCall existingCall : runningAsyncCalls) {
    if (existingCall.host().equals(host)) return existingCall;
  }
  for (AsyncCall existingCall : readyAsyncCalls) {
    if (existingCall.host().equals(host)) return existingCall;
  }
  return null;
}

上述方法可见,方法中首先遍历了正在运行的异步请求队列 然后再遍历准备运行的异步请求队列,来返回是否存在同一主机的任务。

promoteAndExecute方法分配任务并执行

Dispatcher.java

private boolean promoteAndExecute() {
  assert (!Thread.holdsLock(this));

  List<AsyncCall> executableCalls = new ArrayList<>();
  boolean isRunning;
  synchronized (this) {
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall asyncCall = i.next();

      if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
      if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

      i.remove();
      asyncCall.callsPerHost().incrementAndGet();
      executableCalls.add(asyncCall);
      runningAsyncCalls.add(asyncCall);
    }
    isRunning = runningCallsCount() > 0;
  }

  for (int i = 0, size = executableCalls.size(); i < size; i++) {
    AsyncCall asyncCall = executableCalls.get(i);
    asyncCall.executeOn(executorService());
  }

  return isRunning;
}

上述方法可见,对准备队列进行了遍历,并判断正在运行的请求队列是否 >= 最大请求数(默认64)和 判断此请求所属主机的正在执行任务 >= 最大值(默认5),如果条件没问题,便将本身任务从准备队列移除并添加到正在运行的队列。最后遍历刚才添加到正在运行的队列对其调用executeOn方法传入Dispatcher中的线程池进行执行任务。

executeOn方法执行任务

RealCall.java

final class AsyncCall extends NamedRunnable {
  // ……
  void executeOn(ExecutorService executorService) {
    assert (!Thread.holdsLock(client.dispatcher()));
    boolean success = false;
    try {
      // 关键代码
      executorService.execute(this);
      success = true;
    } catch (RejectedExecutionException e) {
      InterruptedIOException ioException = new InterruptedIOException("executor rejected");
      ioException.initCause(e);
      transmitter.noMoreExchanges(ioException);
      responseCallback.onFailure(RealCall.this, ioException);
    } finally {
      if (!success) {
        client.dispatcher().finished(this); // This call is no longer running!
      }
    }
  }

  @Override protected void execute() {
    boolean signalledCallback = false;
    // 开始超时计算
    transmitter.timeoutEnter();
    try {
      // 关键代码:调用 getResponseWithInterceptorChain()获得响应内容
      Response response = getResponseWithInterceptorChain();
      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);
      }
    } catch (Throwable t) {
      cancel();
      if (!signalledCallback) {
        IOException canceledException = new IOException("canceled due to " + t);
        canceledException.addSuppressed(t);
        // 关键代码:返回取消的失败的请求响应
        responseCallback.onFailure(RealCall.this, canceledException);
      }
      throw t;
    } finally {
      // 关键代码:将任务从正在运行队列中移除
      client.dispatcher().finished(this);
    }
  }
}

因为AsyncCall继承于NamedRunnable,而NamedRunnable又继承于Runnable,所以AsyncCall方法中关键代码:executorService.execute(this);最终会调用到execute方法去。execute方法内,先开始了超时计算,接着重点调用了getResponseWithInterceptorChain方法来获得请求结果,然后根据情况返回我们传入的Callback接口中的onResponse或onFailure方法,最后无论成功与否将任务从正在运行异步队列中移除。

3.5发起同步请求

认识了异步请求过程后,再来看同步请求会发现很多相同的地方,如代码。

RealCall.java

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  transmitter.timeoutEnter();
  transmitter.callStart();
  try {
    // 关键代码
    client.dispatcher().executed(this);
    return getResponseWithInterceptorChain();
  } finally {
    client.dispatcher().finished(this);
  }
}

上述方法,可见完成了6件事情:

  1. 先进行了判断是否已执行过,如果已经执行过便抛出异常;
  2. 开始调用了Transmitter对象的timeoutEnter方法,开始超时计算;
  3. 开始调用了Transmitter对象的callStart方法,通知网络请求开始;
  4. 任务自身添加到正在运行的同步请求队列;
  5. 仍然是调用了getResponseWithInterceptorChain方法来获得请求结果;
  6. 最后无论成功与否将任务从正在运行同步队列中移除。

3.6 通过getResponseWithInterceptorChain方法认识拦截器

异步请求和同步请求最终核心都调用到getResponseWithInterceptorChain方法才返回结果,所以我们来重点看看getResponseWithInterceptorChain是做了什么事情。

RealCall.java

Response getResponseWithInterceptorChain() throws IOException {
  // 关键代码1,创建一个拦截器列表
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());				// 添加Application拦截器,也就是开发者可以自定义的应用拦截器
  interceptors.add(new RetryAndFollowUpInterceptor(client));		// 添加重定向拦截器		
  interceptors.add(new BridgeInterceptor(client.cookieJar()));	        // 添加必要的请求头信息、gzip处理拦截器
  interceptors.add(new CacheInterceptor(client.internalCache()));	// 添加缓存处理拦截器
  interceptors.add(new ConnectInterceptor(client));			// 添加连接拦截器
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());		        // 添加Network拦截器,也就是开发者可以自定义的网络拦截器
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));		// 添加访问服务器拦截器

  Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
      originalRequest, this, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  boolean calledNoMoreExchanges = false;
  try {
    // 关键代码2
    Response response = chain.proceed(originalRequest);
    if (transmitter.isCanceled()) {
      closeQuietly(response);
      throw new IOException("Canceled");
    }
    return response;
  } catch (IOException e) {
    calledNoMoreExchanges = true;
    throw transmitter.noMoreExchanges(e);
  } finally {
    if (!calledNoMoreExchanges) {
      transmitter.noMoreExchanges(null);
    }
  }
}

我们在上一篇文章《Android网络编程(九) 之 OkHttp框架的使用》中有简单介绍了拦截器的结构以及开发者可自定义的两种拦截器,其实就是对应上述代码。方法开始创建了一个用于存在拦截器列表的数组,然后按顺序将所有拦截器添加到数组中,接着利用该数组创一个RealInterceptorChain对象,再对其调用proceed方法。

RealInterceptorChain.java

@Override public Response proceed(Request request) throws IOException {
  return proceed(request, transmitter, exchange);
}

public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange) throws IOException {
  if (index >= interceptors.size()) throw new AssertionError();

  calls++;

  // ……

  // Call the next interceptor in the chain.
  RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
      index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);

  // ……

  return response;
}

上述代码可见,proceed是由多个入口进行调用,而每次调用后index都会+1,其实这就是逐一对interceptors数组内的拦截器进行获取和执行其intercept方法。

3.6.1 重定向拦截器RetryAndFollowUpInterceptor

重定向拦截器的主要作用是负责请求的重定向操作以及请求失败后像路由错误、IO异常等的失败的重试。

RetryAndFollowUpInterceptor.java

@Override public Response intercept(Chain chain) throws IOException {
  Request request = chain.request();
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Transmitter transmitter = realChain.transmitter();

  int followUpCount = 0;
  Response priorResponse = null;
  // 关键代码1:开启一个循环,因为重定向可能不止一次
  while (true) {
    // 关键代码2:连接准备,如判断是否相同连接、是否释放连接等
    transmitter.prepareToConnect(request);

    if (transmitter.isCanceled()) {
      throw new IOException("Canceled");
    }

    Response response;
    boolean success = false;
    try {
      // 关键代码3:触发下一个拦截器
      response = realChain.proceed(request, transmitter, null);
      success = true;
    } catch (RouteException e) {
      // ……
    } catch (IOException e) {
      // ……
    } finally {
      // The network call threw an exception. Release any resources.
      if (!success) {
        transmitter.exchangeDoneDueToException();
      }
    }

    // Attach the prior response if it exists. Such responses never have a body.
    if (priorResponse != null) {
      response = response.newBuilder()
          .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
          .build();
    }

    Exchange exchange = Internal.instance.exchange(response);
    Route route = exchange != null ? exchange.connection().route() : null;
    // 关键代码4:重定向处理
    Request followUp = followUpRequest(response, route);

    if (followUp == null) {
      if (exchange != null && exchange.isDuplex()) {
        transmitter.timeoutEarlyExit();
      }
      return response;
    }

    RequestBody followUpBody = followUp.body();
    if (followUpBody != null && followUpBody.isOneShot()) {
      return response;
    }

    closeQuietly(response.body());
    if (transmitter.hasExchange()) {
      exchange.detachWithViolence();
    }

    // 关键代码5:重定向次数不能大于20次
    if (++followUpCount > MAX_FOLLOW_UPS) {
      throw new ProtocolException("Too many follow-up requests: " + followUpCount);
    }

    request = followUp;
    priorResponse = response;
  }
}

该方法内部执行过程大概是这么几件事:

  1. 开启一个循环,因为重定向可能不止一次;
  2. 执行Transmitter的prepareToConnec方法进行连接准备,如判断是否相同连接、是否释放连接等;
  3. 执行Chain的proceed方法触发下一个拦截器;
  4. 执行followUpRequest方法处理重定向,方法内会做30X(300~399是重定向相关返回码)的返回码判断从而进行重定向请求;
  5. 判断重定向是否大于最大限制。

3.6.2 必要的请求头信息、gzip处理拦截器BridgeInterceptor

该拦截器主要是添加一些必要的请求头信息,代码相对较容易理解。

BridgeInterceptor.java

@Override public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder();

  RequestBody body = userRequest.body();
  if (body != null) {
    MediaType contentType = body.contentType();
    if (contentType != null) {
      requestBuilder.header("Content-Type", contentType.toString());
    }

    long contentLength = body.contentLength();
    if (contentLength != -1) {
      requestBuilder.header("Content-Length", Long.toString(contentLength));
      requestBuilder.removeHeader("Transfer-Encoding");
    } else {
      requestBuilder.header("Transfer-Encoding", "chunked");
      requestBuilder.removeHeader("Content-Length");
    }
  }

  if (userRequest.header("Host") == null) {
    requestBuilder.header("Host", hostHeader(userRequest.url(), false));
  }

  if (userRequest.header("Connection") == null) {
    requestBuilder.header("Connection", "Keep-Alive");
  }

  // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
  // the transfer stream.
  boolean transparentGzip = false;
  if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
    transparentGzip = true;
    requestBuilder.header("Accept-Encoding", "gzip");
  }

  List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
  if (!cookies.isEmpty()) {
    requestBuilder.header("Cookie", cookieHeader(cookies));
  }

  if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
  }
  // 关键代码:触发下一个拦截器
  Response networkResponse = chain.proceed(requestBuilder.build());

  HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

  Response.Builder responseBuilder = networkResponse.newBuilder()
      .request(userRequest);

  if (transparentGzip
      && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
      && HttpHeaders.hasBody(networkResponse)) {
    GzipSource responseBody = new GzipSource(networkResponse.body().source());
    Headers strippedHeaders = networkResponse.headers().newBuilder()
        .removeAll("Content-Encoding")
        .removeAll("Content-Length")
        .build();
    responseBuilder.headers(strippedHeaders);
    String contentType = networkResponse.header("Content-Type");
    responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
  }

  return responseBuilder.build();
}

3.6.3 缓存处理拦截器CacheInterceptor

该拦截器主要是对缓存进行判断是否存在和有效,然后读取缓存,更新缓存等。

CacheInterceptor.java

@Override public Response intercept(Chain chain) throws IOException {
//关键代码1,获得缓存对象
  Response cacheCandidate = cache != null
      ? cache.get(chain.request())
      : null;

  long now = System.currentTimeMillis();
  //关键代码2,通过CacheStrategy.Factory里缓存相关策略计算,获得networkRequest和cacheResponse两个对象,分别表示网络请求对象和缓存请求对象
  CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  Request networkRequest = strategy.networkRequest;
  Response cacheResponse = strategy.cacheResponse;

  if (cache != null) {
    cache.trackResponse(strategy);
  }

  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }

  // If we're forbidden from using the network and the cache is insufficient, fail.
  if (networkRequest == null && cacheResponse == null) {
    return new Response.Builder()
        .request(chain.request())
        .protocol(Protocol.HTTP_1_1)
        .code(504)
        .message("Unsatisfiable Request (only-if-cached)")
        .body(Util.EMPTY_RESPONSE)
        .sentRequestAtMillis(-1L)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
  }

// 关键代码3,判断networkRequest为null,表示缓存有效,直接返回缓存
  if (networkRequest == null) {
    return cacheResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .build();
  }

  Response networkResponse = null;
  try {
    // 关键代码4:触发下一个拦截器
    networkResponse = chain.proceed(networkRequest);
  } finally {
    // If we're crashing on I/O or otherwise, don't leak the cache body.
    if (networkResponse == null && cacheCandidate != null) {
      closeQuietly(cacheCandidate.body());
    }
  }

  // 关键代码5,判断cacheResponse不为null,表示网络请求和缓存请求同时存在
  if (cacheResponse != null) {
// 如果返回码是304,表示服务端告知跟上次请求一样没有发生过变化,即缓存仍可用
    if (networkResponse.code() == HTTP_NOT_MODIFIED) {
      Response response = cacheResponse.newBuilder()
          .headers(combine(cacheResponse.headers(), networkResponse.headers()))
          .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
          .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build();
      networkResponse.body().close();

      // Update the cache after combining headers but before stripping the
      // Content-Encoding header (as performed by initContentStream()).
      cache.trackConditionalCacheHit();
      cache.update(cacheResponse, response);
      return response;
    } else {
      closeQuietly(cacheResponse.body());
    }
  }
  // 关键代码6,缓存不可用,使用网络请求
  Response response = networkResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();
  // 关键代码7,缓存响应结果
  if (cache != null) {
    if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
      // Offer this request to the cache.
      CacheRequest cacheRequest = cache.put(response);
      return cacheWritingResponse(cacheRequest, response);
    }

    if (HttpMethod.invalidatesCache(networkRequest.method())) {
      try {
        cache.remove(networkRequest);
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
    }
  }

  return response;
}

该方法内部执行过程大概是这么几件事:

  1. 获得缓存对象;
  2. 通过CacheStrategy.Factory的get方法计算缓存是否可用,然后获得网络请求对象networkRequest和缓存对象cacheResponse;
  3. 判断networkRequest为null,表示缓存有效,直接返回缓存;
  4. 触发下一个拦截器;
  5. 判断cacheResponse不为null,表示网络请求和缓存请求同时存在进一步判断缓存是否可用;
  6. 如果上述中缓存不可用,便使用网络请;
  7. 缓存响应结果。

如果有看过我前面文章《Android网络编程(八) 之 HttpURLConnection原理分析》的朋友会发现里头的“缓存策略”,其实跟上述的缓存处理拦截器的逻辑基本上是一样的,而CacheStrategy.Factory的get方法也在文章有详细介绍过,主要是判断缓存是否为空、是否缺少握手、是否不允许缓存、是否已过期不新鲜等,这里就不重复了。

3.6.4 连接拦截器ConnectInterceptor

如果前面缓存拦截器中,缓存无效,则会触发到连接拦截器。

ConnectInterceptor.java

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  Transmitter transmitter = realChain.transmitter();

  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 关键代码1,执行连接
  Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

  // 关键代码2:触发下一个拦截器
  return realChain.proceed(request, transmitter, exchange);
}

Transmitter.java

Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
  // ……
// 关键代码
  ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
  Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

  synchronized (connectionPool) {
    this.exchange = result;
    this.exchangeRequestDone = false;
    this.exchangeResponseDone = false;
    return result;
  }
}

ExchangeFinder.java

public ExchangeCodec find(
    OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
  int connectTimeout = chain.connectTimeoutMillis();
  int readTimeout = chain.readTimeoutMillis();
  int writeTimeout = chain.writeTimeoutMillis();
  int pingIntervalMillis = client.pingIntervalMillis();
  boolean connectionRetryEnabled = client.retryOnConnectionFailure();

  try {
    // 关键代码
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
    return resultConnection.newCodec(client, chain);
  } catch (RouteException e) {
    trackFailure();
    throw e;
  } catch (IOException e) {
    trackFailure();
    throw new RouteException(e);
  }
}

上述几处代码中,首先在连接拦截器中,通过Transmitter的newExchange方法,然后再是ExchangeFinder中的finHealthyConnection方法来寻找一个健康的连接。到这里又会发现,这里的finHealthyConnection方法,跟前面文章《Android网络编程(八) 之 HttpURLConnection原理分析》里头的“网络请求”中的finHealthyConnection方法是一样的,如果继续往方法内部看下去,就是关于:创建与服务端建立Socket连接、发起与服务端的TCP连接、服务端建立TCP连接、TLS握手等的逻辑处理,这里就不重复了。

3.6.5 访问服务器拦截器CallServerInterceptor

该拦截器是拦截器列表中,最后执选择拦截器,它用于向服务器发起网络请求。

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Exchange exchange = realChain.exchange();
  Request request = realChain.request();

  long sentRequestMillis = System.currentTimeMillis();
  // 关键代码1,写入请求头信息
  exchange.writeRequestHeaders(request);

  boolean responseHeadersStarted = false;
  Response.Builder responseBuilder = null;
  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    // 如果请求有"Expect: 100-continue"的头信息,那么在发送请求正文前要先等待一个"HTTP/1.1 100 Continue" 的响应。如果没有获取该响应,就不再发送请求体并获取例如4xx响应
    if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      exchange.flushRequest();
      responseHeadersStarted = true;
      exchange.responseHeadersStart();
      responseBuilder = exchange.readResponseHeaders(true);
    }

    if (responseBuilder == null) {
      if (request.body().isDuplex()) {
        // Prepare a duplex body so that the application can send a request body later.
        exchange.flushRequest();
        BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, true));
        // 关键代码2,写入请求主体信息
        request.body().writeTo(bufferedRequestBody);
      } else {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, false));
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    } else {
      exchange.noRequestBody();
      if (!exchange.connection().isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
        // from being reused. Otherwise we're still obligated to transmit the request body to
        // leave the connection in a consistent state.
        exchange.noNewExchangesOnConnection();
      }
    }
  } else {
    exchange.noRequestBody();
  }

  // 关键代码3,结束请求
  if (request.body() == null || !request.body().isDuplex()) {
    exchange.finishRequest();
  }

  if (!responseHeadersStarted) {
    exchange.responseHeadersStart();
  }

  if (responseBuilder == null) {
    responseBuilder = exchange.readResponseHeaders(false);
  }

  // 关键代码4,获得响应头信息
  Response response = responseBuilder
      .request(request)
      .handshake(exchange.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  int code = response.code();
  if (code == 100) {
    // server sent a 100-continue even though we did not request one.
    // try again to read the actual response
    response = exchange.readResponseHeaders(false)
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    code = response.code();
  }

  exchange.responseHeadersEnd(response);

  if (forWebSocket && code == 101) {
    // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
    response = response.newBuilder()
        .body(Util.EMPTY_RESPONSE)
        .build();
  } else {
    // 关键代码5,获得响应主体信息
    response = response.newBuilder()
        .body(exchange.openResponseBody(response))
        .build();
  }

  // 关键代码6,关闭连接
  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    exchange.noNewExchangesOnConnection();
  }

  if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
    throw new ProtocolException(
        "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
  }

  return response;
}

该方法内部忽略100和101的返回码逻辑,过程大概是这么几件事:

  1. 写入请求头信息;
  2. 写入请求主体信息;
  3. 结束请求;
  4. 获得响应头信息;
  5. 获得响应主体信息;
  6. 关闭连接。

3 原理总结

至此,关于OkHttp3框架就介绍完毕,虽然过程中省略了大理的细节,但整体过程原理还是通过源码了解很多。那我们现在用简短的话来总结一下原理:

1创建 OkHttpClient 对象,用构造器模式初始化了一些配置信息:任务分发器Dispatcher(其内部包含无核心无上限适用于大量的耗时短的60S自动回收的线程池用于执行请求任务,和三个请求队列:异步准备、异步进行、同步进行)、支持协议(HTTP_2, HTTP_1_1)、连接池ConnectionPool (其内部包含一个管理闲置泄漏、维护连接的线程池)、连接/读/写超时时长等信息。

2创建Request请求对象,传入url、get/post、body等信息。

3通过 OkHttpClient 传入Request对象创建出一个 Call对象,创建过程还创建了一个Transmitter发射器(用于判断是否有可以重用的连接或者对没用的连接需释放)。

4若是异步请求,调用RealCall#enqueue方法,传入一个Callback

         4.1一个 Call 只能执行一次,否则会抛异常。

4.2创建了一个 AsyncCall 并将Callback传入,接着再交给任务分发器 Dispatcher.enqueue方法进一步处理。

方法内先将任务加入到准备运行异步队列中。

接着对遍历队列,并判断正在运行的请求队列是否 >= 最大请求数(默认64)和 判断此请求所属主机的正在执行任务 >= 最大值(默认5),如果不满足便将本身任务从准备队列移除并添加到正在运行的队列。

然后遍历刚才添加到正在运行的队列对其调用executeOn方法传入Dispatcher中的线程池进行执行任务。

最后会调用 getResponseWithInterceptorChain方法获得响应内容,最终无论成功与否将任务从正在运行异步队列中移除。

5若是同步请求,调用RealCall#execute方法,过程跟异步类似

         5.1一个 Call 只能执行一次,否则会抛异常。

         5.2将任务自身添加到正在运行的同步请求队列。

         5.3仍然是调用了getResponseWithInterceptorChain方法来获得请求结果,并在最后无论成功与否将任务从正在运行同步队列中移除。             

6RealCall#getResponseWithInterceptorChain是重要内容

    1. 创建一个数组
    2. 添加ApplicationInterceptor应用拦截器(请求开始之前最早被调用)
    3. 创建一个RetryAndFollowUpInterceptor拦截器(用于请求的重定向操作以及请求失败后像路由错误、IO异常等的失败的重试)
    4. 创建一个BridgeIntercepto拦截器并添加(用于添加一些必要的请求头信息、gzip处理等)
    5. 创建一个CacheInterceptor拦截器并添加(用于是对缓存进行判断是否存在和有效,然后读取缓存,更新缓存等)
    6. 创建一个ConnectInterceptor拦截器并添加(若前面缓存无效,则创建与服务端建立Socket连接、发起与服务端TCP连接、服务端建立TCP连接、TLS握手等逻辑处理)
    7. 添加NetworkInterceptor网络拦截器(组装完成请求之后,真正发起请求之前被调用)
    8. 创建一个CallServerInterceptor拦截器并添加(用于向服务器发起真正的网络请求)
    9. 通过RealInterceptorChain#proceed(Request)来执行整个拦截器数组的第一个拦截器,然后每一个拦截器处理完毕后再次调用此方法进行下一拦截器的执行。

 

 

 

 

发布了106 篇原创文章 · 获赞 37 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/lyz_zyx/article/details/103137379