OkHttp相信搞android的都不陌生,它是目前应用最多的网络请求开源框架,虽然现在Retrofit更加流行,但到底层其实也是基于OkHttp的。你要是说你没用过OkHttp都不好意思说自己是做过Android开发。那么今天就来聊聊OkHttp。
本文的要点如下:
- 概述
- OkHttp的使用
- 源码分析
- 同步请求
- 异步请求
- 总结
概述
OkHttp是一个网络请求开源库,即将网络请求的相关功能封装好的类库。要知道,没有网络请求框架之前,App想与服务器进行网络请求交互是一件很痛苦的事,因为Android的主线程不能进行耗时操作,那么就需另开1个线程请求、考虑到线程池,缓存等一堆问题。
于是乎,网络请求库出现了,网络请求库的本质其实是封装了 网络请求 + 异步 + 数据处理功能的库。
OkHttp的使用
同步请求:
GET请求:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
POST请求:
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("username","abc")
.add("password","123456")
.build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
异步请求(以GET为例):
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
使用这一块没什么好讲的,不同类型的请求方式有些许的不同,不过都比较好理解。下面我们还是来看看源码中究竟是怎么做的吧。
源码分析
ps:我用的源码是OkHttp3.14.1。
首先,使用OkHttp时,必然要先创建OkHttpClient对象。
OkHttpClient client = new OkHttpClient();
一行代码就搞定了,似乎有点过于简单了,我们来看看OkHttpClient()里面做了什么:
public OkHttpClient() {
this(new Builder());
}
原来是方便我们使用,提供了一个默认的配置,直接传入了一个默认的Builder类型的对象。Builder在初始化时就会设置这一些参数,全都是默认值。这里就是运用了Builder模式,简化了构建过程。
public Builder() {
dispatcher = new Dispatcher();//调度器
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
if (proxySelector == null) {
proxySelector = new NullProxySelector();
}
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
callTimeout = 0;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
同步请求
尽管现在项目中很少用同步请求了,但是其实异步请求的基础还是同步请求,只不过中间转换了线程而已。
在同步请求,初始化之后,我们又用Builder构建了Request对象,然后执行了OKHttpClient的newCall方法,那么咱们就看看这个newCall里面都做什么操作?
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
可以看出client.newCall(request).execute();实际上执行的是RealCall的execute方法,现在咱们再回来看下RealCall的execute的具体实现。
@Override
public Response execute() throws IOException {
synchronized (this) {
//同步判断,保证每个call只用一次,重复使用会抛出异常
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
client.dispatcher().executed(this);
return getResponseWithInterceptorChain();
} finally {
client.dispatcher().finished(this);
}
}
这里主要做了 4 件事:
- 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用call#clone方法进行克隆。
- 利用client.dispatcher().executed(this)来进行实际执行,dispatcher是刚才看到的OkHttpClient.Builder的成员之一。
- 调用getResponseWithInterceptorChain()函数获取 HTTP 返回结果,从函数名也可以看出,这一步还会进行一系列“拦截”操作。
- 最后还要通知dispatcher自己已经执行完毕,释放dispatcher。
那么我们看下dispatcher里面的execute()是如何处理的。
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
可以看到,其实也很简单,runningSyncCalls执行了add方法,添加的参数是RealCall。runningSyncCalls是什么呢?
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
可以看到runningSyncCalls是双向队列。另外,我们发现Dispatcher里面定义了三个双向队列,看下注释,我们大概能明白readyAsyncCalls是一个存放了等待执行任务Call的双向队列,runningAsyncCalls是一个存放异步请求任务Call的双向任务队列,runningSyncCalls是一个存放同步请求的双向队列。
那么这一步的目的就是将RealCall放入同步队列中。
回到之前,执行完client.dispatcher().executed()方法,要执行getResponseWithInterceptorChain()方法,这就是OkHttp的核心拦截器的工作了:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//添加开发者应用层自定义的Interceptor
interceptors.addAll(client.interceptors());
//这个Interceptor是处理请求失败的重试,重定向
interceptors.add(new RetryAndFollowUpInterceptor(client));
//这个Interceptor工作是添加一些请求的头部或其他信息
//并对返回的Response做一些友好的处理(有一些信息你可能并不需要)
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
interceptors.add(new CacheInterceptor(client.internalCache()));
//这个Interceptor的职责是建立客户端和服务器的连接
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//添加开发者自定义的网络层拦截器
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
//把chain传递到第一个Interceptor手中
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
可以看到,主要的操作就是new了一个ArrayList,然后就是不断的add拦截器Interceptor,之后new了一个RealInterceptorChain对象,最后调用了chain.proceed()方法。
我们来看看RealInterceptorChain()构造方法。
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, Connection connection, int index, Request request) {
this.interceptors = interceptors;//将拦截链保存
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
}
就是一些赋值操作,将信息保存,关键的是this.interceptors = interceptors这里就保存了拦截链。
之后我们来看一下chain.proceed()方法获取返回的信息。由于Interceptor是个接口,所以应该是具体实现类RealInterceptorChain的proceed实现。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection connection) throws IOException {
//省略其他代码
calls++;
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
//省略其他代码
return response;
}
然后看到在proceed方面里面又new了一个RealInterceptorChain类的next对象,这个next对象和chain最大的区别就是index属性值不同chain是0,而next是1,然后取interceptors下标为1的对象的interceptor。由从上文可知,如果没有开发者自定义的应用层Interceptor时,首先调用的RetryAndFollowUpInterceptor,如果有开发者自己定义的应用层interceptor则调用开发者interceptor。
后面的流程都差不多,在每一个interceptor的intercept方法里面都会调用chain.proceed()从而调用下一个interceptor的intercept(next)方法,这样就可以实现遍历getResponseWithInterceptorChain里面interceptors的item,实现遍历循环。
之前我们看过getResponseWithInterceptorChain里面interceptors的最后一个item是CallServerInterceptor,最后一个Interceptor(即CallServerInterceptor)里面是直接返回了response 而不是进行继续递归。
CallServerInterceptor返回response后返回给上一个interceptor,一般是开发者自己定义的networkInterceptor,然后开发者自己的networkInterceptor把他的response返回给前一个interceptor,依次以此类推返回给第一个interceptor,这时候又回到了realCall里面的execute()里面了。
最后把response返回给get请求的返回值。至此同步GET请求的大体流程就已经结束了。
异步请求
讲了这么多同步请求,其实异步请求才是重头戏,毕竟现在的项目中大多用的都是异步请求。
由于前面的步骤和同步一样new了一个OKHttp和Request。这块和同步一样就不说了,那么说说和同步不一样的地方,后面异步进入的是newCall()的enqueue()方法。
之前分析过,newCall()里面是生成了一个RealCall对象,那么执行的其实是RealCall的enqueue()方法。我们来看源码:
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
//同步判断,保证每个call只用一次,重复使用会抛出异常
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.callStart();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到和同步请求的enqueue方法一样,还是先同步判断是否被请求过了,不一样的地方就在于调用了client.dispatcher().enqueue(new AsyncCall(responseCallback))方法。即实际调用的是Dispatcher的enqueue()方法:
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);//将call添加到了异步请求队列
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.get().forWebSocket) {
AsyncCall existingCall = findExistingCallWithHost(call.host());
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
promoteAndExecute();
}
这里主要做了两件事:
- 将call添加到了异步请求队列;
- 调用promoteAndExecute方法。
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);//将异步请求添加到executableCalls中
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
可以看出先是迭代了上面的队列,取出队列里的AsyncCall后添加到了executableCalls集合中。然后遍历这个集合,开始执行每个AsyncCall的executeOn方法。参数是executorService(),我们来具体看看:
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
不难看出,这个方法传递进去的是ExecutorService线程池。那看来关键就在executeOn方法中了:
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);//用executorService线程池执行了当前的线程
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
transmitter.noMoreExchanges(ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
通过executorService线程池执行了当前的线程,也就是AsyncCall,那么AsyncCall由于继承了NamedRunnable,这个NamedRunnable的run方法里又执行了抽象方法execute,所以,实际上这里执行了AsyncCall的execute方法。
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
Response response = getResponseWithInterceptorChain();
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);
}
}
终于,我们看到了几个熟悉的名字,getResponseWithInterceptorChain、onResponse和onFailure。不难看出,这里依旧是用getResponseWithInterceptorChain()通过拦截链进行请求,最终执行结果回调给了我们传递进去的Callback。至此,异步请求的主要流程也分析完了。
对比一下同步请求和异步请求,不难看出,其实异步请求就是维护了一个线程池用于进行请求,在请求完成之后回调我们一开始传入的CallBack接口。
总结
- OkHttpClient实现了Call.Factory,负责为Request创建Call;
- RealCall为具体的Call实现,execute()为同步接口,通过getResponseWithInterceptorChain()函数实现;enqueue()为异步接口通过Dispatcher利用ExecutorService线程池实现,而最终进行网络请求时和同步接口一致,都是通过getResponseWithInterceptorChain()函数实现;
- getResponseWithInterceptorChain()中利用拦截链机制,分层实现缓存、透明压缩、网络 IO 等功能。
网友评论