OKHttp
- 系列
- OKHTTP
- OKHTTP请求流程
- 核心类
- 使用方法切入点
- 同步和异步请求代码
- 同步请求
- 异步请求
- 同步和异步请求源码分析
- OKHttpClient实例化
- Request实例化
- Call实例化
- 执行同步请求execute
- RealInterceptorChain
- RealInterceptorChain.proceed
- dispatcher.finished
- 执行异步请求enqueue
- NamedRunnable
- dispatcher.enqueue
- AsyncCall
- dispatche.promoteCalls
OKHTTP
写博客快一年了,虽然中间停了一段时间,但是还算是写了不少,有Android系统源码分析的,有Android功能开发相关的,有开发工具相关的,但是从头看下来总是感觉少了点什么,少了啥呢?
没错,就是少了开源框架的这部分!所以接下来的多数博客基本是围绕着一些应用比较广泛的框架进行展开,描述它们的使用方法,源码解读等,比如OKHttp,Retrofit,Glide,Picasso,ButterKnife,EventBus,RXJava等
OKHTTP对于Android开发者的重要性可以说是不言而喻了,这里我采用的阅读源码方法是根据使用方法顺藤摸瓜查看源码和阅读几个重点类的相结合的方法去理解OKHTTP,我认为结合使用情况去阅读更能有代入感,更能加深理解,同时选择几个关键类辅助更能帮我们理解整体框架
所以这里我将以OKHTTP的同步请求方法和异步请求方法作为切入点,同时配有几个关键类去阅读它的源码
OKHTTP请求流程
在阅读框架源码时一定要对整体流程有个把握,如图:
核心类
- OkHttpClient:整个框架的客户端类,OkHttpClient强烈建议全局单例使用,因为每一个OkHttpClient都有自己单独的连接池和线程池,复用连接池和线程池能够减少延迟、节省内存
- ConnectionPool:管理HTTP和HTTP / 2连接的重用,以减少网络延迟。 相同Address的HTTP请求可以共享Connection。 此类实现了哪些连接保持打开以供将来使用的策略
- Request:封装请求报文信息的对象,比如请求url,请求方法,超时时间,还有各种请求头
- Call:一个Call表示一个为执行而准备的请求,一个接口
- RealCall:Call的实现类,它是连接Request和Response的桥梁
- Response:请求响应的封装对象
- Dispatcher:分发器类,维护同步请求和异步请求的状态,内部维护了一个线程池,用于执行网络请求的线程;同时还维护了三个请求队列,一个是正在进行异步请求的队列(包含了已取消但未完成的请求),一个是处于等待执行的异步请求队列,最后一个是正在进行同步请求的队列(包含了已取消但未完成的请求)
- Interceptor:拦截器类,可以观察修改我们的请求和响应,是一个接口,有五个实现类,它们形成一个拦截器链,依次对我们的请求响应做处理
- RetryAndFollowUpInterceptor:Interceptor实现类之一,是网络请求失败时进行重连及服务器返回当前请求需要进行重定向的拦截器
- BridgeInterceptor:桥接连接器,主要是进行请求前的一些操作,将我们的请求设置成服务器能识别的请求,比如设置一系列头部信息,比如设置请求内容长度,编码,gzip压缩,cookie等
- CacheInterceptor:缓存拦截器,这个就很明显了,作用是缓存请求和响应,比如同一个请求之前发生过,这次就不需要重新构建了,直接从缓存取;响应同理
- ConnectInterceptor:连接拦截器,为当前请求找到一个合适的连接,比如如果从连接处中可以找到能复用的连接,就不要再创建新的连接了
- CallServerInterceptor:连接服务器拦截器,负责向服务器发送真正的请求,接受服务器的响应
使用方法切入点
- 同步请求方法:Call.execute,在当前线程执行请求,会阻塞当前线程
- 异步请求方法:Call.enqueue,新开一个线程执行请求,不会阻塞当前线程
同步和异步请求代码
使用步骤基本分为如下几步:
- 第一步:实例化OKHttpClient对象
- 第二步:实例化网络请求对象Request
- 第三步:实例化一个准备执行的请求的对象
- 第四步:执行请求方法
同步请求
OkHttpClient httpClient = new OkHttpClient
.Builder()
.connectTimeout(3000, TimeUnit.SECONDS)
.readTimeout(3000,TimeUnit.SECONDS)
.build();
public String syncRequest(String url){
String result = null;
Request request = new Request
.Builder()
.url(url)
.build();
Call call = httpClient.newCall(request);
Response response = null;
try {
response = call.execute();
if(response.isSuccessful()){
result = response.body().string();
}
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
异步请求
OkHttpClient httpClient = new OkHttpClient
.Builder()
.connectTimeout(3000, TimeUnit.SECONDS)
.readTimeout(3000,TimeUnit.SECONDS)
.build();
public void asyncRequest(String url){
Request request = new Request
.Builder()
.url(url)
.build();
Call call = httpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败了,这里是在子线程回调,不能在这里直接更新UI
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//获取请求结果,这里是在子线程回调
String result = response.body().string();
}
});
}
同步和异步请求源码分析
从上面的代码可以看出来,不管是同步和异步,在真正执行请求前,也就是execute和enqueue方法调用前所做的操作都是一样的,接下来我们就从这相同部分开始看
OKHttpClient实例化
我们这里是通过它的静态内部类Builder构建OKHttpClient,也就是使用常见的构建者模式,代码如下
public Builder() {
//实例化分发器
dispatcher = new Dispatcher();
//设置支持的网络协议,默认支持HTTP/2和http/1.1
protocols = DEFAULT_PROTOCOLS;
//设置支持的连接,默认是使用SNI和ALPN等扩展的现代TLS连接和未加密、未认证的http连接
connectionSpecs = DEFAULT_CONNECTION_SPECS;
//Call状态监听器
eventListenerFactory = EventListener.factory(EventListener.NONE);
//使用默认的代理选择器
proxySelector = ProxySelector.getDefault();
//默认没有cookie
cookieJar = CookieJar.NO_COOKIES;
//创建socket工厂类
socketFactory = SocketFactory.getDefault();
//下面四个是安全相关的配置
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
//实例化连接池 使用连接池技术减少请求的延迟(如果SPDY是可用的话)
connectionPool = new ConnectionPool();
//域名解析系统
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
//为了保持长连接,我们必须间隔一段时间发送一个ping指令进行保活
pingInterval = 0;
}
这里有两个很重要的操作
- 一个就是实例化Dispatcher,由这个对象对OKHttp中的所有网络请求任务进行调度,我们发送的同步或异步请求都会由它进行管理;它里面维护了三个队列,上面有说到,后面在分析这个类的时候会详细介绍其操作
- 还有一个就是实例化了连接池ConnectionPool,我们常说OKHttp有多路复用机制和减少网络延迟功能就是由这个类去实现;我们将客户端与服务端之间的连接抽象为一个connection(接口),其实现类是RealConnection,为了管理所有connection,就产生了ConnectionPool这个类;当一些connection共享相同的地址,这时候就可以复用connection;同时还实现了某些connection保持连接状态,以备后续复用(不过是在一定时间限制内,不是永远保持打开的)
Request实例化
虽然Request构建如下方所示
Request request = new Request
.Builder()
.url(url)
.build();
但是整个代码实现流程是
- 先通过Request构建内部builder对象,在构建它的过程,默认指定请求方法为get,然后又构建了一个请求头Headers的Builder对象
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
- Headers类的Builder对象没有构造方法,只不过内部定义了一个List数组,用来存放头部信息
public static final class Builder {
final List<String> namesAndValues = new ArrayList<>(20);
}
- 然后通过Request的Builder对象设置url,再调用build方法,该方法实例化了Request对象并返回
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
- 在实例化Request的时候,Request的构造方法里又调用了内部Builder对象内部的Headers.Builder对象的build方法,这个方法实例化了Headers对象,Headers对象的构造方法将List数组转换成了String数组
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
public Headers build() {
return new Headers(this);
}
Headers(Builder builder) {
this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
}
Call实例化
Call在OKHttp中是一个接口,它抽象了用户对网络请求的一些操作,比如执行请求(enqueue方法和execute方法),取消请求(cancel方法)等,真正实现是RealCall这个类
它的实例化是由OkHttpClient对象的newCall方法去完成的
/**
* 准备在将来某个时间点执行Request
*/
@Override
public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
这里RealCall的构造方法第一个参数是OkHttpClient对象,第二个参数是Request对象,第三个参数是跟WebSocket请求有关,这个后续在讲到这点的时候再说;至于在RealCall里真正做了哪些事需要到构造方法看看
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
final EventListener.Factory eventListenerFactory = client.eventListenerFactory();
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
// TODO(jwilson): this is unsafe publication and not threadsafe.
this.eventListener = eventListenerFactory.create(this);
}
- 首先RealCall内部会持有OkHttpClient和Request这两个对象的引用
- 实例化了RetryAndFollowUpInterceptor对象,这是一个拦截器,上面说到,它是一个请求重试和重定向的拦截器,后续在讲到拦截器的时候具体分析它
- 最后持有一个Call状态监听器的引用
总结:不管是同步请求还是异步请求,在执行请求前都要做这三个操作,也就是实例化OKHttp客户端对象OkHttpClient,包含请求信息的Request对象,一个准备执行请求的对象Call;接下来就要分道扬镳了
执行同步请求execute
Call.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);
}
}
- 第一步:首先出现一个同步代码块,对当前对象加锁,通过一个标志位executed判断该对象的execute方法是否已经执行过,如果执行过就抛出异常;这也就是同一个Call只能执行一次的原因
- 第二步:这个是用来捕获okhttp的请求堆栈信息不是重点
- 第三步:调用Dispatcher的executed方法,将请求放入分发器,这是非常重要的一步
- 第四步:通过拦截器连获取返回结果Response
- 第五步:调用dispatcher的finished方法,回收同步请求
这里我们来看下第三步源码:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
可以看到这里的逻辑很简单,只是将请求添加到了runningSyncCalls中,它是一个队列,看看它的定义
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
如果大家看过AsyncTask的源码,一定对ArrayDeque这个数据结构熟悉,不熟悉的可以看看博主的从源码解析-Android数据结构之双端队列ArrayDeque;这个队列会保存正在进行的同步请求(包含了已取消但未完成的请求)
接下来我们就要想了,executed方法只是将请求存到了队列当中,那什么时候去执行这个请求呢?上面说到过拦截器和拦截器链的概念,那么我们就回到上面的第四步去看看它的真面目
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 (!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);
}
我们通过Call.execute()方法进行同步请求获取最终的返回结果就是通过这个方法实现的,看看内部逻辑:
- 实例化一个List,用来存放Interceptor对象
- addAll(client.interceptors())这一步用来添加用户自定义的拦截器
- 依次添加OKHttp内部的五个拦截器
- 创建了一个RealInterceptorChain对象,它构建了一个拦截器链,通过proceed方法将这些拦截器一一执行
具体逻辑来看它的内部实现
RealInterceptorChain
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
}
- interceptors 存放了众多拦截器
- 其中第二,第三,第四个参数都是null
- index默认传的是0,其实是interceptors数组的索引,在proceed方法获取数组中的拦截器用的
- request就是包含请求信息的对象
RealInterceptorChain.proceed
@Override
public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
这里的设计有点奇怪,构造方法中已经传了Request ,然后在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");
}
// Call the next interceptor in the chain.
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;
}
- 如果索引index大于interceptors的大小,就抛出异常;然后将calls加一
- 接下来的几个关于httpCodec的if判断暂时不用管
我们重点看中间的几行代码:
又实例化了一个RealInterceptorChain对象,然后根据index从interceptors列表中取出下一个拦截器,最后调用该拦截器的intercept方法,并将拦截器链对象传递进去,最终获取结果返回
这里我们要看到一个很重要的点就是在实例化新的RealInterceptorChain时候,传入的参数是index+1;这样当第一个拦截器执行自己的intercept方法,做相关的逻辑,如果能获取结果就返回,不再继续执行接下来的拦截器;如果需要通过下一个拦截器获取结果,那就通过传入的参数RealInterceptorChain调用proceed方法,这样在这个方法里根据递增的索引index就能不断的从interceptors列表中取出下一个拦截器,执行每个拦截器自己的逻辑获取结果,直到所有拦截器执行完毕;这就是OKHttp拦截器链的由来,也是它的精妙所在
至于每个拦截器具体逻辑,在后续文章给出
接下来看看第五步
dispatcher.finished
当队列中的请求执行完毕后该怎么处理呢?难道继续存放在队列中?我们来看看
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
当同步请求执行到这里说明这个请求已经完成了,需要进行收尾工作
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
第一个参数是正在进行同步请求的队列,第二个参数是Call对象,第三个参数是调整异步请求队列的标志
这里也有一个同步代码块,因为队列是线程不安全的实现;首先将这个Call对象从队列中移出,不能移出就抛出异常;这里promoteCalls是false,所以promoteCalls方法不执行;接下来调用runningCallsCount方法,计算目前正在执行的请求,看看具体实现
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
返回的值是正在进行的同步请求数和正在进行的异步请求数
然后给idleCallback赋值
最后判断runningCallsCount 是否为0,这说明整个dispatcher分发器内部没有维护正在进行的请求了,同时idleCallback不为null,那就执行它的run方法(每当dispatcher内部没有正在执行的请求时进行的回调)
可以看出来,在进行同步请求的时候dispatcher分发器做的事很简单,就是添加请求到队列,从队列移除请求,那异步请求的时候是如何呢?看下方
执行异步请求enqueue
Call.enqueue
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
- 方法接收一个Callback参数,它是用来进行请求结束回调的
- 同样有一个同步代码块,判断该请求是否已经执行过;可以看出来,同一个Call对象,不管是调用execute方法还是enqueue方法,都只能调用一次
- 获取堆栈信息
- 调用dispatcher的enqueue方法
这里跟同步请求不同,它构建了一个新的Call,即AsyncCall对象,是RealCall内部类,继承NamedRunnable
NamedRunnable
NamedRunnable实现了Runnable接口
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
内部逻辑比较简单,设置了线程名,然后调用execute方法,也就是说AsyncCall具体逻辑将在execute方法执行
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,且单个Host正在执行的请求数小于5;如果满足,将请求添加到runningAsyncCalls队列,同时使用内部线程池执行该请求
- 如果条件不满足就将请求添加到readyAsyncCalls队列
看看线程池的构造
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;
}
通过构造方法可以看出该线程池没有核心线程,且最大线程数是Integer.MAX_VALUE,基本上是无限大了,保活时间是60s,也就是说工作线程如果空闲时间超过60s,将被回收
有的童靴可能想了,没有设置最大线程数,那是不是对手机性能消耗是不是特别大;其实不会,因为OKHttp对正在进行的异步请求数有限制,最大是64个
既然要被执行,那就看看AsyncCall 的执行逻辑
AsyncCall
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override
protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
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!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
在execute方法中也有调用getResponseWithInterceptorChain这个方法,跟同步请求差不多,同时会将请求结果通过Callback返回回去,一定要注意,回调是在子线程执行的,不要直接在这里进行更新UI的操作
最后要注意client.dispatcher().finished(this)这一句,同步请求时也调用了它,我们去看看有什么不同
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
虽然参数不同,但是调用finished方法的三个参数跟同步请求是不同的
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
其余代码在同步请求时已经讲过,这里只将一句
promoteCalls为true,那么会调用promoteCalls方法,这是dispatche中很重要的一个方法
dispatche.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.
}
}
- 如果正在进行请求的异步队列大小大于等于64,或者正在等待执行的异步请求队列是空的,那就直接返回
- 对readyAsyncCalls队列进行迭代,如果同一个host上的请求数小于5个,那就将这个请求添加到runningAsyncCalls中并执行它,同时从readyAsyncCalls移除
- 如果添加后的runningAsyncCalls的大小大于等于64就返回
可以看出来这个方法主要是用来调整异步请求队列的
在进行异步请求的时候,dispatcher分发类通过内部的两个队列和一个线程池对请求进行管理,保存传递进来的请求,执行传递进来的请求,移除执行完的请求