作为android著名开源厂商square的主打产品,OkHttp可谓是具有诸多优点,毕竟站在巨人的肩膀上,吸取了众多网络框架的优点,提升了稳定性。
对于不会使用OkHttp的,推荐泡在网上的日子的一片博文OkHttp使用教程。
OkHttp的调用可以简单的分为以下几步
- 生成一个OkHttpClient(用以总的控制)
- 用各种键值对包装我们的Request
- 将请求加入队列生成一个Call管理请求
- 若是同步请求直接执行excute等待处理返回Response,若为异步则实现Callback回调,在onResponse里获取Response参数
因此我们的分析也将从这几个步骤出发。
首先对于OkHttpClient来说,OkHttp官方文档并不建议我们创建多个OkHttpClient,因此全局使用一个。 如果有需要,可以使用clone方法,再进行自定义,类似于数据操作类,采用单例模式有利于我们省却同步问题,以及节省性能。
当我们初始化OkHttpClient的时候,其内部先生成了一个Builder,然后初始化一系列管理器,以及初始化参数
OkHttpClient的Builder初始化参数
这里我们着重分析下Dispatcher,首先我们需要了解下线程池,以及反向代理模式
线程池技术
相比我们对于异步任务的需求应该遇到了不少,首先想到的便是Thread,handler等异步机制,Java已经做了很好的封装,但是当我们需要使用许多异步任务,而这些任务只做了一点点事情就完成了它的使命,当我们不断的创建,销毁线程的时候,对系统的开销是相当大的,因为这些过程也是耗费时间的,这个时候我们就需要用到线程池了,我们只要往池子里放任务,他就会自动帮你管理线程的创建与销毁,利用缓存复用减少线程销毁创建,优化系统性能。以下是线程池的优点:
- 通过对线程进行缓存,减少了创建销毁的时间损失
- 通过控制线程数量阀值,减少了当线程过少时带来的CPU闲置(比如说长时间卡在I\O上了)与线程过多时对JVM的内存与线程切换压力
而Dispatcher内部的核心即线程池
- int corePoolSize: 最小并发线程数,这里并发同时包括空闲与活动的线程,如果是0的话,空闲一段时间后所有线程将全部被销毁。
- int maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理
- long keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间,类似于HTTP中的Keep-alive
- TimeUnit unit: 时间单位,一般用秒
- BlockingQueue workQueue: 工作队列
- ThreadFactory threadFactory: 单个线程的工厂,可以打Log,设置Daemon(即当JVM退出时,线程自动结束)等
反向代理模式
为了解决单生产者多消费者问题,OkHttp采用了反向代理模式,来解决非阻塞,高并发问题
Dispatch模式
- int corePoolSize: 最小并发线程数,这里并发同时包括空闲与活动的线程,如果是0的话,空闲一段时间后所有线程将全部被销毁。
- int maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理
- long keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间,类似于HTTP中的Keep-alive
- TimeUnit unit: 时间单位,一般用秒
- BlockingQueue workQueue: 工作队列
- ThreadFactory threadFactory: 单个线程的工厂,可以打Log,设置Daemon(即当JVM退出时,线程自动结束)等
OkHttp内Dispatcher原理
其实当我们调用client.newCall(request).enqueue(new callback(){...})的时候实质是
此时判断线程池内有无空闲,否则进入等待队列,AsyncCall实质是Runnable,当任务执行完毕后,总会调用finally{
client.dispatcher().finished(this);}清除此任务。
我们回头看看OkhttpClient初始化内其它一些参数Expires,Cache-Control,Last-Modified,If-Modified-Since,ETag,If-None-Match...这些都牵扯到缓存问题,大概对于缓存的判断过程入下图
okhttp内部是通过转化实现以上机制
参数解释:
request:这些头参数会被okhttp的流程装置Interceptor打断(下面会讲),组装成新的request
cacheCandidate:上次与服务器交互的缓存的Response,基于文件系统的Map,key为url的md5,置换算法为lru,即带缓存Header的Response。
具体内部实现为CacheStrategy,有兴趣的可以自己看下源码
接下来讲讲上面提到的流程工具Interceptor
从上图看出主要用于两个地方,对request进行包装,或者对response进行解析,Interceptor可以用来监控log,修改请求,修改结果,还有GZIP压缩。
对于request加工过程
- 加工过程是一个自增递归调用过程,直到将拦截器全部调用完。(header加一个map就生成一个拦截器)
- 处理完后,获得网络请求,getResponse(),将按照http协议规范重新序列化对象中的数据信息,最终为Raw文本
获得Response后的加工
总体是一个将Socket解析为Response对象的过程
- 读取Raw,反序列化为Statusline对象
- 以Transfer-Encoding:chunked的模式传输并组装Body(此处不太明白)
接下来看看Call,Call相对比较简单是个接口,RealCall为其实现类主要实现一个桥梁作用,连接okhttpclient与request最后我们要进行真正的网络请求了,就要涉及到网络路径选择,这又是okhttp的另一大优势
原来的http链接我们知道是单次传输,结束后就不再通信了,对于网络请求比较频繁的操作,这种方式貌似比较浪费时间,中间需要不断的建立连接,因此我们需要socket那种一直保持连接的方式,所幸Http有keepalive属性,直接保持连接
OkHttp中会保持5个并发,时间为5分钟,当然这些数字是经过考量,测试得出的
- 首先因为服务器的带宽是有限定的,他能提供的服务也是有限的,就像银行办事,可以开很多个窗口,但是银行员工就那么几个,你要是站着茅坑不拉屎,势必影响整个银行的办事效率
- 服务器/防火墙一般都会有并发限制,毕竟这个就跟硬件搭界了
- 当然不排除黑客用僵尸连接一直占着你服务器资源
这里对于连接的控制主要由Connection(连接主要对象),StreamAllocation计数器(实现回收策略关键),ConnectionPool连接池以及Deque构成
- 首先Connection是个接口,他的实现类是RealConnection,这个对象保存了连接的信息,比如,核心Socket对象,Handshake握手信息,Protocol信息...是整个请求的核心部分,也是管理单元
- 其次StreamAllocation是个计数器,用来记录Connection被引用的次数,以便我们在清理整个ConnectionPool的时候可根据最少使用原则,将引用置换或者清理
- Dequeue用来存放RealConnection
- ConnectionPool是管理Connection的集合,提供类基本的管理类该有的功能,其中包含一个线程池来定时执行清理任务cleanup(long now)具体清理过程有别于GC的线索查找,即上述的计数器原理,而cleanup内又有pruneAndGetAllocationCount函数用来扫描当前Dequeue内Connection的状况,并返回等待时间(主要条件即空闲Socket>5&&keepalive<5)
由于个人项目网络缓存是自己写的,更好控制,okhttp的缓存策略也就不深入研究了,不过大家可以了解下Okio这个库,是个对文件I/O封装很好的库,如果有兴趣的同学可以参考
文/BlackSwift(简书作者)
原文链接:http://www.jianshu.com/p/aad5aacd79bf
再次感谢该作者的4篇文章