前言
节前想着丰富下假期,在慕**上花了几百大洋买了个课程,结果三观都掉没了,虽然现在知识付费了,但这tama也叫高级课程!为了省钱,最直接的办法就是自己干,当然我是指学习方面。
一般而言懂得如何使用,此人可以认定为初级;懂得内部逻辑,熟悉流程间衔接,能够在框架提供的框框内玩耍API,世俗认定为此人中高级、已经很不错了;但我认为还不够。
本着不懂就查,不懂就深追的精神!!此文不同于其他讲解(介绍下如何使用、基本的源码流程、责任链和几大拦截器,这也是现状),我剑走偏锋,不是流程上的讲解,而是深入研读每一步的源码了解作者为何及意图,这也意味着你必需要知道OkHttp浅层的知识,否则看起来会很累,文章采用CPU响应中断保护现场的模式书写,以免思路混乱,同时文章也会异常的长长长。。。。
虽然与现在大环境相逆,但我深信:知其然,要知其所以然,这才是真正的工程师,而不是码农。
干货总结
由于文章太长、内容太多,相信大多数人不可能全部读完,因此总结放在前面,希望能够提起兴趣读完全文。
- 接口适配器模式
- StrictMode之CloseGuard
-
双端队列的概念及环形队列的操作
/* 4XX: client error */
/**
* HTTP Status-Code 400: Bad Request.
*/
public static final int HTTP_BAD_REQUEST = 400;
/**
* HTTP Status-Code 401: Unauthorized.
*/
public static final int HTTP_UNAUTHORIZED = 401;
/**
* HTTP Status-Code 402: Payment Required.
*/
public static final int HTTP_PAYMENT_REQUIRED = 402;
/**
* HTTP Status-Code 403: Forbidden.
*/
public static final int HTTP_FORBIDDEN = 403;
/**
* HTTP Status-Code 404: Not Found.
*/
public static final int HTTP_NOT_FOUND = 404;
/**
* HTTP Status-Code 405: Method Not Allowed.
*/
public static final int HTTP_BAD_METHOD = 405;
/**
* HTTP Status-Code 406: Not Acceptable.
*/
public static final int HTTP_NOT_ACCEPTABLE = 406;
/**
* HTTP Status-Code 407: Proxy Authentication Required.
*/
public static final int HTTP_PROXY_AUTH = 407;
/**
* HTTP Status-Code 408: Request Time-Out.
*/
public static final int HTTP_CLIENT_TIMEOUT = 408;
/**
* HTTP Status-Code 409: Conflict.
*/
public static final int HTTP_CONFLICT = 409;
/**
* HTTP Status-Code 410: Gone.
*/
public static final int HTTP_GONE = 410;
/**
* HTTP Status-Code 411: Length Required.
*/
public static final int HTTP_LENGTH_REQUIRED = 411;
/**
* HTTP Status-Code 412: Precondition Failed.
*/
public static final int HTTP_PRECON_FAILED = 412;
/**
* HTTP Status-Code 413: Request Entity Too Large.
*/
public static final int HTTP_ENTITY_TOO_LARGE = 413;
/**
* HTTP Status-Code 414: Request-URI Too Large.
*/
public static final int HTTP_REQ_TOO_LONG = 414;
/**
* HTTP Status-Code 415: Unsupported Media Type.
*/
public static final int HTTP_UNSUPPORTED_TYPE = 415;
正文
以下内容基于OkHttp3.11.0版本
OkHttp的所有操作,都在当前线程,包括结果回调,这也意味着使用时必须为其开单独的子线程,否则OkHttp内部检查线程时就会报错。
OkHttpClient通过构建者模式创建实例,初始化参数,项目中可以使用单例,如果是多后台项目需要清楚内部参数设置,看完全文后会清楚原因。
Request同理创建实例,初始化参数。并作为参数初始化接口Call
这里可见,Call的实现类为RealCall,且持有OkHttpClient实例对象。
经由静态方法,进入构造方法给变量赋值。
分支开始EventListener,保存RealCall位置
为什么非要经过静态方法呢?给每一个Call对象的eventListener赋值为
经okHttpClient实例对象eventListenerFactory创建的EventListener。
EventListener.Factory来自Builder
如果Builder没有主动设置过,则默认生成。
EventListener.Factory接口根据传入的Call生成EventListener
EventListener是什么有什么用?
通过类结构,可以看出EventListener是一个抽象类,采用适配器模式(若定义为接口,则必须实现全部方法,又称接口适配器模式)默认每个方法都是空实现,继承者根据需要实现相应方法。方法名也很好理解,结果表明:这就是是请求生命周期的回调,在请求的不同时期会回调相应的方法。
那Call中的EventListener对象是哪里实现的?
一步步,通过Builder初始化默认eventListenerFactory,再传递到okHttpClient的eventListenerFactory,最终由eventListenerFactory创建EventListener。
哪里啊?没看见!这里确实绕了几个弯,第三、四张图是关键。
第四张图静态方法生成EventListenerFactory,但非常简单,传入的参数是EventListener,什么都没做就返回出去了,表明实际的eventListener对象是外部初始化后传入的。
回来看第三张图,传入的eventListener对象是什么?EventListener.NONE,又回去
eventListener对象为默认的EventListener类内部静态变量,且各个生命周期的回调并没有重写,意味着生命周期回调还是空实现。
这里就需要我们手动继承EventListener实现自己的生命周期监听类,并实现
EventListener.Factory接口,重写create返回自己的生命周期监听类,赋值给OkHttpClient.Builder
这样下来,经由此okHttpClient对象的Call,在相应的生命周期就会回调到自己的生命周期监听类内。
虽然代码绕来绕去,但更能够理解作者的意图,每个okHttpClient对象内不直接赋值或生成EventListener,而是存储eventListenerFactory工厂,让具体的EventListener由具体的eventListenerFactory生成,降低了okHttpClient对象与eventListener对象的耦合。
分支结束EventListener,读取RealCall位置
RealCall的构造方法内还赋值了retryAndFollowUpInterceptor变量,这是拦截器的起点(下面讲)。
Call对象初始化完成,其实就是基本的变量初始化。
执行execute方法,在其实现类RealCall内
每个Call对象只能被执行一次,有变量executed来记录自己是否被执行过,这里加锁为了防止多线程间线程安全,因为每个请求都会放到池子pool内(后面讲)。
分支开始StackTrace,保存RealCall位置
execute内执行captureCallStackTrace,译为"开启Call的栈跟踪"。
Platform类如其意"平台",注意有Android的Logger打印日志类变量,返回的PLATFORM来自findPlatform。
注释也写了,通过运行环境匹配合适的Platform,这里列举了几个平台,除了android其他的都不认识。
如果运行环境平台都不符合,则new Platform();
方法getStackTraceForCloseable返回的是Throwable
若符合android平台,通过反射获取一些证书、Session等网络相关的类。当Platform支持此功能时,便会反射invoke相关功能。
返回AndroidPlatform,其继承自Platform
android平台下getStackTraceForCloseable被重写
这里有个内部类CloseGuard,其实这个类在android源码dalvik.system.CloseGuard包下,注释中也有提到,这里只是利用反射,封装了原CloseGuard的方法。
分支开始CloseGuard,保存StackTrace位置
先看看源码中的CloseGuard类
想多学习的也可以看看 http://duanqz.github.io/2015-11-04-StrictMode-Analysis#All
源码取自android7.0路径看图
注释中解释了此类的作用及如何使用,我再复述一遍。
CloseGuard类用来记录对象是否被关闭,某些情况下如果对象不关闭的话就会造成内存泄露或其他问题,由于CloseGuard类是源码级别的类,很多源码中判断对象是否关闭都有用到它如InputStream,可以看看引用的文章。
CloseGuard类使用也很简单,如例子,在使用对象Foo类中增加变量guard,实例化Foo对象时调用guard的open方法,表示对象开启在被使用,对象关闭调用cleanup,此时guard调用close,表明对象关闭不再使用。此时JVM便会GC此对象调用其finalize方法,如果对象不被使用且没有关闭调用cleanup(即内存泄露),GC时便会发现guard,并警告调用warnIfOpen。
整体思路就是这样,看看源码的实现。
注释写的很好,很明白。
CloseGuard类为final,在JVM方法区运行时常量池中存在NOOP对象,当CloseGuard功能不使用时,可避免浪费内存生成对象。
CloseGuard功能默认是打开的,android在启动的时候关闭了此功能,注释中也有提到。
何为关闭?功能开启情况下一个对象对应一个CloseGuard对象,关闭情况下,所有对象对应一个CloseGuard对象NOOP,即起不到监控对象是开启还是关闭的状态。
get方法直接返回自己或实例化自己
open方法则初始化了Throwable的allocationSite对象,针对不同的对象传入不同的closer信息,可以加速问题的排查
close方法置空了allocationSite对象
warnIfOpen方法在发现功能开启且对象未关闭时,便会报异常信息
而报异常信息的对象是系统级别
分支结束CloseGuard,读取StackTrace位置
CloseGuard通过反射取得源类的Method作为自己的变量,对象调用方法时,再利用反射invoke源类中相应的方法,达到对应的功能
getStackTraceForCloseable方法调用返回的是源CloseGuard对象
源CloseGuard对象
又被赋值给责任链的第一环RetryAndFollowUpInterceptor拦截器中,此时的对象关系为:RealCall对象持有RetryAndFollowUpInterceptor对象,RetryAndFollowUpInterceptor对象持有CloseGuard对象。简言之:一个Call对象对应一个CloseGuard对象,当Call对象没有被close时便会报异常。
那这个CloseGuard对象何时被close呢?答案是没有,因为有池子的关系所有的Call都在池子内,当Call真正被回收清除的时候就会通过CloseGuard对象log下。
CloseGuard对象赋值给RetryAndFollowUpInterceptor对象
RetryAndFollowUpInterceptor对象转手有将它赋值给StreamAllocation对象(后面讲)
StreamAllocation对象转手有将它赋值给StreamAllocationReference它自己的弱引用
当池子需要清理了
便会取出之前的CloseGuard对象
log一下通知此连接已关闭,但并非真关闭,只是此次的连接请求关闭了,连接本身还留着执行下次的请求,这是pool的功能。
如果CloseGuard对象没有成功log,便会调用android中的Log类。
到此就是整个Call连接的路径追踪,从创建Call到连接流关闭,Platformge根据平台的不同,以不同的方式打印追踪Call连接流的关闭事件。
分支结束StackTrace,读取RealCall位置
eventListener生命周期回调callStart
紧接着httpClient对象调用dispatcher执行call对象
dispatcher对象在httpClient通过builder创建时默认生成
Dispatcher为异步请求执行策略,且为final类不允许继承
有可能官方后面会有扩充,通过开放继承或实现接口以用来个性化定制。目前版本,此类主要针对异步请求,对同步请求没影响仅做计数功能
Dispatcher中几个重要的变量,从上至下依次(其实变量名起的很明白)
- 最大请求数
- 每台主机最大请求数
- Dispatcher空闲时的回调
- 线程池
- 异步等待队列
- 异步请求队列
- 同步请求队列
分支开始Deque,保存RealCall位置
什么是Deque?与Queue有什么关系?Queue又是什么?
Queue是数据结构中的队列,Queue提供了队列的基本操作,不同的实现类有具体的实现
通过类图看出,Deque接口继承自Queue,Dispatcher中的对象为ArrayDeque,它实现了Deque接口并继承自AbstractCollection。
那不是实现了两次Collection接口吗?
并不是,首先看一下实现与继承的区别:
java是单继承多实现,接口表示具体功能,实现接口意味着类具有某功能。继承为子承父业,父类具有的功能,子类同样具有。实现为具有某功能,继承为具有某功能的实现。
AbstractCollection为抽象类,实现了Collection声明的方法,又将功能合并生成抽象方法;ArrayDeque实现了Collection接口,表示ArrayDeque类具有Collection接口声明的功能,功能的具体实现在父类AbstractCollection中,ArrayDeque只需实现AbstractCollection类合并功能后的抽象方法。
简言之:ArrayDeque实现了Collection接口,但具体的实现在AbstractCollection中。这种设计思路值得学习
Queue为队列提供了队尾插入add、offer功能,队首移除remove、poll功能,还有偷窥队首元素element、peek功能,属于常规的队列操作接口
Deque继承自Queue,但其增加了队列首尾的功能,使队列成为双端队列,即首尾都可插入、移除。其还有栈功能,但不在此次介绍范围内。
ArrayDeque为双端队列实现的其中一种,简单看下源码:
与大部分集合工具类一样,具体的数据存储在数组中,可以看到transient关键字,表明不能够被序列化,但其内部有writeObject、readObject自己的序列化方式。
ArrayDeque是一种环形队列(源码中的head、tail指针与图中的算法不同,此图仅做参考,ArrayDeque中tail指针指null)
通过head、tail指针完成整个队列的操作
初始化存储空间,numElements二进制每位或1操作,使initialCapacity为奇数,再自加为偶数,保证initialCapacity一定为2的n次方(英文翻译),正确应是:保证initialCapacity一定为偶数,只有这样在后面的求mask时二进制的各位才能都被与操作到。
如果initialCapacity超出最大长度则分配2^30 elements
双端队列插入操作
头部指针指向位置插入元素
红框标示的位置及为什么ArrayDeque的容量必须为偶数,只有偶数减1,必为奇数,而奇数二进制位的最后一位必定位1,这样与操作才不会漏位。
offer的内部逻辑与add是一致的,只不过offer成功会return true,不成功就NullPointerException
接着就是扩容逻辑
断言判断队列是否已满,保存临时变量记录位置,容量扩为原来的2倍,
将队列中head指针右侧的数组,拷贝到新数组的左端,紧接着拷贝剩余位置起点到tail指针的数组,最终新数组左侧填满,右侧空。
poll操作不会抛出异常,而remove不同,会有NoSuchElementException
为什么说tail指null呢?看pollFirst直接取head指针取数据,而pollLast是先计算tail指针再去取
注意get操作,并没有删除数据,但会NoSuchElementException
反倒peek不会
移除第一个匹配项,分为从head循环和tail循环,首次遇到equal项即删除
所有的数组集合工具类一样,删除意味着移动
check了队列的正确性后,移动数组,分了两种情况:
- head在tail前
- head在tail后
队列操作的实质
验证了我之前对head和tail的解析
分支结束Deque,读取RealCall位置
Dispatcher使用队列,正式因为队列的FIFO性质,符合网络请求的场景
dispatcher对象将call对象放入同步请求队列runningSyncCalls即结束
然后在请求返回后,调用dispatcher对象将call结束
简单的操作,将call从队列中移除,由于是同步操作所以没有触发promoteCalls,并重新计算此dispatcher(即okHttpclient)中正在运行的call总数
也很easy就是求和,如果没有正在执行的请求就执行Dispatcher空闲时的回调。
现在可能对Dispatcher的作用产生疑问,它到底有什么用?提前剧透,OkHttp中有池子的概念即复用,这会导致不同的请求使用同一个流,如何正确的识别请求而对外屏蔽池子,即是Dispatcher的功能。我们只管向Dispatcher中加请求,完成了就移除,判断请求存在与否时直接使用Dispatcher,即使请求被复用到了其他请求,Dispatcher也会将之前的请求移除,认为是不同的操作。
除了以上Dispatcher还做了请求量的控制和一些基本Call对象操作,接下来异步请求时会涉及
Dispatcher的在同步请求时没做多少事情,到此就休息了。那请求的结果是如何得到的?