OkHttp库使用及源码解析
特点
- OkHttp是一个高效的Http客户端,有如下的特点:
- 支持HTTP2/SPDY黑科技
- socket自动选择最好路线,并支持自动重连
- 拥有自动维护的socket连接池,减少握手次数
- 拥有队列线程池,轻松写并发
- 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
- 基于Headers的缓存策略
使用
OkHttp是由Square开发的网络通信库,使用前先添加依赖库
dependencies {
...
compile 'com.squareup.okhttp3:okhttp:3.4.1'
}
不要忘记声明使用网络的权限
<uses-permission android:name="android.permission.INTERNET" />
一个简单的GET请求的步骤(阻塞方式):
- 创建一个OkHttpClient对象
- 创建一个Request对象
- 调用client.newCall(request)创建Call对象
- 调用call.execute()执行请求(阻塞)
try
{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder() //这里创建了Builder对象,然后进行配置
.url("http://www.baidu.com")
.build(); //创建Request对象
Response response = client.newCall(request).execute(); //创建一个Call对象,然后发送请求,response接收返回的数据,此方法blocks直到响应返回
String message = response.message(); //response状态
String responseData = response.body().string(); //将返回的数据转换为字符串
Log.d(TAG, "message : " + message);
Log.d(TAG, "data: \n" + responseData);
}
catch (Exception e)
{
e.printStackTrace();
}
当请求的域名不存在时会抛出异常:java.net.UnknownHostException
更好的用法(异步)
//创建okHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//创建一个Request
final Request request = new Request.Builder()
.url("https://github.com/hongyangAndroid")
.build();
//new call
Call call = mOkHttpClient.newCall(request);
//请求加入调度
call.enqueue(new Callback()
{
@Override
public void onFailure(Request request, IOException e)
{
//这里处理请求时发生的异常
}
@Override
public void onResponse(final Response response) throws IOException
{
//这里处理返回的response
}
});
调用enqueue()方法使得call的执行是异步的,不影响其他代码的执行。
当请求执行完后,会回调Callback里的方法,我们可以在里面执行我们的代码。
查看源码可以知道onFailure调用的时机:
Called when the request could not be executed due to cancellation, a connectivity problem or timeout.
当request由于取消、连接问题或者超时导致不能完成的情况下调用。
提交数据的POST请求
代码如下,其它跟上面的一样
RequestBody requestBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build();
Request request = new Request.Builder() //这里创建了Builder对象,然后进行配置
.url("http://www.baidu.com")
.post(requestBody)
.build(); //创建Request对象
Builder的post()方法
public Builder post(RequestBody body) {
return method("POST", body);
}
当Builder调用了post(RequestBody)方法后自动将请求方法设为“POST”
源码解析
OkHttp中主要的几个类:
- RealCall,Call的实现类,读源码从这里入手
- RealConnection,Connection的实现类,主要做TCP连接和协议(TSL握手)处理
- HttpCodec,编码Http请求和解码Http响应
- OkHttpClient,做配置工作
- Dispatcher,做异步工作的分发
- RealInterceptorChain,拦截链,负责取出拦截器并执行
OkHttp的线程池
//最大同时请求数
private int maxRequests = 64;
//每个IP最多请求数
private int maxRequestsPerHost = 5;
synchronized void enqueue(AsyncCall call) {
//从判断条件可以看出,OKHttp3同时最多有64个请求执行,而每一个host最多有5个请求同时执行。其他请求都放在等待队列中。
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
public synchronized ExecutorService executorService() {
if (executorService == null) {
//全部使用非核心线程,60秒超时回收
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
对于同步Call和异步Call的处理
使用Call.execute:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
使用Call.enqueue:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
同步Call是直接在调用方所在线程执行网络请求操作,而异步Call需要提交到线程池中执行。
无论是同步还是异步Call,最终都识调用RealCall中的getResponseWithInterceptorChain()方法来获得响应:
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
public final class RealInterceptorChain implements Interceptor.Chain {
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
}
我们手动添加的拦截器是处于最上层的,而最下层的是CallServerIntercepter负责真正的网络请求处理。
拦截器
OkHttp用责任链设计模式来将Request层层转交给下面的拦截器进行处理,每一层都可以拿到上一层的request,而且修改返回的Response,转交的顺序是:
- 用户设置的client.interceptors
- retryAndFollowIntercepter //重试和重定向处理拦截器
- BridgeIntercepter //将应用代码转成网络代码,包括cookie的设置、user-agent等
- CacheIntercepter //处理缓存
- ConnectIntercepter //创建web连接
- 非WebSocket下:用户设置的networkIntercepters
- CallServerIntercepter //往TCP流中写request,读response
献上一张神图来解释这条链是怎么运作的:
HttpStream和StreamAllocation
HttpStream
HttpStream主要定义了写请求头、写请求体和读响应头、读响应体的接口方法。
public interface HttpStream {
int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
Sink createRequestBody(Request request, long contentLength);
void writeRequestHeaders(Request request) throws IOException;
void finishRequest() throws IOException;
Response.Builder readResponseHeaders() throws IOException;
ResponseBody openResponseBody(Response response) throws IOException;
void cancel();
}
HttpStream的实现类有:Http1xStream、Http2xStream,分别对应HTTP/1.1和HTTP/2。
HttpStream的实现类使用source来写入请求体,使用sink来读取响应体,这两个都是缓存流。
public final class Http1xStream implements HttpStream {
private final BufferedSource source;
private final BufferedSink sink;
}
StreamAllocation
定义了三个实体:
1. Connections 物理的socket连接
2. Streams 逻辑的Http请求/响应体,一个Connection可以有多个并发的Streams,但是有一个分配限制(allocation limit)
3. Calls streams的序列,通常是一个初始的request和后跟的request
Instances of this class act on behalf of the call, using one or more streams over one or more connections。
public final class StreamAllocation {
public final Address address;
private Route route;
private final ConnectionPool connectionPool; 连接池
// State guarded by connectionPool.
private final RouteSelector routeSelector;
private int refusedStreamCount;
private RealConnection connection; 当前连接
private boolean released; 连接池是否被释放了
private boolean canceled; 连接取消
private HttpStream stream;
}
BridgeInterceptor
主要是将处理请求的header和body的规范化(用用户代码到网络代码),包括Gzip编码和解码:
public final class BridgeInterceptor implements Interceptor {
private final CookieJar cookieJar;
@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"); //默认keep-Alive,即不断开TCP连接
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false; //如果没有主动设置Accept-Encoding,默认是gzip,同时我们还负责将response解压缩
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
//如果设置了CookieJar,添加Cookie
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()); //默认是"okhttp/3.11.0"
}
//交给下一拦截器处理,获取响应
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);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
}
默认的CookieJar,不进行任何保存:
public interface CookieJar {
/** A cookie jar that never accepts any cookies. */
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
}
@Override public List<Cookie> loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};
}
读取响应头,保存Cookie:
public final class HttpHeaders {
public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
if (cookieJar == CookieJar.NO_COOKIES) return;
List<Cookie> cookies = Cookie.parseAll(url, headers);
if (cookies.isEmpty()) return;
//调用CookieJar的回调方法保存
cookieJar.saveFromResponse(url, cookies);
}
}
Cookie.parseAll就是通过响应头的“Set-Cookie”值获取:
/** Returns all of the cookies from a set of HTTP response headers. */
public static List<Cookie> parseAll(HttpUrl url, Headers headers) {
List<String> cookieStrings = headers.values("Set-Cookie");
List<Cookie> cookies = null;
for (int i = 0, size = cookieStrings.size(); i < size; i++) {
Cookie cookie = Cookie.parse(url, cookieStrings.get(i));
if (cookie == null) continue;
if (cookies == null) cookies = new ArrayList<>();
cookies.add(cookie);
}
return cookies != null
? Collections.unmodifiableList(cookies)
: Collections.<Cookie>emptyList();
}
ConnectInterceptor
做TCP连接的拦截器:从连接池中获取Connection,如果没有就创建一个接:
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@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");
//查找RealConnection并返回一个String
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
CallServerInterceptor
真正进行网络请求的拦截器,把resquest发送到TCP流上,把Response从TCP读进来,主要流程如下:
public final class CallServerInterceptor implements Interceptor {
private final boolean forWebSocket;
public CallServerInterceptor(boolean forWebSocket) {
this.forWebSocket = forWebSocket;
}
@Override public Response intercept(Chain chain) throws IOException {
...
//1.写请求头
httpStream.writeRequestHeaders(request);
//这里判断请求方法,GET方法不能发送请求体
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
//2.写请求体
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
//提交(提交buffer的内容)
httpStream.finishRequest();
//3.读响应头
Response response = httpStream.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake()) //TSL握手
.sentRequestAtMillis(sentRequestMillis) //发送时间
.receivedResponseAtMillis(System.currentTimeMillis()) //接收时间
.build();
//4.如果当前不是websocket的话,读响应体
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpStream.openResponseBody(response))
.build();
}
//遇到响应体有Connection:close时释放连接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
//响应码为204、205的没有响应体,而且不进行页面跳转
int code = response.code();
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连接池
public final class ConnectionPool {
//默认闲置连接数:5,连接keep alive:5分钟
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
if (keepAliveDuration <= 0) {
throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
}
}
}
OkHTTP的缓存实现
OkHttp在责任链中添加了一个CacheIntercepter,主要用来从缓存中读取和保存response。
CacheInterceptor
缓存拦截器,在Brige层下面:
public final class CacheInterceptor implements Interceptor {
final InternalCache cache;
public CacheInterceptor(InternalCache cache) {
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
...
}
}
可以看到缓存是通过InternalCache来完成的。从OkHttp的构建来看,默认的Cache和InternalCache是null。我们再往下看:
long now = System.currentTimeMillis();
//这里涉及到缓存策略的问题,提供一个请求和一个已存在的缓存,让缓存策略判断是否使用这个缓存。
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
...
// 如果我们不允许使用网络(比如设置了CacheControl.FORCE_CACHE),而且缓存也不可用,则返回一个504错误
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();
}
···
//下面的就是不使用缓存,进行网络请求
networkResponse = chain.proceed(networkRequest);
//下面处理的一个情况是服务器返回304 NOT MODIFIED,表示服务器这边的数据没有发生变化,可以使用缓存。
// If we have a cache response too, then we're doing a conditional get.
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); //保存更新后的缓存
//可以返回response了
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//下面就是没有用到缓存的情况,获得了正常的response
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);
}
//如果本次请求是POST、PATCH、PUT、DELETE、MOVE方法的话,则删除缓存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}