Okhttp特性
Okhttp是一个高效的,请求速度更快,更节省流量的http库。拥有以下特性。
- 支持SPDY和http2,对同一服务器的所有请求共享同一个socket。
- 拥有自动维护的socket连接池,减少握手次数。
- socket自动选择最好路线,并支持自动重连。
- 拥有队列线程池,轻松写并发。
- 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)。
- 无缝的支持GZIP来减少数据流量。
- 支持基于Headers的缓存策略,缓存响应数据来减少重复的网络请求。
- 支持服务器多IP重连,支持从常用的连接问题中自动恢复,还处理了代理服务器问题和SSL握手失败问题。
注:SPDY是什么?
SPDY(读作“SPeeDY”)是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。
SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。
谷歌表示,引入SPDY协议后,在实验室测试中页面加载速度比原先快64%。
OkHttpClient分析版本
Okhttp3(3.2.0版本)
OkHttpClient的简单调用
Get请求
public void doGet(String url){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
Log.i("输出:" + response.body().string());
}
Post请求
public void doPost(String url){
MediaType json = MediaType.parse("application/json; charset=utf-8")
RequestBody body = RequestBody.create(JSON, json);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).post(body).build();
Response response = client.newCall(request).execute();
Log.i("输出:" + response.body().string());
}
OkHttpClient介绍
OkHttpClient顾名思义是一个http请求的客户端,实现了Call.Factory接口,提供newCall方法用于创建一个请求调用Call。
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
OkHttpClient包含很多模块,包括Dispatcher(请求分发器),ProxySelector(代理服务器选择),InternalCache(内部缓存)等其他的模块,由它对外提供这些模块的访问,是典型的外观模式,同时OkHttpClient设计为建造者模式,提供Builder方便为不同模块进行配置。
Request,Response,Call,RealCall,AsynCall介绍
- Request作为请求信息的封装,内部包含了请求url,请求方法method,请求头headers,请求体RequestBody,tag标签等。
- Response作为相应信息的封装,内部包含对应的请求信息request,http协议protocol,响应码code,响应头headers,响应消息message,响应体ResponseBody等。
- Call作为一个请求的接口,提供获取对应请求信息息request,执行请求execute,异步请求入队enqueue,取消请求cancel等方法的定义。
- RealCall是Call请求的具体实现,实现了同步请求执行,异步请求入队,请求取消等操作。同时内部提供ApplicationInterceptorChain负责请求和响应的拦截处理。
- AsynCall是一个Runnable异步请求任务,分发器Dispatcher负责管理这些异步请求,在可请求数量满足的情况下会交给线程池执行它的execute方法。
Dispatcher请求调用分发器
Dispatcher是请求Call的分发器,用于管理请求的等待,执行,取消。分为同步请求RealCall和异步请求AsyncCall的管理。
- 针对同步请求,用runningSyncCalls队列记录正在执行的同步请求。
- 针对异步请求,用readyAsyncCalls(待执行的异步请求队列)和runningAsyncCalls(正在执行的异步请求队列)记录异步请求。
- 内部提供线程池,负责执行异步请求,查看executorService()。
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
这个线程池,我们看参数解析:
1. 参数0,代表核心线程的数量,即线程池中最少线程时的数量(当所有线程都空闲时),为0的话,说明线程空闲时,不保留任何线程,做到线程低占用。
2. 参数Integer.MAX_VALUE,代表最大线程数,即线程池中最多线程时的数量,为Integer.MAX_VALUE的话,说明可以开启无限多的线程进行工作,线程工作完了再关闭。
3. 参数60,和参数TimeUnit.SECONDS,表示当线程池的数量比核心线程的数量大时,等待60秒之后就会去关闭空闲线程,使总的线程数量不会大于核心线程数量。
4. 参数new SynchronousQueue<Runnable>(), 代表这个线程等待队列是同步队列,当有一个线程进来时,就同时会有一个线程出去,也就是说线程不会停留在其中,一进入就立马出去执行,这种方式在高频请求时是很合适的。
5. 参数Util.threadFactory("OkHttp Dispatcher", false),表示提供一个线程工厂,用于创建新的线程。
这个线程池的设计,是需要时可以创建无限多的线程,不需要时不保留任何线程,空闲线程60秒后如果还是空闲就会回收,以保证高阻塞低占用的使用。
- 设置有maxRequests(最大异步请求的数量)和maxRequestsPerHost(单个服务器的最大异步请求数量),这是为了限制同时执行的总的请求数量和针对同一个服务器访问的请求数量,如果有超过了这两个的限制,就将异步请求AsyncCall添加到readyAsyncCalls(待执行的异步请求队列)去等待执行。这两个参数可以配置。
- enqueue方法,AsyncCall入队操作
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//未超过限制,执行加入到线程池执行异步请求
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
//超过限制,将行异步请求加入等待队列,等待执行
readyAsyncCalls.add(call);
}
}
- finished方法,标记当前AsyncCall已经完成,这时会考虑从异步等待队列取出请求去执行。
//标记请求完成
synchronized void finished(AsyncCall call) {
if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
promoteCalls();
}
//从异步等待队列取出请求去执行,同时记录到正在执行的异步队列中
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> 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.
}
}
- cancelAll方法,取消所有请求,包括所有同步请求,所有正在执行的异步请求,所有等待执行的异步请求。
public synchronized void cancelAll() {
for (AsyncCall call : readyAsyncCalls) {
call.cancel();
}
for (AsyncCall call : runningAsyncCalls) {
call.cancel();
}
for (RealCall call : runningSyncCalls) {
call.cancel();
}
}
RealCall 真正执行请求调用的地方
RealCall是真正执行请求调用的入口,无论是同步请求的execute,还是异步请求的enqueue(由Dispatcher进行分发),最终都会到RealCall的getResponseWithInterceptorChain。
getResponseWithInterceptorChain里创建了一个ApplicationInterceptorChain拦截链来处理当前RealCall中的Request请求数据。接下来讲讲Okhttp中拦截器是这样的一种运行模式。
Interceptors拦截器原理
Interceptors拦截器采用了责任链模式一层一层的处理请求响应信息。这里我们从同步请求去分析添加了HttpLoggingInterceptor日志打印拦截器的运行原理。
final class RealCall implements Call {
//执行请求,这里创建了ApplicationInterceptorChain拦截链,负责对所有拦截器进行调用,调用proceed开始处理拦截
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
class ApplicationInterceptorChain implements Interceptor.Chain {
private final int index;
private final Request request;
private final boolean forWebSocket;
ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
this.index = index;
this.request = request;
this.forWebSocket = forWebSocket;
}
@Override public Connection connection() {
return null;
}
@Override public Request request() {
return request;
}
//这里遍历拦截器,通过index在拦截链中找到对应的拦截器,然后调用intercept进行拦截处理,返回加工后的Response响应信息
@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;
}
//当所有拦截器都遍历处理后,开始执行真正请求,返回Response,这里才是真正产生Response响应的地方。
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
}
}
看了以上代码之后,我们知道是通过index一个一个遍历拦截链的拦截器,去拦截处理,这是一个递归的过程,当所有的拦截器处理了Request请求信息之后(也就是真正请求之前的预处理,比如打日志,修改请求头什么的),才真正的交给网络请求引擎去执行请求,返回响应信息,然后又按相反顺序对Response响应信息进行拦截处理(也就是对响应信息的再加工,比如打印响应信息等)。那么我们分析HttpLoggingInterceptor日志打印拦截器会更加的的清晰这个过程。
public final class HttpLoggingInterceptor implements Interceptor {
//拦截链上的
@Override public Response intercept(Chain chain) throws IOException {
Level level = this.level;
//获取请求信息
Request request = chain.request();
//如果不打印日志,那就直接调用chain.proceed执行下一个拦截操作,不做其他操作。
if (level == Level.NONE) {
return chain.proceed(request);
}
//这部分是打印请求信息,对请求进行预处理(这里可以对请求信息进行修改或做其他操作)
---------------------------------------
boolean logBody = level == Level.BODY;
boolean logHeaders = logBody || level == Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
}
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
logger.log(name + ": " + headers.value(i));
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyEncoded(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
logger.log("");
logger.log(buffer.readString(charset));
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
}
}
//请求预处理完成
----------------------------------
//这里调用chain.proceed执行下一个拦截操作,返回Response响应信息,结合ApplicationInterceptorChain的proceed方法,很容易看出是一个责任链的递归调用模式
long startNs = System.nanoTime();
Response response = chain.proceed(request);
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
//接下来打印响应信息,对Response响应进行再加工(这里可以对响应信息进行修改或做其他操作)
---------------------------------------
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- " + response.code() + ' ' + response.message() + ' '
+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
+ bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
logger.log(headers.name(i) + ": " + headers.value(i));
}
if (!logBody || !HttpEngine.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyEncoded(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
logger.log("");
logger.log("Couldn't decode the response body; charset is likely malformed.");
logger.log("<-- END HTTP");
return response;
}
}
if (contentLength != 0) {
logger.log("");
logger.log(buffer.clone().readString(charset));
}
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
//响应再加工完成
----------------------------------
//这里返回响应信息
return response;
}
}
明白拦截器的运行模式之后,我们知道真正的请求是在RealCall的getResponse方法中开始的。
/**
* Performs the request and returns the response. May return null if this call was canceled.
*/
Response getResponse(Request request, boolean forWebSocket) throws IOException {
//这里负责执行请求,然后返回响应数据
...
}
具体请求我们下节分析。