前言
用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(); ) {