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

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. TCP协议中URG和PSH位

    URG(紧急位):设置为1时,首部中的紧急指针有效:为0时,紧急指针没有意义. PSH(推位):当设置为1时,要求把数据尽快的交给应用层,不做处理 通常的数据中都会带有PSH但URG只在紧急数据的时设 ...

  2. top 常用命令

    参考文档: http://www.cnblogs.com/allen8807/archive/2010/11/10/1874001.html [root@linux ~]# top [-d] | to ...

  3. Swift和Objective C关于字符串的一个小特性

    一.Unicode的一个小特性 首先,Unicode规定了许多code point,每一个code point表示一个字符.如\u0033表示字符"3",\u864e表示字符&qu ...

  4. 2016级算法第三次上机-C.AlvinZH的奇幻猜想——三次方

    905 AlvinZH的奇幻猜想--三次方 思路 中等题.题意简单,题目说得简单,把一个数分成多个立方数的和,问最小立方数个数. 脑子转得快的马上想到贪心,从最近的三次方数往下减,反正有1^3在最后撑 ...

  5. 关于导入本地maven项目pom.xml出现missing artifact org....报错处理

    一.导入本地maven项目步骤:

  6. editplus5注册码

    注册码:Vovan 3AG46-JJ48E-CEACC-8E6EW-ECUAW

  7. JavaScript实现自定义alert弹框

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAh0AAAFkCAYAAACEpYlzAAAfj0lEQVR4nO3dC5BddZ0n8F93pxOQCO

  8. [转] linux nc命令

    [From] https://blog.csdn.net/freeking101/article/details/53289198 NC 全名 Netcat (网络刀),作者是 Hobbit & ...

  9. Oracle触发器简单使用记录

    在ORACLE系统里,触发器类似函数和过程.1.触发器类型:(一般为:语句级触发器和行级触发器.) 1).DML触发器: 创建在表上,由DML事件引发 2).instead of触发器: 创建在视图上 ...

  10. 反弹shell集锦

    1.   关于反弹shell 就是控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端.reverse shell与telnet,ssh等标准shell对应,本质 ...