1.1 介绍:
okhttp 是目前Android 使用最广泛的网络框架。从android4.4开始HttpUrlConnection的底层实现采用的是OkHttp。
1.2 调用流程
OkHttp请求过程中最少只需要接触OkHttpClient、Request、Call、Response,但是框架内部进行大量的逻辑处理。 所有的逻辑大部分集中在拦截器中,但是在进入拦截器之前还需要依靠分发器来调配请求任务。
分发器:内部维护队列与线程池,完成请求调配;
拦截器:五大默认拦截器完成整个请求过程。
源码分析 构造:OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback2() {}
网络请求是通过call,去执行 同步方法execute()或者异步方法enqueue(),但是call 只是一个接口类,具体的看其实现类。那么okHttpClient.newCall()到底new出了什么?
可以看到其实现类是RealCall,看excute具体实现
核心代码:client.dispatcher().executed(this);将实际操作抛向了dispatcher.
dispatcher中将call请求加入到了一个栈
单从名称就可以看到这个一个正在运行的同步栈。
那么dispatcher的异步方法要稍微复杂一点
首先会先进行判断,当前运行的异步队列是否超过最大限度,当前请求所在ip请求池个数是否超过限度。
如果不满足条件,该请求call 只能被加入到异步准备栈中。如果满足添加则会在线程池中进行执行。但是在线程池执行需要该类是实现了runnable,但是RealCall并没有继承runnable,那么在其内部找找看。
的确找到了其内部类,而且还有execute()方法
在这里我们才看到了具体获取response的地方了,同时也引出了Okhttp 另外一个非常重要的责任链,责任链中有一系列的拦截器来帮助我们得到相应。这里我们就已经结合源码把整体流程大致的梳理了一遍。拦截器的内容跟细节下面继续讲。
1.3 Keep-Alive
提高网络性能优化,很重要的一点就是降低延迟和提升响应速度。 通常我们在浏览器中发起请求的时候header部分往往是这样的
keep-alive就是浏览器和服务端之间保持长连接,这个连接是可以复用的。在HTTP1.1中是默认开启的。
1.4 连接复用
连接的复用为什么会提高性能呢? 通常我们在发起http请求的时候首先要完成tcp的三次握手,然后传输数据,最后再释放连接。三次握手的过程可以参考这里 TCP三次握手详解及释放连接过程 一次响应的过程
在高并发的请求连接情况下或者同个客户端多次频繁的请求操作,无限制的创建会导致性能低下。 如果使用keep-alive
1.5 连接池
ConnectionPool:连接池复用socket
excutor : 线程池,用来检测闲置socket并对其进行清理。 connections : connection缓存池。Deque是一个双端列表,支持在头尾插入元素,这里用作LIFO(后进先出)堆栈,多用于缓存数据。 routeDatabase :用来记录连接失败router
1.6 缓存操作
ConnectionPool提供对Deque<RealConnection>进行操作的方法分别为put、get、connectionBecameIdle、evictAll几个操作。分别对应放入连接、获取连接、移除连接、移除所有连接操作。
1.7 分发器:异步请求工作流程
Q: 如何决定将请求放入ready还是running?
A:这个问题在上面梳理流程的阶段已经说过了。如果当前正在请求数不小于64放入ready;如果小于64,但是已经存在同一域名主机的请求5个放入ready!
Q: 从ready移动running的条件是什么?
在前面realcall源码中可以找到当每个call请求结束后,将会去执行client.dispatcher().finished(this);
在finished()方法中可以看到,首先会在运行列表中讲刚刚执行完的call remove掉,然后执行了上面红框中的promotecalls()方法。而我们这题的答案也就在里面了。
Q: 分发器线程池的工作行为?
这题的答案其实也在上面,当前一个call请求结束后,将会讲准备栈的请求加入运行栈中,执行其请求,依次循环。无等待,最大并发。后面任务分发也会做更详细的介绍。
1.8 线程池
当一个任务通过execute(Runnable)方法添加到线程池时:
线程数量小于corePoolSize,新建线程(核心)来处理被添加的任务; 线程数量大于等于 corePoolSize,存在空闲线程,使用空闲线程执行新任务;
线程数量大于等于 corePoolSize,不存在空闲线程,新任务被添加到等待队列,添加成功则等待空闲线程,添加失败:线程数量小于maximumPoolSize,新建线程执行新任务;
线程数量等于maximumPoolSize,拒绝此任务。
2 HTTP协议重定向与缓存处理
2.1 HTTP重定向
重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置(如:网页重定向、域名的重定向、路由选择的变化也是对数据报文经由路径的一种重定向)
1、永久重定向 这种重定向操作是永久性的。它表示原 URL 不应再被使用,而应该优先选用新的 URL。搜索引擎机器人会在遇到该状态码时触发更新操作,在其索引库中修改与该资源相关的 URL 。
2、临时重定向 有时候请求的资源无法从其标准地址访问,但是却可以从另外的地方访问。在这种情况下可以使用临时重定向。搜索引擎不会记录该新的、临时的链接。在创建、更新或者删除资源的时候,临时重定向也可以用于显示临时性的进度页面。
3、特殊重定向 使页面跳转到本地缓存的版本当中,是一种手工重定向
2.2 缓存处理
缓存的优势
缓存的使用场景很多,通过它可以将数据通过一定的规则存储起来,再次请求数据的时候就可以快速从缓存中读取了,缓存有以下优势。
HTTP的缓存机制
1、强制缓存
2、对比缓存
3 高并发请求队列:任务分发
3.2 Dispatcher分发器
对于同步请求,分发器只记录请求,用于判断IdleRunnable是否需要执行 对于异步请求,向分发器中提交请求:在RealCall的enqueue()方法
3.3 原理
当一个任务通过execute(Runnable)方法添加到线程池时: 线程数量小于corePoolSize,新建线程(核心)来处理被添加的任务;
线程数量大于等于 corePoolSize,存在空闲线程,使用空闲线程执行新任务;
线程数量大于等于 corePoolSize,不存在空闲线程,新任务被添加到等待队列,添加成功则等待空闲线程,添加失败: 线程数量小于maximumPoolSize,新建线程执行新任务; 线程数量等于maximumPoolSize,拒绝此任务。
3.4 获得响应
在请求需要执行时,通过 getResponseWithInterceptorChain 获得请求的结果: Response
3.5 执行流程
4 责任链模式拦截器设计
拦截器vs责任链
1、重试拦截器在交出(交给下一个拦截器)之前,负责判断用户是否取消了请求;在获得了结果之后,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器。
2、桥接拦截器在交出之前,负责将HTTP协议必备的请求头加入其中(如:Host)并添加一些默认的行为(如:GZIP压缩);在获得了结果后,调用保存cookie接口并解析GZIP数据。
3、缓存拦截器顾名思义,交出之前读取并判断是否使用缓存;获得结果后判断是否缓存。
4、连接拦截器在交出之前,负责找到或者新建一个连接,并获得对应的socket流;在获得结果后不进行额外的处理。
5、请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的响应数据。
RealCall中有多种责任链