目录

1、okhttp工作的大致流程
2、okhttp中的连接
3、Dispatcher和线程池
4、OkHttp中的设计模式
5、OkHttp的优势
6、参考连接

1、okhttp工作的大致流程

1.1、整体流程

(1)、当我们通过OkhttpClient创建一个Call,并发起同步或异步请求时;
(2)、okhttp会通过Dispatcher对我们所有的RealCall(Call的具体实现类)进行统一管理,并通过execute()及enqueue()方法对同步或异步请求进行处理;
(3)、execute()及enqueue()这两个方法会最终调用RealCall中的getResponseWithInterceptorChain()方法,从拦截器链中获取返回结果;
(4)、拦截器链中,依次通过RetryAndFollowUpInterceptor(重定向拦截器)、BridgeInterceptor(桥接拦截器)、CacheInterceptor(缓存拦截器)、ConnectInterceptor(连接拦截器)、CallServerInterceptor(网络拦截器)对请求依次处理,与服务的建立连接后,获取返回数据,再经过上述拦截器依次处理后,最后将结果返回给调用方。
提供两张图便于理解和记忆:

okhttp整体流程1

okhttp整体流程2

这张图只画出了请求流程,没有数据返回流程,后期会处理。

1.2、各大拦截器的原理解析

1.2.1、RetryAndFollowUpInterceptor:负责重定向

构建一个StreamAllocation对象,然后调用下一个拦截器获取结果,从返回结果中获取重定向的request,如果重定向的request不为空的话,并且不超过重定向最大次数的话就进行重定向,否则返回结果。注意:这里是通过一个while(true)的循环完成下一轮的重定向请求。

(1)、StreamAllocation为什么在第一个拦截器中就进行创建?
       便于取消请求以及出错释放资源。
(2)、StreamAllocation的作用是什么?
       StreamAllocation负责统筹管理Connection、Stream、Call三个实体类,具体就是为一个Call(Realcall),寻找( findConnection() )一个Connection(RealConnection),获取一个Stream(HttpCode)。

1.2.2、BridgeInterceptor

负责将原始Requset转换给发送给服务端的Request以及将Response转化成对调用方友好的Response。
具体就是对request添加Content-Type、Content-Length、cookie、Connection、Host、Accept-Encoding等请求头以及对返回结果进行解压、保持cookie等。

1.2.3、CacheInterceptor

CacheInterceptor:负责读取缓存以及更新缓存。
在请求阶段:

  1. 读取候选缓存cacheCandidate;

  2. 根据originOequest和cacheresponse创建缓存策略CacheStrategy;

  3. 根据缓存策略,来决定是否使用网络或者使用缓存或者返回错误。
    具体的的缓存策略就是http的缓存策略,详见下图:
    在结果返回阶段:
    负责将网络结果进行缓存(使用于DiskLruCache)。

okhttp&http缓存策略

强制缓存:当客户端第一次请求数据是,服务端返回了缓存的过期时间(Expires与Cache-Control),没有过期就可以继续使用缓存,否则则不适用,无需再向服务端询问。

对比缓存:当客户端第一次请求数据时,服务端会将缓存标识(Etag/If-None-Match与Last-Modified/If-Modified-Since)与数据一起返回给客户端,客户端将两者都备份到缓存中 ,再次请求数据时,客户端将上次备份的缓存

标识发送给服务端,服务端根据缓存标识进行判断,如果返回304,则表示缓存可用,如果返回200,标识缓存不可用,使用最新返回的数据。

ETag是用资源标识码标识资源是否被修改,Last-Modified是用时间戳标识资源是否被修改。ETag优先级高于Last-Modified。

1.2.4、ConnectInterceptor:负责与服务器建立连接

使用StreamAllocation.newStream来和服务端建立连接,并返回输入输出流(HttpCodec),实际上是通过StreamAllocation中的findConnection寻找一个可用的Connection,然后调用Connection的connect方法,使用socket与服务端建立连接。

1.2.5、CallServerInterceptor:负责从服务器读取响应的数据

主要的工作就是把请求的Request写入到服务端,然后从服务端读取Response。
(1)、写入请求头
(2)、写入请求体
(3)、读取响应头
(4)、读取响应体

2、连接池原理

由于HTTP是基于TCP,TCP连接时需要经过三次握手,为了加快网络访问速度,我们可以Reuqst的header中将Connection设置为keepalive来复用连接。

Okhttp支持5个并发KeepAlive,默认链路生命为5分钟(链路空闲后,保持存活的时间),连接池有ConectionPool实现,对连接进行回收和管理。

2.1、连接池的清理

ConectionPool在内部使用一个异步线程来清理连接。
当连接池中有连接时:清理任务由cleanup()方法完成,首先执行清理,并返回下次需要清理的间隔时间,调用调用wait() 方法释放锁。等时间到了以后,再次进行清理,并返回下一次需要清理的时间间隔,再次进入wait,以此循环往复。

当连接池中没有连接时:cleanup()返回-1,跳出循环,下次有连接加进来时,再次开启线程进行循环清理。

之所以连接池线程可以跳出循环,是因为,他是子线程,
而looper选择一直阻塞是因为他是主线程,如果跳出,程序执行结束。

1、首先统计空闲连接数量;
2、然后通过for循环查找最长空闲时间的连接以及对应空闲时长;
3、然后判断这个最长空闲时间的连接是否超出最大空闲连接数或者或者超过最大空闲时间,满足其一则清除最长空闲的连接。如果不满足清理条件,则返回一个对应等待时间。

这个对应等待的时间又分二种情况:
1 有空闲连接:则返回:keepAliveDurationNs-longestIdleDurationNs;
2 没有空闲的连接,则返回:keepAliveDurationNs
注意:清除一个空闲连接后,会返回0,再次立即开始清理。

如何统计空闲连接呢?

StreamAllocation创建或者复用一个Connection后,会将自己添加到Connection的connection.allocations列表中,数据读取完毕之后,会将自己从Connection的connection.allocations中移除,所以判读一个Connection是否是空闲连接可以采用引用计数法,判断connection.allocations列表中是否有StreamAllocation,如果没有就是空闲连接,否则不是。

3、OkHttp中Dispatcher和线程池

3.1、OkHttp中线程池

public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));}return executorService;}

OkHttp中的线程池是一个 newCachedThreadPool。
所以在 OkHttp 中线程池只是一个辅助作用,仅仅是用来做线程缓存,便于复用的

真正控制请求并发数量和执行时机是通过调度器 Dispatcher 完成的。

3.2、OkHttp中Dispatcher

RealCall.execute

先将RealCall加入Dispatcher的runningSyncCalls队列,然后调用getResponseWithInterceptorChain获取Response,最后调用Dispatcher的finished方法,将自身从runningSyncCalls移除,然后进行轮询readyAsyncCalls队列,取出ready的异步任务在满足条件的情况下进行执行。

RealCall.enqueue

如果当前正在执行的RealCall的数量小于最大并发数maxRequest(64),并且该call对应的Host上的call小于同一host上的最大并发数maxRequestsPerHos(5),则将该call加入runningAsyncCalls,并将这个call放到线程池中进行执行,否则加入readyAsyncCall排队等待。

注意:

同步请求和异步请求执行完成之后,都会调用dispatcher的finished方法,将自身从对应的队列中移除,然后进行轮询readyAsyncCalls队列,取出ready的异步任务在满足条件下放到线程池中执行。

轮询readyAsyncCalls

Dispatcher.中的并发数量及三个队列的作用

maxRequests = 64   // 最大并发请求数为64
maxRequestsPerHost = 5 //每个主机最大请求数为5
ExecutorService executorService  //消费者池(也就是线程池)
Deque<AsyncCall> readyAsyncCalls:// 异步的缓存,正在准备被消费的(用数组实现,可自动扩容,无大小限制)
Deque<AsyncCall> runningAsyncCalls //正在运行的 异步的任务集合,仅仅是用来引用正在运行的任务以判断并发量,注意它并不是消费者缓存
Deque<RealCall> runningSyncCalls  //正在运行的,同步的任务集合。仅仅是用来引用正在运行的同步任务以判断并发量

4、OkHttp中的设计模式

责任链模式:拦截器链
单例模式:线程池
观察者模式:各种回调监听
策略模式:缓存策略
Builder模式:OkHttpClient的构建过程
外观模式:OkHttpClient封装了很对类对象
工厂模式:Socket的生产

5、OkHttp的优势

5.1、功能方面:

功能全面,满足了网络请求的大部分需求。

5.2、网络优化方面:

(1)内置连接池,支持连接复用
(2)支持gzip压缩响应体
(3)通过缓存避免重复的请求
(4)支持http2,对一台机器的所有请求共享同一个socket

5.3、扩展性方面:

拦截器模式使得我们很容易使得我们很容易添加一个自定义拦截器对请求和返回结果进行处理。

6、参考链接

6.1、https://www.jianshu.com/p/6166d28983a2
6.2、https://juejin.im/post/5a704ed05188255a8817f4c9#heading-15

作者:小红军storm
链接:https://www.jianshu.com/p/d7eced552553

关注我获取更多知识或者投稿

Android中okhttp原理详解相关推荐

  1. android中okhttp原理详解,Android中okhttp原理详解-极度针对面试篇

    一.okhttp工做的大体流程 1.一.总体流程 (1).当咱们经过OkhttpClient创立一个Call,并发起同步或者异步请求时: (2).okhttp会经过Dispatcher对咱们全部的Re ...

  2. Android 中malloc_debug 原理详解

    版本基于:Android R 关联博文: Android 中malloc_debug 使用详解 0. 前言 最近上项目中遇到一个native 可能内存泄漏的问题,曾考虑使用HWASAN,但这个工具是针 ...

  3. Android面试Hash原理详解二

    Hash系列目录 Android面试Hash原理详解一 Android面试Hash原理详解二 Android面试Hash常见算法 Android面试Hash算法案例 Android面试Hash原理详解 ...

  4. Android涂鸦画板原理详解——从初级到高级(二)

    前言 前面写了<Android涂鸦画板原理详解--从初级到高级(一)>,讲了涂鸦原理初级和中级的应用,现在讲解高级应用.如果没有看过前面一篇文章的同学,建议先去看看哈. 准备 高级涂鸦涉及 ...

  5. android调webview的方法,Android中的WebView详解

    Android中的WebView详解 WebView详解 基本用法 布局文件配置WebView android:id="@+id/wv_news_detail" android:l ...

  6. Android中mesure过程详解 (结合Android 4.0.4 最新源码)

    如何遍历并绘制View树?之前的文章Android中invalidate() 函数详解(结合Android 4.0.4 最新源码)中提到invalidate()最后会发起一个View树遍历的请求,并通 ...

  7. Android中layout过程详解 (结合Android 4.0.4 最新源码)

    上一篇文章Android中mesure过程详解 (结合Android 4.0.4 最新源码)介绍了View树的measure过程,相对与measure过程,本文介绍的layout过程要简单多了,正如l ...

  8. Android中shape属性详解

    一.简单使用 刚开始,就先不讲一堆标签的意义及用法,先简单看看shape标签怎么用. 1.新建shape文件 首先在res/drawable文件夹下,新建一个文件,命名为:shape_radius.x ...

  9. Android分包MultiDex原理详解

    MultiDex的产生背景 当Android系统安装一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt.DexOpt的执行过程是在第一次加载Dex文件的时候执行的 ...

最新文章

  1. 【矩阵运算c++实现】矩阵封装实现Matrix类
  2. 汇编指令 DUP指令
  3. Python使用远程仓库时建议忽略的文件
  4. 数据湖分析如何面向对象存储OSS进行优化?
  5. POJ - 3254 Corn Fields(状压dp)
  6. Python 类的特殊成员方法详解
  7. MATLAB读写mat文件
  8. 排序算法值--堆排序
  9. 谷歌更新TensorFlow目标检测API
  10. 机器人任务规划:从状态机到形式系统
  11. Android单选中listview中的一项
  12. 白色情人节为你身边的程序猿献上一份礼物!
  13. ASP.NET XML Web 服务的应用程序集成
  14. Cordova+Vue快速搭建Hybrid App
  15. vision画流程图的软件_vision软件怎么画流程图
  16. mysql in和like_mysql-一起使用SQL LIKE和IN
  17. 外挂、破解软件理论与实战
  18. Python爬取京东笔记本电脑,来看看那个牌子最棒!
  19. Java集合、IO、多线程的一些知识
  20. Elasticsearch中FST与前缀搜索

热门文章

  1. QSettings读写ini文件
  2. wp网站怎么搬家服务器,蓝色早晨-UED | WordPress搬家完整详细教程及换域名
  3. 基本凝聚层次聚类算法
  4. 计算机用通讯电压多少,电话线电压多少伏
  5. Solidworks 无法获得下列许可,许可证不一致的解决办法
  6. 九宫格抽奖源代码php,原生javascript实现九宫格抽奖效果代码
  7. copilot使用教程-全新版
  8. springboot项目搭建
  9. 盘点好用的3D渲染软件,哪个更适合你?
  10. 中国微电机行业需求规模与竞争格局研究报告2022版