深入理解OkHttp源码及设计思想

本文深入探讨OkHttp的源码,分析其整体结构、分层设计、请求处理流程以及拦截器机制。OkHttp采用外观模式和拦截器链,通过分层设计解决网络请求的复杂性,实现请求和回调、分发器及线程池、拦截器等功能。文章详细讲解了请求数据、行为、分发器管理、线程池的实现以及拦截器的层次设计和链式处理,帮助读者理解OkHttp的工作原理。
摘要由CSDN通过智能技术生成

前言

用OkHttp很久了,也看了很多人写的源码分析,在这里结合自己的感悟,记录一下对OkHttp源码理解的几点心得。

整体结构

网络请求框架虽然都要做请求任务的封装和管理,但是最大的难点在于网络请求任务的多样性,因为网络层情况复杂,不仅要考虑功能性的建立Socket连接、文件流传输、TLS安全、多平台等,还要考虑性能上的Cache复用、Cache过期、连接池复用等,这些功能如果交错在一起,实现和维护都会有很大的问题。

为了解决这个问题,OkHttp采用了分层设计的思想,使用多层拦截器,每个拦截器解决一个问题,多层拦截器套在一起,就像设计模式中的装饰者模式一样,可以在保证每层功能高内聚的情况下,解决多样性的问题。

OkHttp使用了外观模式,开发者直接操作的主要就是OkHttpClient,其实如果粗略划分的话,整个OkHttp框架从功能上可以分为三部分:

1.请求和回调:具体的类就是Call、RealCall(及其内部类AsyncCall)、Callback等。

2.分发器及线程池:具体的类就是Dispatcher、ThreadPoolExecutor等。

3.拦截器:实现了分层设计+链式调用,具体的类就是Interceptor+RealInterceptorChain。

至于更具体的操作,均由拦截器实现,包括应用层拦截器、网络层拦截器等,开发者也可以自己扩展新的拦截器。

请求

网络请求其实可以分为数据和行为两部分,数据即我们的请求数据和返回数据,行为则是发起网络请求,以及得到处理结果。

数据(Request和Response)

在OkHttp中,用Request定义请求数据,用Response定义返回数据,这两个类都使用了建造者模式,把对象的创建和使用分离开,但这两个类更接近于数据模型,主要用来读写数据,不做请求动作。

行为(Call/RealCall/AsyncCall和Callback)

在OkHttp中,用Call和Callback定义网络请求,用Call去发起网络请求,用Callback去接收异步返回,(如果是同步请求,就直接返回Response数据)。

其中,Call是个接口,真正的实现类是RealCall,RealCall如果需要异步处理,还会先包装为RealCall的内部类AsyncCall,然后再把AsyncCall交给线程池。

在具体执行过程中,把数据对象交给行为对象去操作:

在RealCall行为中调用enqueue去发起异步网络请求,此时需要传参Request数据对象;返回的Callback会传递Response数据对象。

如果RealCall行为中调用的是execute同步网络请求,就直接返回Response数据对象。

RealCall只是对请求做了封装,真正处理请求的是分发器Dispatcher。

分发器及线程池

对于网络请求RealCall来说,需要可并行、可回调、可取消,因为OkHttp统一使用Dispatcher分发器来分发所有的Call请求,分发给多个线程进行执行(所以Dispatcher也叫反向代理),所以,这几个问题就需要交给Dispatcher来处理,对于Dispatcher来说,可并行、可回调、可取消的问题可以进一步被分解为以下几个问题,并分别处理:

1.有没有必要管理所有的请求

不论是同步请求还是异步请求,都是耗时操作,所以是个需要观测的行为,比如请求结束需要处理,请求本身可能取消等,都需要管理起来。

而且,不论是正在运行的,还是等待运行的,都需要管理。

2.如何管理所有的请求

为了管理所有的请求,Dispatcher采用了队列+生产+消费的模式。

为同步执行提供了runningSyncCalls来管理所有的同步请求;

为异步执行提供了runningAsyncCalls和readyAsyncCalls来管理所有的异步请求。

其中readyAsyncCalls是在当前可用资源不足时,用于缓存请求的。

由于这三个队列的使用场景类似于栈,偶尔需要删除功能,所以OkHttp使用了ArrayDeque双端队列来管理,ArrayDeque的设计和实现非常精妙,感兴趣的可以深入了解一下。

https://www.jianshu.com/p/132733115f95

3.如何确保多个队列之间能顺畅地调度

对于多线程情况下的队列调度,其实就是数据移动和失败阻塞的这两个问题。

对于数据移动来说,就是要考虑多线程下队列数据移动的问题。

对于同步请求来说,只有1个队列,不存在数据移动,数据移动的场景在两个异步队列,每当有一个异步请求finish了,就需要从待处理readyAsyncCalls队列移动到runningAsyncCalls队列,这在多线程场景下并不安全,需要加锁:

synchronized (this) {//加锁操作

if(!calls.remove(call))thrownewAssertionError("Call wasn't in-flight!");

if(promoteCalls) promoteCalls();

runningCallsCount = runningCallsCount();

idleCallback =this.idleCallback;

}

在promoteCalls时,会把call从ready队列转移到running队列:

privatevoidpromoteCalls(){

if(runningAsyncCalls.size() >= maxRequests)return;// Already running max capacity.

...

for(Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值