1、引言
1.1、定义
OkHttp可以说是当前Android中最火热的网络请求轻量级框架,被很多程序员广泛用于自己的项目中,当然这也是因为它其中所隐含的策略以及其使用的简单性,才有它今天的地位的。当然看这篇博客之前最好先去了解一下socket的使用。
1.2、特性
- 允许所有同一主机地址的请求共享同一个socket,其中的实现类为ConnectionPool;为什么是同一主机地址下面也会进行讲解;
- 通过连接池减少连接延时;
- 通过Gzip压缩下载体积;
- 缓存响应,减少网络完全重复的网络访问;
- 自动恢复连接,如果你的服务有多个IP地址,当第一个访问失败的时候,它会交替尝试所配置的其他IP地址;
- OkHttp还处理了代理服务器问题和SSL握手失败问题
1.3、职责链模式
我们知道了OkHttp里面的一些特性,这里还不得不讲解一下职责链模式,因为它在OkHttp中设置interceptors的时候扮演着很重要的角色。
1.3.1、定义
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将多个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。比如在公司中办理请假,你的上级同意之后还需要向上级传递,上级的上级同意之后还要向hr部门传递并且做记录扣你的工资。
2、代码讲解
2.1、OkHttp的使用
在网上OkHttp的使用一搜一大把,这里就不做详细的讲解,主要步骤就是:
- 获取OkHttpClient对象,在构造OkHttpClient对象的过程中我们直接使用其中默认的Builder进行构造,当然也可以自定义其中的属性;
- 通过Builder构造Request对象,包括构造url、请求的方法(get、post)以及body(参数)等;
- 通过调用OkHttpClient中的newCall方法同步或者异步(execut,enqueue)的去访问网络;
- 处理返回结果(response);
2.2、代码解析
跟着使用过程的调用流程一步一步的往下走渐渐的将其中的一些谜团给解开。
2.2.1、OkHttpClient
首先会通过new的方式获取到OkHttpClient对象,在该类中我们通过Builder用于初始化各个属性的值,包括ConnectionPool(复用连接池)、SocketFactory(用于获取Socket对象)、DisPatcher等。
2.2.2、ReaCall
在调用client.newCall(request)的时候实际调用的对象是RealCall,并通过其中的同步或者异步方法进行网络访问。 (1)同步(execute):
@Override public Response execute() throws IOException { synchronized (this) { //同一个call只能执行一次 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); } } 复制代码
(2)构建interceptor
Response getResponseWithInterceptorChain() throws IOException { //通过一个列表存储interceptor,并在后续的使用中以先进先出的原则进行链的运行 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); }复制代码
2.2.3、RealInterceptorChain
该类主要负责将所有的拦截器串连起来,使所有的拦截器以递归的方式进行实现,也就是只有所有的拦截器都执行完之后才会有Response的返回。对该类中的主要方法proceed进行讲解
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"); } // 生成下一个拦截器调动该方法的RealInterceptorChain对象,其中index+1用于获取下一个拦截器 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; }复制代码
2.2.4、RetryAndFollowUpInterceptor
该类是在进行网络访问添加的第一个拦截器,用于连接重试或者连接成功后的重定向(所访问的url可能不是资源的存放路径,server端会在返回的Response中返回正确的url,然后该类会使用新的url进行重新访问),所以我们可以将其分为连接重试和重定向两部分讲解。
@Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); //生成streamAllocation对象,该对象会在ConnectInterceptor拦截器中被用到,用于获取socket连接 streamAllocation = new StreamAllocation( client.connectionPool(), createAddress(request.url()), callStackTrace); int followUpCount = 0; Response priorResponse = null; while (true) { //如果用户取消了连接,那么就通过streamAllocation对象关闭当前的socket连接 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) { //该异常是在RealConnection的connect函数中抛出; //如果异常中包含了以下异常,那么就取消重连: //(1)当前client不支持重连;(2)非IOException;(3)致命异常;(4)中断异常但是连接超时可重连; //(5)三次握手异常;(6)验证异常;(7)没有更多的连接线路 if (!recover(e.getLastConnectException(), false, request)) { throw e.getLastConnectException(); } //不释放当前连接 releaseConnection = false; continue; } catch (IOException e) { //和RouteException大同小异,只是其中一个参数不一样而已 boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { // Were 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(); } //当server端接收到client端的请求后发现资源存放在另一个位置,于是server在返回数据的时候在response //header和location字段中写入资源存放位置的url,并设置Response的状态码。当解析到新的url后就会构造一个新的request Request followUp = followUpRequest(response); //如果没有构造出request,那么直接返回从服务器返回的response if (followUp == null) { if (!forWebSocket) { streamAllocation.release(); } return response; } closeQuietly(response.body()); //重定向次数大于了最大可重定向次数 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()); } //用于检测是否是同一个连接,如果不是则构造一个新的streamAllocation创建新的connection 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 = followUp; priorResponse = response; } }复制代码
2.2.5、BridgeInterceptor
转换请求拦截器,在请求server端的数据之前,如果用户设置了header信息则直接使用如果没有则设置为默认的header信息。
@Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body() //以下是对request中header的构建过程,包括User-Agent、Cookie、Accept-Encoding等头信息的设置 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"); } // 添加编解码方式为Gzip boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } 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()); } //调用下一个拦截器 Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); //如果支持Gzip,则对数据进行压缩 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(); }复制代码
有关HTTP请求报文和响应报文参考:
2.2.5、CacheInterceptor
该类的主要作用就是主要做了以下几件事: (1)获取缓存中的数据;(2)根据缓存策略判断缓存的数据是否有效;(3)获取网路中的数据并判断是否重定向;(4)缓存从网路获取到的数据。
@Override public Response intercept(Chain chain) throws IOException { //查看是否有缓存的数据 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()); } // 如果禁止使用网络且缓存的数据无效,那么在该处构造一个空的response并返回 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 (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 (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(); 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)) { CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } //判断请求的方法是否合法 if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { } } } return response; }复制代码
2.2.6、ConnectInterceptor
该类主要是通过StreamAllocation类进行Socket的连接,连接成功后调用下一个拦截器,在StreamAllocation中主要做了以下几件事情: (1)获取读取连接、读取数据、写数据的时间限制;(2)从ConnectionPool中获取一条可复用的连接,如果没有找到那么就重新创建一条;
@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 = streamAllocation.newStream(client, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); }复制代码
2.2.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(); //写入request中请求头信息 httpCodec.writeRequestHeaders(request); Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { //判断是否可以向服务器发送请求体 if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { //强制将数据全部写入 httpCodec.flushRequest(); //获取Response中header信息,如果为true则返回空不获取 responseBuilder = httpCodec.readResponseHeaders(true); } //如果返回的Response中header信息为空则将请求体写入到请求中 if (responseBuilder == null) { Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength()); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); } else if (!connection.isMultiplexed()) { //关闭当前socket streamAllocation.noNewStreams(); } } //关闭请求 httpCodec.finishRequest(); if (responseBuilder == null) { responseBuilder = httpCodec.readResponseHeaders(false); } //设置请求数据时间、接收数据时间以及三次握手并获取到返回的Response Response response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); //获取响应体中的code int code = response.code(); //如果获取到的code=101,说明服务器还在处理则继续获取数据 if (forWebSocket && code == 101) { response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { //根据当前的Response中的信息重新传入请求体进行数据请求 response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } //关闭socket连接 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; }复制代码
100-continue:属于http的请求头,用于客户端在通过POST方式发送数据给客户端的时候,征询服务器情况是否处理POST的数据,在实际的情况中,通过在POST大数据的时候才会被使用到。
2.2.8、总结
从OkHttp的使用出发一步一步的忘往层走,可以发现这其中很好的使用了职责链模式并通过递归的方式一层一层的去执行注册的拦截器代码,对整个网络请求的过程进行很好的封装。当然这里面只是涉及到了拦截器相关的代码,还有很多重要的类没有进行讲解,比如RealConnection、ConnectionPool以及StreamAllocation等都需要花费一定的笔墨去理解去写才行。讲解完了所有的拦截器代码现在再去看OkHttp的好处是不是就很好理解了啊。