OkHttp源码学习记录(请求网络)

本篇文章通过源码了解OkHttp的请求网络流程,先来学习OkHttp的请求网络流程。

OkHttp的请求网络流程

1.从请求处理开始分析

当我们要请求网络时需要用OkHttp.newCall(request)进行execute或者enqueue操作(同步和异步),当调用newCall()方法时,我们来看看发生了什么

newCall()方法

@Override public Call newCall(Request request) {
    return new RealCall(this, request);
  }

发现实际返回的是一个RealCall类,我们调用enqueue()异步请求网络实际上是调用了RealCall的enqueue()方法,点进去查看RealCall的enqueue方法做了什么

RealCall的enqueue()方法

void enqueue(Callback responseCallback, boolean forWebSocket) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
  }

可以看到最后是通过dispatcher来完成的,接下来分析dispatcher


2. Dispatcher的任务调度

Dispatcher主要用于控制并发请求,主要维护了以下变量

//最大并发请求数
private int maxRequests = 64;

//每个主机最大请求数
private int MaxRequestsPerHost = 5;

//消费者线程池
private ExecutorService executorService;

//将要运行的异步请求队列
private final Deque readyAsyncCalls = new ArrayDeque<>();

//正在运行的异步请求队列
private final Deque runningAsyncCalls = new ArrayDeque<>();

//正在运行的同步请求队列
private final Deque runningSyncCalls = new ArrayDeque<>();

接下来看看Dispatcher的构造方法

Dispatcher()构造方法

ublic 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有两个构造方法,可以使用自己设定的线程池。如果没有设定线程池,则会在请求网络前自己会创建默认线程池。这个线程池类似于CachedThreadPool,比较适合执行大量的耗时比较少的任务。

另外上面已经说过当调用RealCall的enqueue()方法时,实际上调用了Dispatcher的enqueue()方法,那么我们就来看看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);
    }
  }

当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时,把请求加载到runningAsyncCalls中并在线程池中执行,否则就加入到readyAsyncCalls中进行缓存等待

线程池中传进来的参数AsyncCall,是RealCall的内部类,其内部也实现了execute()方法,下面我们看看execute()方法做了什么

execute()方法

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain(forWebSocket);
        if (canceled) {
          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!
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

这里只需要看finally里无论请求结果如何都会执行finished()方法,下面看看finished()方法做了什么

finished()方法

synchronized void finished(AsyncCall call) {
    if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
    promoteCalls();
  }

finished()方法将此次请求从runningAsyncCalls用remove()移除后还调用了promoteCalls()方法,下面看看promoteCalls()方法做了什么

promoteCalls()方法

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator 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.
    }
  }

最关键的一点就是会从readyAsyncCalls中用迭代器取出下一个请求,变成AsyncCall的call对象后加入runningAsyncCalls中并交由线程池处理。

还记得刚才的AsyncCall的execute()方法吗?在第一个try块中,getResponseWithInterceptorChain()方法返回了Response,明显这是在请求网络


3. Interceptor拦截器

接下来查看getResponseWithInterceptorChain()方法做了什么

getResponseWithInterceptorChain()方法

private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
    return chain.proceed(originalRequest);
  }

发现这个方法里new了一个ApplicationInterceptorChain,是一个拦截器链,这个类也是RealCall的内部类,接下来它执行了proceed()方法

proceed()方法

@Override public Response proceed(Request request) throws IOException {
      // If there's another interceptor in the chain, call that.
      if (index < client.interceptors().size()) {
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        Interceptor interceptor = client.interceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }

      // No more interceptors. Do HTTP.
      return getResponse(request, forWebSocket);
    }

在第一个if块第2行使用get()方法从拦截器队列里取出拦截器,如果当前存在多个拦截器时会在下一行使用intercept()方法阻塞,等待下一个拦截器的调用返回

拦截器是一种能够监控,重写,重试调用的机制,通常情况下拦截器用来添加、移除、转换请求和响应头部信息。

查看上面代码最后一个return语句,返回一个getResponse()表示没有更多拦截器的话就执行网络请求,我们看看getResponse()方法做了什么

getResponse()方法

Response getResponse(Request request, boolean forWebSocket) throws IOException {
    // Copy body metadata to the appropriate request headers.
    RequestBody body = request.body();
    if (body != null) {
      Request.Builder requestBuilder = request.newBuilder();

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

      request = requestBuilder.build();
    }

    // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
    engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);

    int followUpCount = 0;
    while (true) {
      if (canceled) {
        engine.releaseStreamAllocation();
        throw new IOException("Canceled");
      }

      boolean releaseConnection = true;
      try {
        engine.sendRequest();
        engine.readResponse();
        releaseConnection = false;
      } catch (RequestException e) {
        // The attempt to interpret the request failed. Give up.
        throw e.getCause();
      } catch (RouteException e) {
···
  }
}

这段代码过长未截取完,以···表示,我们省略前半和后半部分,直接来到while循环处的上两行,发现这里new了一个HttpEngine类并且在下面的try/catch块中调用了sendRequest()方法和readResponse()方法,下面我们看看这两个方法做了什么


4. 缓存策略

sendRequest()方法

public void sendRequest() throws RequestException, RouteException, IOException {
    if (cacheStrategy != null) return; // Already sent.
    if (httpStream != null) throw new IllegalStateException();

    Request request = networkRequest(userRequest);

    InternalCache responseCache = Internal.instance.internalCache(client);
    Response cacheCandidate = responseCache != null
        ? responseCache.get(request)
        : null;

    long now = System.currentTimeMillis();
    cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
    networkRequest = cacheStrategy.networkRequest;
    cacheResponse = cacheStrategy.cacheResponse;

    if (responseCache != null) {
      responseCache.trackResponse(cacheStrategy);
    }

    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) {
      userResponse = new Response.Builder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_BODY)
          .build();
      return;
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .cacheResponse(stripBody(cacheResponse))
          .build();
      userResponse = unzip(userResponse);
      return;
    }

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean success = false;
    try {
      httpStream = connect();
      httpStream.setHttpEngine(this);
···
  }
}  

这段代码同样很长,我们省略前半和后半部分,浏览一遍发现代码显然是在发送请求,但是最主要的是做了缓存策略。第2个if块中的cacheCandidate是上次与服务器交互时缓存的Response,这里缓存均基于Map。

key是请求中url的md5,value是在文件中查询到的缓存,页面置换基于LRU算法,我们现在只需要知道cacheCandidate是一个可读取缓存Header的Response即可。

往下走第3个if块的上面cacheStrategy进行一些处理得到networkRequest和cacheResponse这两个值,根据这两个值的数据是否为null来进行进一步处理。

  1. 在networkRequest和cacheResponse都为null时,即不进行网络请求并且缓存不存在或者过期,此时返回504错误
  2. 当networkRequest为null时也就是不进行网络请求,如果缓存可以使用时则直接返回缓存,其他情况则请求网络

接下来看看HttpEngine的readResponse()方法

readResponse()方法

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (validate(cacheResponse, networkResponse)) {
        userResponse = cacheResponse.newBuilder()
            .request(userRequest)
            .priorResponse(stripBody(priorResponse))
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();
        releaseStreamAllocation();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        InternalCache responseCache = Internal.instance.internalCache(client);
        responseCache.trackConditionalCacheHit();
        responseCache.update(cacheResponse, stripBody(userResponse));
        userResponse = unzip(userResponse);
        return;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    userResponse = networkResponse.newBuilder()
        .request(userRequest)
        .priorResponse(stripBody(priorResponse))
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (hasBody(userResponse)) {
      maybeCache();
      userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
    }
  }

这个方法主要用来解析Http响应报头,如果有缓存并且可用,即第1个if语句和第2个if语句的判断条件为真,则用缓存数据并更新缓村,否则就用网络请求返回的数据。接下来看看第2个if语句中validate()方法判断缓存是否可用是如何工作的

validate()方法

private static boolean validate(Response cached, Response network) {
    if (network.code() == HTTP_NOT_MODIFIED) {
      return true;
    }

    // The HTTP spec says that if the network's response is older than our
    // cached response, we may return the cache's response. Like Chrome (but
    // unlike Firefox), this client prefers to return the newer response.
    Date lastModified = cached.headers().getDate("Last-Modified");
    if (lastModified != null) {
      Date networkLastModified = network.headers().getDate("Last-Modified");
      if (networkLastModified != null
          && networkLastModified.getTime() < lastModified.getTime()) {
        return true;
      }
    }

    return false;
  }

第一个if语句中如果缓存有效,则返回304 Not Modified,否则直接返回body。如果缓存过期或者强制放弃缓存,则缓存策略全部交给服务器判断,客户端只需要发送条件GET请求即可。
条件GET请求有两种方式,一种是Last-Modified-Date,另一种是ETag。这里采用的是前者,通过缓存和网络请求响应中的Last-Modified来计算是否是最新数据,如果是,则缓存有效


5. 失败重连

最后我们再回到RealCall类的getResponse()方法,把没截完的代码补全,如下

getResponse()方法

boolean releaseConnection = true;
      try {
        engine.sendRequest();
        engine.readResponse();
        releaseConnection = false;
      } catch (RequestException e) {
        // The attempt to interpret the request failed. Give up.
        throw e.getCause();
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
        if (retryEngine != null) {
          releaseConnection = false;
          engine = retryEngine;
          continue;
        }
        // Give up; recovery is not possible.
        throw e.getLastConnectException();
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        HttpEngine retryEngine = engine.recover(e, null);
        if (retryEngine != null) {
          releaseConnection = false;
          engine = retryEngine;
          continue;
        }

        // Give up; recovery is not possible.
        throw e;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          StreamAllocation streamAllocation = engine.close();
          streamAllocation.release();
        }
      }

      Response response = engine.getResponse();
      Request followUp = engine.followUpRequest();

      if (followUp == null) {
        if (!forWebSocket) {
          engine.releaseStreamAllocation();
        }
        return response;
      }

      StreamAllocation streamAllocation = engine.close();

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (!engine.sameConnection(followUp.url())) {
        streamAllocation.release();
        streamAllocation = null;
      }

      request = followUp;
      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
          response);
    }
  }

发现上述代码在几个catch块中只要发生IOException或者RouteException就会调用engine的recover()方法,接下来看看recover()方法做了什么

recover()方法

public HttpEngine recover(IOException e, Sink requestBodyOut) {
    if (!streamAllocation.recover(e, requestBodyOut)) {
      return null;
    }

    if (!client.retryOnConnectionFailure()) {
      return null;
    }

    StreamAllocation streamAllocation = close();

    // For failure recovery, use the same route selector with a new connection.
    return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
        forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
  }

观察它的return语句,返回了一个new HttpEngine并返回,用于完成重连,到这里就是一个完整的OkHttp请求流程,我们串联一下调用的方法有哪些:

用于请求处理:
newCall()——RealCall的enqueue()

用于任务调度:
RealCall的enqueue()——Dispatcher的enqueue()——execute()——finished()——promoteCalls()

用于拦截器:
execute()——getResponseWithInterceptorChain()——proceed()——getResponse()

用于缓存:
getResponse()——sendRequest()
getResponse()——readResponse()——validate()

用于失败重连:
getResponse()——recover()


具体流程如下


image.png

本文摘抄自《Android进阶之光——刘望舒》,为自己学习路程中的记录,不以盈利为目的。


欢迎指正。

你可能感兴趣的:(OkHttp源码学习记录(请求网络))