系列文章
1.简介
Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient,HttpURLConnection相对来说比HttpClient难用,google自从2.3版本之后一直推荐使用HttpURLConnection,并且在6.0版本的sdk中直接删掉了HttpClient类。Android4.4的源码中可以看到OkHttp替换了HttpURLConnection
相比以上两种通信类,OKHttp有巨大的优势:
- 支持HTTP/2,HTTP/2通过使用多路复用技术,在一个单独的TCP链接上支持并发,通过在一个连接上一次性发送多个请求来发送或接收数据
- 如果HTTP/2不可用,链接池减少请求延迟
- 支持GZIP,额可以压缩下载体积
- 响应缓存可以避免重复请求网络
- 会从很多常用的连接问题中自动恢复,如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OKHttp会尝试下一个IP地址
- OKHttp还处理了代理服务器问题和SSL握手失败问题
流程图:
(图1)2.使用
2.1 构建OkHttpClient
OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; //调度器,主要用于异步网络请求的控制 this.proxy = builder.proxy; //代理 this.protocols = builder.protocols; //协议 this.connectionSpecs = builder.connectionSpecs; //指定配置(tls版本、密码组等) 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; //dns this.followSslRedirects = builder.followSslRedirects; //是否支持http与https之间互相重定向 this.followRedirects = builder.followRedirects; //否是支持重定向 this.retryOnConnectionFailure = builder.retryOnConnectionFailure; //连接失败重试 this.connectTimeout = builder.connectTimeout; //连接超时时间 this.readTimeout = builder.readTimeout; //读取超时时间 this.writeTimeout = builder.writeTimeout; //写入超时时间 this.pingInterval = builder.pingInterval; }复制代码
2.2 get请求:
OkHttpClient client = new OkHttpClient(); public String get(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); Response response = client.newCall(request).execute(); return response.body().string(); }复制代码
2.3 post请求:
OkHttpClient client = new OkHttpClient(); public String post(String url,String json) throws IOException { RequestBody body = RequestBody.create(JSONObject,json); Request request = new Request.Builder() .post(body) .url(url) .build(); Response response = client.newCall(request).execute(); return response.body().string(); }复制代码
3.流程
以下以同步的get请求来分析整体的请求过程
Response response = client.newCall(request).execute();复制代码
OkHttpClient 首先创建一个Call(RealCall):
@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); } }复制代码
在RealCall的execute()方法中,有两步比较重要: client.dispatcher().executed(this);
OKHttp的分发器在请求的开始会记录此次请求,请求结束后会移除。分发器在OKHttp的异步请求中比较中要,下面再讲。
Response result = getResponseWithInterceptorChain()
OKHttp完整的网络请求就是在这步中进行的。下面我们来看下其中做了什么?
Response getResponseWithInterceptorChain() throws IOException { Listinterceptors = 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); return chain.proceed(originalRequest); }复制代码
OKHttp的完整请求就在这里,请求和返回数据的处理就是由这些拦截器来完成的,各个拦截器由RealInterceptorChain进行串联。如流程图图1。具体的串联实现,可以看下源码。
4.拦截器
OKHttp的拦截器主要是下面几种:
- client.interceptors()
- retryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- client.networkInterceptors()
- CallServerInterceptor
4.1 client.interceptors()
APP层面的拦截器(Application Interception 应用拦截器),是在配置OKHttpClient时设置的interceptors
- 对请求参数进行统一的加密处理
- 拦截不符合规则的URL
- 对请求或者返回参数设置统一的编码方式
- 其他
4.2 RetryAndFollowUpInterceptor
重试与重定向拦截器,负责重试及重定向。现在我们来分析源码:
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); streamAllocation = new StreamAllocation( client.connectionPool(), createAddress(request.url()), callStackTrace); int followUpCount = 0; Response priorResponse = null; //while循环进行重试,请求成功则返回response;请求失败,则对responseCode判断,并更改相应设置,进行重新请求。 while (true) { if (canceled) { streamAllocation.release(); throw new IOException("Canceled"); } Response response = null; boolean releaseConnection = true; try { //进行链式请求,并对请求结果进行判定是否重试 response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. // 路由连接失败,请求将不会被发送 if (!recover(e.getLastConnectException(), false, request)) { throw e.getLastConnectException(); } releaseConnection = false; continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. // 服务器连接失败,请求可能已被发送 boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { // We are throwing an unchecked exception. Release any resources. // 抛出未检查的异常,释放资源 if (releaseConnection) { streamAllocation.streamFailed(null); streamAllocation.release(); } } // 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(); } //对response进行判定,是否进行重定向重试 Request followUp = followUpRequest(response); //followUp为空,表明不是需要重定向的responseCode,返回结果response(可能是请求成功,也可能是请求失败) if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } return response; } //释放response.body() closeQuietly(response.body()); //重定向超过最大次数(20次),抛出异常 if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } // response 和 followUp 比较是否为同一个连接 // 若为重定向就销毁旧连接,创建新连接 if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation( client.connectionPool(), createAddress(followUp.url()), callStackTrace); } else if (streamAllocation.codec() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } // 将重定向操作得到的新请求设置给 request request = followUp; priorResponse = response; } }复制代码
4.3 BridgeInterceptor
桥接拦截器(内容拦截器),负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应。
@Override public Response intercept(Chain chain) throws IOException { // 将用户友好的 request 构造为发送给服务器的 request 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 are 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"); } // 设置 cookie Listcookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); } // 构造完后,将 request 交给下一个拦截器去处理。最后又得到服务端响应 networkResponse Response networkResponse = chain.proceed(requestBuilder.build()); // 保存 networkResponse 的 cookie HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); // 将 networkResponse 构造为对用户友好的 response Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); // 如果 networkResponse 使用 gzip 并且有响应体的话,给用户友好的 response 设置响应体 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); responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody))); } return responseBuilder.build(); }复制代码
4.4 CacheInterceptor
缓存拦截器,负责读取缓存直接返回、更新缓存。当网络请求有符合要求的Cache时,直接返回Cache。如果当前Cache失效,则删除。CacheStrategy:缓存策略,CacheStrategy类是一个非常重要的类,用于控制请求是网络获取还是缓存获取。
@Override public Response intercept(Chain chain) throws IOException { // 得到 request 对应缓存中的 response Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; // 获取当前时间,会和之前缓存的时间进行比较 long now = System.currentTimeMillis(); // 得到缓存策略 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(); } // If we don't need the network, we're done. //不用网络请求,返回缓存 if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; try { 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()); } } // If we have a cache response too, then we are doing a conditional get. // 如果我们同时有缓存和 networkResponse ,根据情况使用 if (cacheResponse != null) { 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()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); // 保存之前未缓存的缓存 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; }复制代码
4.5 ConnectInterceptor
连接拦截器,负责和服务器建立连接
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); StreamAllocation streamAllocation = realChain.streamAllocation(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals("GET"); //在连接池中找个可用的链接,并创建HttpCodec HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); }复制代码
链接拦截器主要是完成了网络设施的建设,包含连接connection,根据socket生成输出流sink和输入流source。 后面的CallServerInterceptor使用这些设施完成请求的发送以及响应的读取。这些请求过程,我们将在CallServerInterceptor一章中详细解读。
4.6 client.networkInterceptors()
网络请求层面的拦截器,配置OkHttpClient时设置的networkInterceptors:
请求时:
- 可以修改OkHttp框架自动添加的一些属性
- 可以观察最终完整的请求参数
返回结果:
- 能够对中间的响应进行操作,比如重定向和重试
- 当发生网络短路时,不调用缓存的响应结果
- 监控数据,就像数据在网络上传输一样
- 访问承载请求的连接Connection。
4.7 CallServerInterceptor
请求服务拦截器,负责向服务器发送请求数据、从服务器读取响应数据
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; HttpCodec httpCodec = realChain.httpStream(); StreamAllocation streamAllocation = realChain.streamAllocation(); RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); long sentRequestMillis = System.currentTimeMillis(); // 整理请求头并写入 httpCodec.writeRequestHeaders(request); Response.Builder responseBuilder = null; // 检查是否为有 body 的请求方法 if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { // If there is a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 // Continue" response before transmitting the request body. If we do not get that, return what // we did get (such as a 4xx response) without ever transmitting the request body. // 如果有 Expect: 100-continue 在请求头中,那么要等服务器的响应 if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) { // Write the request body if the "Expect: 100-continue" expectation was met. // 写入请求体 Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength()); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } else if (!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. streamAllocation.noNewStreams(); } } httpCodec.finishRequest(); // 得到响应头 if (responseBuilder == null) { responseBuilder = httpCodec.readResponseHeaders(false); } Response response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); int code = response.code(); // 如果为 web socket 且状态码是 101 ,那么 body 为空 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 { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } // 如果请求头中有 close 那么断开连接 if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } // 抛出协议异常 if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response; }复制代码
小结
本章主要讲了OkHttp的整体流程,并对源码的关键点进行了备注。详细的讲解在各个拦截器的单章讲解中,开发者可以在后面几章查看。
OkHttp使用分层的思想,每一个拦截器就是一个完成相关功能的任务层,并使用链Chain串联整个流程,使用了责任链模式。在整个链中,向用户提供了插入点,用户可以自定义拦截器,完成一些自定义的加密、数据统计等相关功能。