Android OkHttp源码分析

10月裸辞至今找两个月工作了,本来面试就少才四个,还都没结果,麻了。。

在家呆着没事,也懒得复习面试八股文了,就分析下OkHttp吧,Android端使用最广泛的网络框架。

版本:3.14.9

个人将项目拆解为五个部分进行理解会议,包括请求封装请求、任务调度、责任链模式、连接池以及网络数据流的读写。

封装请求

设计模式是采用Builder构建者模式,原因是构建对象参数比较灵活,其次请求对象构建完成后是不可变对象,因此使用构建者模式比较合适。

通过 curl命令 curl -v https://www.baidu.com/,可见以下打印:

> GET / HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.79.1
> Accept: */*

对应请求实体类Request, 将请求地址,请求方式以及请求头进行封装:

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Map<Class<?>, Object> tags;
}

RequestBody是对请求体的封装,通过GET/HEAD/POST/DELETE/PUT/PATCH封装。

HTTP协议的方法及应用场景 - susanhonly - 博客园

  • HEAD:HEAD和GET本质是一样的,区别在于HEAD不含有呈现数据,而仅仅是HTTP头信息。有的人可能觉得这个方法没什么用,其实不是这样的。想象一个业务场景:欲判断某个资源是否存在,我们通常使用GET,但这里用HEAD则一样更加明确。
  • PUT:这个方法比较少见。HTML表单也不支持这个。本质上来讲,PUT和POST极为相似,都是向服务器发送数据,但它们之间有一个重要区别,PUT通常指定了资源的存放位置,而POST则没有,POST的数据存放位置由服务器自己决定。举个例子:如一个用于提交博文的URL,/addBlog。如果用PUT,则提交的URL会是像这样的“/addBlog/abc123”,其中abc123就是这个博文的地址。而如果用POST,则这个地址会在提交后由服务器告知客户端。目前大部分博客都是这样的。显然,PUT和POST用途是不一样的。具体用哪个还取决于当前的业务场景。
  • DELETE:删除某一个资源。基本上这个也很少见,不过还是有一些地方比如amazon的S3云服务里面就用的这个方法来删除资源。
  • OPTIONS:这个方法很有趣,但极少使用。它用于获取当前URL所支持的方法。若请求成功,则它会在HTTP头部包含一个名为“Allow”的头,值是所支持的方法,如“GET,POST”。

客户端用的比较多的是GET和POST。当使用POST时,通过FormBody或者MultipartBody构建数据。FormBody用于传输表单数据,需要以JSON格式传递数据时设置为 application/json; MultipartBody用于文件上传时,通过RequestBody.create接口创建上传对象,可以是文件或者字节流。

给我们开发一个启示,如果api提供的接口请求参数比较多,可以使用Request包装的方式使得调用更简单。

任务调度

 请求任务根据是否异步进行分发,任务根据同步还是异步存入不同类型的队列中,后进入责任链模块处理;异步任务中有限制:然后会根据64并发限制以及同主机5并发限制启动线程池。

因为网络接口支持取消的功能,因此无论是同步还是异步请求任务都会首先添加到队列中做记录,后续如果有取消的触发,直接从队列中取出对应的值取消就可以了。

同步队列:  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

异步队列:

private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

当readyAsyncCalls中的任务通过并发限制检查之后,移入runningAsyncCalls队列中。

ArrayDeque 

按照注释解释:容器大小可动态调整的非线程安全队列;元素非空;以栈方式使用时性能高于Stack,以队列方式使用时性能高于LinkedList。请求任务变化频繁,需要经常性添加与删除,AD采用循环数组实现,在增删查以及扩容方面优化多。平常开发可借鉴使用。Java ArrayDeque源码剖析 - CarpenterLee - 博客园

责任链

责任链模式必须注意先后顺序问题。

从前往后依次是:自定义拦截器,重试和重定向,配置请求头,读写缓存,连接,请求。

自定义拦截器是开发可以插入处理操作的部分,必须前置。还有WebSocket的处理省略了,平常从来没有用到过。

连接池

获取连接流程是在ConnectInterceptor中完成。创建Socket连接后,

1.从当前请求中获取连接,如果有且可用则返回,此处感觉像是处理重定向的情况。

2.从连接池中获取新连接。

3.如果未从连接池获取到,遍历路由,继续获取,这里应该是考虑到Http2协议,http2可以让多个请求合并为一个TCP连接发起。

4.如果连接池未获取到,创建新连接,并与当前请求绑定。

在连接池内部维护一个ArrayDeque的双端队列,默认的闲置的连接数为5,闲置连接时间为5分钟。当创建新连接添加入队时,会在子线程启动清理任务来删除长时间不用的连接避免资源浪费。

 请求接口

利用在ConnectInterceptor中返回的ExchangeCodec与服务端交互。低层数据流工具使用了Okio的Source/Sink实现读写流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值