- Retrofit解析
- EventBus解析
一、整体思路 {#section}
从使用方法出发,首先是怎么使用,其次是我们使用的功能在内部是如何实现的,实现方案上有什么技巧,有什么范式。全文基本上是对 OkHttp 源码的一个分析与导读,非常建议大家下载 OkHttp 源码之后,跟着本文,过一遍源码。对于技巧和范式,由于目前我的功力还不到位,分析内容没多少,欢迎大家和我一起讨论。
首先放一张完整流程图(看不懂没关系,慢慢往后看):
二、基本用例
来自OkHttp 官方网站。
2.1.创建 OkHttpClient 对象
OkHttpClient client = new OkHttpClient();
咦,怎么不见 builder?莫急,且看其构造函数:
public OkHttpClient() {
this(new Builder());
}
原来是方便我们使用,提供了一个“快捷操作”,全部使用了默认的配置。OkHttpClient.Builder
类成员很多,后面我们再慢慢分析,这里先暂时略过:
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
proxySelector = ProxySelector.getDefault();
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;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
}
2.2.发起 HTTP 请求
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
实现了Call.Factory
,负责根据请求创建新的Call
。
那我们现在就来看看它是如何创建 Call 的:
/**
- Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
如此看来功劳全在RealCall
类了,下面我们一边分析同步网络请求的过程,一边了解RealCall
的具体内容。
2.2.1.同步网络请求
我们首先看RealCall#execute
:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”); // (1)
executed = true;
}
try {
client.dispatcher().executed(this); // (2)
Response result = getResponseWithInterceptorChain(); // (3)
if (result == null) throw new IOException(“Canceled”);
return result;
} finally {
client.dispatcher().finished(this); // (4)
}
}
这里我们做了 4 件事:
- 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用
call#clone
方法进行克隆。 - 利用
client.dispatcher().executed(this)
来进行实际执行dispatcher
是刚才看到的OkHttpClient.Builder
的成员之一,它的文档说自己是异步 HTTP 请求的执行策略,现在看来,同步请求它也有掺和。 - 调用
getResponseWithInterceptorChain()
函数获取 HTTP 返回结果,从函数名可以看出,这一步还会进行一系列“拦截”操作。 - 最后还要通知
dispatcher
自己已经执行完毕。
dispatcher 这里我们不过度关注,在同步执行的流程中,涉及到 dispatcher 的内容只不过是告知它我们的执行状态,比如开始执行了(调用executed
),比如执行完毕了(调用finished
),在异步执行流程中它会有更多的参与。
真正发出网络请求,解析返回结果的,还是getResponseWithInterceptorChain
:
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
在[OkHttp 开发者之一介绍 OkHttp 的文章里面,作者讲到:
the whole thing is just a stack of built-in interceptors.
可见Interceptor
是 OkHttp 最核心的一个东西,不要误以为它只负责拦截请求进行一些额外的处理(例如 cookie),实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个Interceptor
,它们再连接成一个Interceptor.Chain
,环环相扣,最终圆满完成一次网络请求。
从getResponseWithInterceptorChain
函数我们可以看到Interceptor.Chain
的分布依次是:
- 在配置
OkHttpClient
时设置的interceptors
; - 负责失败重试以及重定向的
RetryAndFollowUpInterceptor
; - 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的
BridgeInterceptor
; - 负责读取缓存直接返回、更新缓存的
CacheInterceptor
; - 负责和服务器建立连接的
ConnectInterceptor
; - 配置
OkHttpClient
时设置的networkInterceptors
; - 负责向服务器发送请求数据、从服务器读取响应数据
CallServerInterceptor
。
在这里,位置决定了功能,最后一个 Interceptor 一定是负责和服务器实际通讯的,重定向、缓存等一定是在实际通讯之前的。
责任链模式在这个Interceptor
链条中得到了很好的实践。
它包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。
对于把Request
变成Response
这件事来说,每个Interceptor
都可能完成这件事,所以我们循着链条让每个Interceptor
自行决定能否完成任务以及怎么完成任务(自力更生或者交给下一个Interceptor
)。这样一来,完成网络请求这件事就彻底从RealCall
类中剥离了出来,简化了各自的责任和逻辑。两个字:优雅!
责任链模式在安卓系统中也有比较典型的实践,例如 view 系统对点击事件(TouchEvent)的处理。
回到 OkHttp,在这里我们先简单分析一下ConnectInterceptor
和CallServerInterceptor
,看看 OkHttp 是怎么进行和服务器的实际通信的。
2.2.1.1.建立连接:ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals(“GET”);
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
实际上建立连接就是创建了一个HttpCodec
对象,它将在后面的步骤中被使用,那它又是何方神圣呢?它是对 HTTP 协议操作的抽象,有两个实现:Http1Codec
和Http2Codec
,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。
在Http1Codec
中,它利用Okio对Socket
的读写操作进行封装,Okio 以后有机会再进行分析,现在让我们对它们保持一个简单地认识:它对java.io
和java.nio
进行了封装,让我们更便捷高效的进行 IO 操作。
而创建HttpCodec
对象的过程涉及到StreamAllocation
、RealConnection
,代码较长,这里就不展开,这个过程概括来说,就是找到一个可用的RealConnection
,再利用RealConnection
的输入输出(BufferedSource
和BufferedSink
)创建HttpCodec
对象,供后续步骤使用。
2.2.1.2.发送和接收数据:CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpCodec.finishRequest();
Response response = httpCodec.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if (“close”.equalsIgnoreCase(response.request().header(“Connection”))
|| “close”.equalsIgnoreCase(response.header(“Connection”))) {
streamAllocation.noNewStreams();
}
// 省略部分检查代码
return response;
}
我们抓住主干部分:
- 向服务器发送 request header;
- 如果有 request body,就向服务器发送;
重要知识点
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。
高级进阶篇——高级UI,自定义View(部分展示)
UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
- 面试题部分合集
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
到不错的工作了。不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
[外链图片转存中…(img-Bi6gDdOL-1715422591701)]
- 面试题部分合集
[外链图片转存中…(img-7qQkCQ1e-1715422591702)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!