阿里P8大牛从零开始教学开源框架:OkHttp解析+Retrofit解析

回到 OkHttp,在这里我们先简单分析一下ConnectInterceptorCallServerInterceptor,看看 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 协议操作的抽象,有两个实现:Http1CodecHttp2Codec,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。

Http1Codec中,它利用OkioSocket的读写操作进行封装,Okio 以后有机会再进行分析,现在让我们对它们保持一个简单地认识:它对java.iojava.nio进行了封装,让我们更便捷高效的进行 IO 操作。

而创建HttpCodec对象的过程涉及到StreamAllocationRealConnection,代码较长,这里就不展开,这个过程概括来说,就是找到一个可用的RealConnection,再利用RealConnection的输入输出(BufferedSourceBufferedSink)创建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;
}

我们抓住主干部分:

  1. 向服务器发送 request header;
  2. 如果有 request body,就向服务器发送;
  3. 读取 response header,先构造一个Response对象;
  4. 如果有 response body,就在 3 的基础上加上 body 构造一个新的Response对象;

这里我们可以看到,核心工作都由HttpCodec对象完成,而HttpCodec实际上利用的是 Okio,而 Okio 实际上还是用的Socket,所以没什么神秘的,只不过一层套一层,层数有点多。

其实Interceptor的设计也是一种分层的思想,每个Interceptor就是一层。为什么要套这么多层呢?分层的思想在 TCP/IP 协议中就体现得淋漓尽致,分层简化了每一层的逻辑,每层只需要关注自己的责任(单一原则思想也在此体现),而各层之间通过约定的接口/协议进行合作(面向接口编程思想),共同完成复杂的任务。

简单应该是我们的终极追求之一,尽管有时为了达成目标不得不复杂,但如果有另一种更简单的方式,我想应该没有人不愿意替换。

2.2.2.发起异步网络请求

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());
}
});

// RealCall#enqueue
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”);
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

这里我们就能看到 dispatcher 在异步执行时发挥的作用了,如果当前还能执行一个并发请求,那就立即执行,否则加入readyAsyncCalls队列,而正在执行的请求执行完毕之后,会调用promoteCalls()函数,来把readyAsyncCalls队列中的AsyncCall“提升”为runningAsyncCalls,并开始执行。

这里的AsyncCallRealCall的一个内部类,它实现了Runnable,所以可以被提交到ExecutorService上执行,而它在执行时会调用getResponseWithInterceptorChain()函数,并把结果通过responseCallback传递给上层使用者。

这样看来,同步请求和异步请求的原理是一样的,都是在getResponseWithInterceptorChain()函数中通过Interceptor链条来实现的网络请求逻辑,而异步则是通过ExecutorService实现。

2.3返回数据的获取

在上述同步(Call#execute()执行之后)或者异步(Callback#onResponse()回调中)请求完成之后,我们就可以从Response对象中获取到响应数据了,包括 HTTP status code,status message,response header,response body 等。这里 body 部分最为特殊,因为服务器返回的数据可能非常大,所以必须通过数据流的方式来进行访问(当然也提供了诸如string()bytes()这样的方法将流内的数据一次性读取完毕),而响应中其他部分则可以随意获取。

响应 body 被封装到ResponseBody类中,该类主要有两点需要注意:

  1. 每个 body 只能被消费一次,多次消费会抛出异常;
  2. body 必须被关闭,否则会发生资源泄漏;

在2.2.1.2.发送和接收数据:CallServerInterceptor小节中,我们就看过了 body 相关的代码:

if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}

HttpCodec#openResponseBody提供具体 HTTP 协议版本的响应 body,而HttpCodec则是利用 Okio 实现具体的数据 IO 操作。

这里有一点值得一提,OkHttp 对响应的校验非常严格,HTTP status line 不能有任何杂乱的数据,否则就会抛出异常,在我们公司项目的实践中,由于服务器的问题,偶尔 status line 会有额外数据,而服务端的问题也毫无头绪,导致我们不得不忍痛继续使用 HttpUrlConnection,而后者在一些系统上又存在各种其他的问题,例如魅族系统发送 multi-part form 的时候就会出现没有响应的问题。

2.4.HTTP 缓存

在2.2.1.同步网络请求小节中,我们已经看到了Interceptor的布局,在建立连接、和服务器通讯之前,就是CacheInterceptor,在建立连接之前,我们检查响应是否已经被缓存、缓存是否可用,如果是则直接返回缓存的数据,否则就进行后面的流程,并在返回之前,把网络的数据写入缓存。

这块代码比较多,但也很直观,主要涉及 HTTP 协议缓存细节的实现,而具体的缓存逻辑 OkHttp 内置封装了一个Cache类,它利用DiskLruCache,用磁盘上的有限大小空间进行缓存,按照 LRU 算法进行缓存淘汰,这里也不再展开。

我们可以在构造OkHttpClient时设置Cache对象,在其构造函数中我们可以指定目录和缓存大小:

public Cache(File directory, long maxSize);

而如果我们对 OkHttp 内置的Cache类不满意,我们可以自行实现InternalCache接口,在构造OkHttpClient时进行设置,这样就可以使用我们自定义的缓存策略了。

三、总结

OkHttp 还有很多细节部分没有在本文展开,例如 HTTP2/HTTPS 的支持等,但建立一个清晰的概览非常重要。对整体有了清晰认识之后,细节部分如有需要,再单独深入将更加容易。

在文章最后我们再来回顾一下完整的流程图:

  • OkHttpClient实现Call.Factory,负责为Request创建Call
  • RealCall为具体的Call实现,其enqueue()异步接口通过Dispatcher利用ExecutorService实现,而最终进行网络请求时和同步execute()接口一致,都是通过getResponseWithInterceptorChain()函数实现;
  • getResponseWithInterceptorChain()中利用Interceptor链条,分层实现缓存、透明压缩、网络 IO 等功能;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

文章不易,如果大家喜欢这篇文章,或者对你有帮助希望大家多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

由于文章篇幅问题 查看详细文章以及获取学习笔记链接:GitHub

  • Android进阶学习全套手册
    关于实战,我想每一个做开发的都有话要说,对于小白而言,缺乏实战经验是通病,那么除了在实际工作过程当中,我们如何去更了解实战方面的内容呢?实际上,我们很有必要去看一些实战相关的电子书。目前,我手头上整理到的电子书还算比较全面,HTTP、自定义view、c++、MVP、Android源码设计模式、Android开发艺术探索、Java并发编程的艺术、Android基于Glide的二次封装、Android内存优化——常见内存泄露及优化方案、.Java编程思想 (第4版)等高级技术都囊括其中。

  • Android高级架构师进阶知识体系图
    关于视频这块,我也是自己搜集了一些,都按照Android学习路线做了一个分类。按照Android学习路线一共有八个模块,其中视频都有对应,就是为了帮助大家系统的学习。接下来看一下导图和对应系统视频吧!!!

  • Android对标阿里P7学习视频

  • BATJ大厂Android高频面试题
    这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等
    …(img-8DmZwhoJ-1711105850421)]

  • Android对标阿里P7学习视频

[外链图片转存中…(img-Y3uOl02e-1711105850422)]

  • BATJ大厂Android高频面试题
    这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值