OkHttp完全解析之整体调用流程
前言:阅读好的代码如同观赏美景一样的美妙
OkHttp是一个Square公司在github开源的Java网络请求框架,非常流行。OkHttp 的代码并不是特别庞大,代码很多巧妙的实现,非常值得学习。
建议下载一个OkHttp的源码,使用 Idea 或者 Source Insight 阅读 ,跟随本文一起阅读。
OKHttp的全局概览:
分析一个复杂的框架,可以先
分析简单的调用流程
然后分析全局的类之间的关系
OkHttp 的类图
来自:http://frodoking.github.io/2015/03/12/android-okhttp/
如何编写一个复杂的框架,OkHttp给我们一个好的示范,这符合设计模式中的门面模式,框架的核心是 OkHttpClient 。
使用者的所有操作都是通过操作OkHttpClient 实现,OkHttpClient 下面有很多子模块,OKHttpClient 知道所有子模块的实现,OKHttpClient调用各个子模块完成功能,各个子模块是独立的,
它们不知道OKHttpClient的存在。
OkHttp流程图
(from piasy 大神)
<img src="bc55d9e1-5392-4f7f-8aab-78f0b1ffda5f_files/6869cc51-abb8-4c46-9784-a10ea0239d07.png" border="0" alt="" name="" width="548" height="876" class="">
:
虽然 OKHttpClient 很复杂,但是逻辑还是很简洁的。
可以划分了三个阶段
- 1.OKHttpClient +Request 构造 RealCall
- 2.RealCall 直接同步执行或者进入 异步队列,统一由Dispatcher分发
- 3.通过Interceptor的责任链,层层调用,最后获取 Response
OKHttp最简单的用法是:
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
这里,我们所使用的是最简单的功能,使用的类只有三个OKHttpClient,Request ,Response。
可以说,OKHttp从设计的时候就考虑了易用性
一个网络请求框架,最简单的只需要
有OkHttpClient,Request,Response就能发起一次请求和响应了
下面我们就整个发起请求,接收Response的过程进行分析:
1.0 创建 OkHttpClient
这是发起所有调用Call的工厂,包括发送HTTP Request ,和接收 Response
由于OkHttpClient 内部实现的连接池,线程池,等,所以
每个应用一般只需要一个Single 单例的OKHttpClient即可,复用这个单例进行Http请求,不需要担心单例的扩展性问题,非常的简单易用并且扩展性良好(因为OKHttp 灵活使用的建造器模式 :OkHttpClent.Builder):
例如:
如果你只需要默认配置的OKHttpClient:
那么,
你只需要:
OkHttpClient client=new OkHttpClient();
为啥会没有Builder呢?因为构造函数OkHttpClient() 已经默认使用默认的new Buidler();
其实
public OkHttpClient() {
this(new Builder());
}
private OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
//省略代码
}
如果不使用默认配置,那么我们无法使用 OkHttpClient(Builder builder)
这个私有的方法new 一个OKHttpClient实例的
可以这样:
OkHttpClient.Builder bulider=new OKHttpClient.Builder();
OkHttpClient client=buider.xxx().xxx().build();
那么单例的OKHttpClient会不会影响OkHttpClient的扩展性呢?
不会,我们之所以要单例一个OKHttpClient,是应该不重复创建连接池和线程池
也就是说,可以全局定义一个OkHttpClient ,在需要特殊配置一个OKHttpClient的时候,这个局部的OKHttpClient
引用的连接池和线程池是复用自全局单例的就行;
如:
OkHttpClient eagerClient = client.newBuilder()
.readTimeout(500, TimeUnit.MILLISECONDS)
.build();
Response response = eagerClient.newCall(request).execute();
具体实现是:通过全局的OkHttpClient 单例new属性一样的的Builder,然后给这个Builder单独配置局部的属性
后,再通过这个Builder创建一个局部的OKHttpClient即可
public Builder newBuilder() {
return new Builder(this);
}
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
//省略代码
}
1.1 创建Request
Request request = new Request.Builder()
.url(url)
.build();
同样使用了Builder模式
注意:Request包含的信息只有 url ,body ,header,tag,method等基础的信息
1.2通过OKHttpClient通过new Call(Request request)创建一个RealCall
OkHttpClient:
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
1.3创建 RealCall
OkHttp的网络请求分为同步和异步两种情况:
*同步:
直接调用
RealCall 的 execute的方法,经历一系列的拦截器 Intercepter,返回Response
*异步:
通过RealCall的enqueue方法,创建一个AsyncCall,把这个AsyncCall,通过Client的Dispatcher
分发下去
1.4 同步执行请求和异步执行在Dispatcher的分发过程:
1 同步请求
- RealCall.java
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);//通过Dispatcher记录这个在
//最重要是这一句,通过拦截器的调用链获取Response,同步执行
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);//结束后,Dispatcher负责清理这个记录
}
}
2 异步请求:
利用构造 responseCallback 构造一个RealCall的内部类 AsyncCall的对象,
然后把它插入 Dispatcher的队列中
- RealCall.java
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
- AsyncCall.java
因为AsyncCall是RealCall的内部类 ,所以它的对象,对应的外部类RealCall的实例,并且它实现了Runnable接口,所以可以在线程池执行
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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
- Dispatcher.java
//注意这是一个同步方法
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//为了避免太多请求同时执行,这里有两个限制,
//同时请求的最大数目不能超过 maxRequests ,同时对于每一个主机host(服务器),同时请求数目不能超过maxRequestsPerHost
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
//否则就加入一个准备
readyAsyncCalls.add(call);
}
}
//同步方法 获取一个单实例的 线程池
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;
}
//在一个Call执行结束后
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!");
//需要取出新的Call执行,调用 promoteCalls() 自动执行 N个Call直到达到数目限制
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
//主动取出就绪的Call 执行
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.
}
}
1.5 Interceptor链式调用拦截的原理和流程
在OkHttp中,比如我们有各种需求,需要透明压缩Gzip,我们的请求的Request只有我们的设定的内容,需要添加
Http协议的的Header的固定的头信息,还要允许用户可以编写自己的逻辑插入调用的过程中,这里就需要了解OkHttp的链式调用拦截的逻辑
首先,假设整个请求响应是一个黑盒子
我们
输入的是 application request
输出 application 的 response
中间经历了一些了的Interceptors,有OkHttp自己的定义的,也可以有自定义的:
如:
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);
}
由源代码可以看到,创建了一个 List
保存所有的拦截器
然后利用这个拦截器列表构建了一个 index=0(标识当前的Interceptor) 调用链 Interceptor.Chain chain = new RealInterceptorChain(...,0,...),
调用chain.proceed(originalRequest),传入最原始的的request ,返回对应的response
下面是chain.proceed的源码:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection 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 && !sameConnection(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;
其中:最重要的代码如下:
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
首先创建一个next的Chain
然后根据 index获取当前的拦截器
调用Interceptor.intercept(next),传入下一个 nextChain,并且执行这个拦截逻辑
注意有两种情况,intercept
拦截器可以决定直接返回reponse结束所有的链式调用过程,还是继续调用nextChain.proceed执行下一个拦截调用的过程,返回response
最重要的Interceptor :CallServerInterceptor
这个拦截器是最后一个拦截器,它负责发送请求到服务器
/** This is the last interceptor in the chain. It makes a network call to the server. */
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 {
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();
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return
// what we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
// from being reused. Otherwise we're still obligated to transmit the request body to
// leave the connection in a consistent state.
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
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;
}
static final class CountingSink extends ForwardingSink {
long successfulCount;
CountingSink(Sink delegate) {
super(delegate);
}
@Override public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
successfulCount += byteCount;
}
}
}
总结
OKHttp整体的调用流程还是很清晰的,可以看到作者的功力。我们在分析的时候重点关注一个请求从发起到响应结束的过程就能更容易理解。
OkHttp完全解析之整体调用流程的更多相关文章
- .net core 源码解析-mvc route的注册,激活,调用流程(三)
.net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活c ...
- Android OkHttp完全解析 --zz
参考文章 https://github.com/square/okhttp http://square.github.io/okhttp/ 泡网OkHttp使用教程 Android OkHttp完全解 ...
- Android OkHttp完全解析 是时候来了解OkHttp了
Android OkHttp完全解析 是时候来了解OkHttp了 标签: AndroidOkHttp 2015-08-24 15:36 316254人阅读 评论(306) 收藏 举报 分类: [an ...
- Android进阶:七、Retrofit2.0原理解析之最简流程【下】
紧接上文Android进阶:七.Retrofit2.0原理解析之最简流程[上] 一.请求参数整理 我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢? 这 ...
- mvc route的注册,激活,调用流程
mvc route的注册,激活,调用流程(三) net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所 ...
- Samsung_tiny4412(驱动笔记03)----字符设备驱动基本操作及调用流程
/*********************************************************************************** * * 字符设备驱动基本操作及 ...
- Flask源码解析:Flask应用执行流程及原理
WSGI WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述服务器端如何与web应用程序通信的 ...
- RxJava && Agera 从源码简要分析基本调用流程(2)
版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...
- RxJava && Agera 从源码简要分析基本调用流程(1)
版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/123 来源:腾云阁 https://www.qclo ...
随机推荐
- 洛谷P4451 [国家集训队]整数的lqp拆分(生成函数)
题面 传送门 题解 我对生成函数一无所知 我们设\(F(x)\)为斐波那契数列的生成函数,\(G(x)\)为答案的生成函数,那么容易得到递推关系 \[g_n=\sum_{i=0}^{n-1}f_ig_ ...
- ListView ,GridView 通用适配器
前言 接近半年的时间没有写博客了,今年公司的项目有点多,比较忙,没时间写,这是其一.其次是,这半年来,有时间的时候,我都会看看自己以前写的博客,也许是以前刚刚写博客,经验不足,感觉写出来的博客质量很不 ...
- easyui页面上显示和PL/SQL编码问题
在页面上,只需要显示人们看的懂的文字就行,但是在数据库里面就不一定了,一般情况下,在数据库里面存字母,数字等除了汉字以外的字符,存汉字有个问题,就是有时候不同oracle数据库的客户端会出现乱码问题: ...
- docker搭建elk+cerebro环境
ELK的帮助手册 Docker Hub官网:https://hub.docker.com/r/sebp/elk/ Docker ELK使用文档:http://elk-docker.readthedoc ...
- 使用canvas实现画中画效果的H5
最近看到一个挺有趣的H5,主要效果就是通过不断的放缩来展示画中画,网上找了一下并没有这方面的实现代码,故决定原创一下,并分享出来 主要的思路就是通过canvas不断的写入图片,考虑到每一层的图片的位置 ...
- tomcat正常启动后http://localhost:8080/报错404
病症: tomcat在eclipse里面能正常启动,而在浏览器中访问http://localhost:8080/不能访问,且报404错误.同时其他项目页面也不能访问.关闭eclipse里面的tomca ...
- [摸鱼]cdq分治 && 学习笔记
待我玩会游戏整理下思绪(分明是想摸鱼 cdq分治是一种用于降维和处理对不同子区间有贡献的离线分治算法 对于常见的操作查询题目而言,时间总是有序的,而cdq分治则是耗费\(O(logq)\)的代价使动态 ...
- PHP的curl实现get,post 和 cookie (转)
类似于dreamhost这类主机服务商,是显示fopen的使用 的.使用php的curl可以实现支持FTP.FTPS.HTTP HTPPS SCP SFTP TFTP TELNET DICT FILE ...
- 送给张思漫,李志媛和王颖的C语言经典例题
1.打印乘法口诀表 #include<stdio.h> int main() { int i, j; ; i <= ; i++){ ; j <= i; j++) { print ...
- VUE安装步骤1
文件结构 用官方的 vue-cli 生成的项目文件结构如上图所示,简单介绍下上面图中各文件的作用. src 文件夹 : 1. assets 文件夹:存放各种资源文件,如图片等 2. component ...