前言:阅读好的代码如同观赏美景一样的美妙

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完全解析之整体调用流程的更多相关文章

  1. .net core 源码解析-mvc route的注册,激活,调用流程(三)

    .net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活c ...

  2. Android OkHttp完全解析 --zz

    参考文章 https://github.com/square/okhttp http://square.github.io/okhttp/ 泡网OkHttp使用教程 Android OkHttp完全解 ...

  3. Android OkHttp完全解析 是时候来了解OkHttp了

    Android OkHttp完全解析 是时候来了解OkHttp了 标签: AndroidOkHttp 2015-08-24 15:36 316254人阅读 评论(306) 收藏 举报  分类: [an ...

  4. Android进阶:七、Retrofit2.0原理解析之最简流程【下】

    紧接上文Android进阶:七.Retrofit2.0原理解析之最简流程[上] 一.请求参数整理 我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢? 这 ...

  5. mvc route的注册,激活,调用流程

    mvc route的注册,激活,调用流程(三) net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所 ...

  6. Samsung_tiny4412(驱动笔记03)----字符设备驱动基本操作及调用流程

    /*********************************************************************************** * * 字符设备驱动基本操作及 ...

  7. Flask源码解析:Flask应用执行流程及原理

    WSGI WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述服务器端如何与web应用程序通信的 ...

  8. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  9. RxJava && Agera 从源码简要分析基本调用流程(1)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/123 来源:腾云阁 https://www.qclo ...

随机推荐

  1. 初探 模拟退火算法 POJ2420 HDU1109

    模拟退火算法来源于固体退火原理,更多的化学物理公式等等这里不再废话,我们直接这么来看 模拟退火算法简而言之就是一种暴力搜索算法,用来在一定概率下查找全局最优解 找的过程和固体退火原理有所联系,一般来讲 ...

  2. PHP函数补完:call_user_func()

    call_user_func是PHP的内置函数,该函数允许用户调用直接写的函数并传入一定的参数,下面总结下这个函数的使用方法. 1,call_user_func函数类似于一种特别的调用函数的方法,使用 ...

  3. utf 8无bom和utf 8什么区别

    今天在上传CSV文件的时候,Windows下调试一切正常.妈的一到Linux下面,就出现问题,第一行数据总是读取不出来, 利用print_r()打印出读取文件的内容,发现有一个很奇怪的字符在作怪.为什 ...

  4. java操作AWS S3一些坑记录

    1,aws sdk jar版本不一致问题 一开始我在pom.xml中只配置了如下aws-java-sdk-s3 <!-- https://mvnrepository.com/artifact/c ...

  5. 极光大数据告诉你,程序员们都在"愁"些啥?

    有言道:隔行如隔山.面对不甚熟悉的人群和岗位,我们很容易在固有印象的干扰下,作出一些偏离实际的解读.比如在很多外行人眼中,程序员群体的固有形象是性格木讷,生活方式通常也比较宅.他们最大的爱好就是玩游戏 ...

  6. CF666E Forensic Examination SAM+线段树合并+前缀树倍增

    $ \color{#0066ff}{ 题目描述 }$ 给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l. ...

  7. leetcode-680-Valid Palindrome II

    题目描述: Given a non-empty string s, you may delete at most one character. Judge whether you can make i ...

  8. 玩转MongoDB

    一.主从复制 1)首先主从服务器在启动的时候,分别要设置master.slave选项,对于slave可以启动中设置源,也可以在启动后设置源. 如:主:mongod --dbpath=/data/mon ...

  9. Python爬取LOL英雄皮肤

    Python爬取LOL英雄皮肤 Python 爬虫  一 实现分析 在官网上找到英雄皮肤的真实链接,查看多个后发现前缀相同,后面对应为英雄的ID和皮肤的ID,皮肤的ID从00开始顺序递增,而英雄ID跟 ...

  10. 本地DataGrip连接阿里云MySQL

    1.阿里云上开通MySQL端口 2.MySQL上的设置 1⃣️mysql -uroot -p2⃣️create user 'usrabc'@'%' identified by 'usrabc'; 3. ...