本篇文章通过源码了解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来进行进一步处理。
- 在networkRequest和cacheResponse都为null时,即不进行网络请求并且缓存不存在或者过期,此时返回504错误
- 当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()
具体流程如下
本文摘抄自《Android进阶之光——刘望舒》,为自己学习路程中的记录,不以盈利为目的。
欢迎指正。