OkHttp设计思想

OkHttp源码分析(一)

  • 最近在公司进行了一次技术分享
  • 本篇文章按照分享会PPT的顺序分析

一、简要介绍

  1. OkHttp是一个处理网络请求的开源项目
  2. Android 当前最火热网络框架,由 Square 公司贡献
  3. 替代 HttpUrlConnection 和 HttpClient(6.0里已移除HttpClient)
  4. Retrofit 的底层实现
  5. 本次源码基于okhttp 3.11.0版本进行分析

1. 主要的类

1.1 OkHttpClient(外观类)
  • OkHttp 用到了 外观模式OkHttpClient 就是整个框架的外观类
  • 开发者们直接操作的就是这个类,通过这个类,可以跟框架的子系统进行交互。如:可以通过 addInterceptor 添加自定义的拦截器(这便是与拦截器子系统进行交互)
  • 为何用到外观模式? 因为 OkHttp 是一个很复杂的框架,需要将其内部做的操作都隐藏起来,减少用户的使用成本。
1.2 Request(请求数据类)
  • 是一个 请求数据 的封装类(封装了请求头请求地址等等)
1.3 Dispatcher(分发、调度器)
  • 用来管理所有的请求
1.4 RealCall(同步请求执行者)
  • 可以看做是一个 同步请求
1.5 AsyncCall(异步请求执行者)
  • 可以看做是一个 异步请求

二、okhttp同步请求方法

image

  1. 可以看到,操作很简单:
    • 先创建一个 请求数据类
    • 再创建 外观类
    • 接着生成 同步请求类RealCall)向上转型为 Call 接口
    • 最后 执行 同步请求
  2. 这里的 数据请求类 以及 外观类,使用了 构造者模式
    • 这里 外观类 直接通过构造函数初始化,其实与 new OkHttpClient.Builder().build() 做的事情是一样的
    • 为什么要使用构造者模式呢?
    • 因为它们需要的参数满足这两点中的一点:
      • 1.参数多且杂2.参数不是必须要传入的
    • 值得注意的一点是:数据请求类url 是必须传的,会在 build() 方法里检查,如果 为空会报异常

三、okhttp异步请求方法

image

  • 前几步与同步请求操作是一样的,唯一的不同在于第四步调用的是enqueue方法

四、okhttp框架流程分析

1.OkHttp整体流程图

image

  1. 整体可以粗略分为三部分
    1. 生成请求(封装生成Call):将外观类(OkHttpClient)和数据请求类(Request)封装生成同步请求类(RealCall):
      • 若是同步请求,则直接调用的同步请求类(RealCall)同步(execute)方法;
      • 若是异步请求,则封装成异步请求类(RealCall的内部类AsyncCall),再调用它的异步(enqueue)方法。
    2. 管理请求(Dispatcher调度请求):把生成的请求扔给调度器(Dispatcher)进行管理
    3. 执行请求(拦截器):也就是图中Dispatcher往下的部分:
      • 这个部分是 OkHttp 的核心,使用了 分层设计 + 链式调用 思想,使用了责任链模式
      1. 为何这部分要用责任链模式呢?
      2. 因为网络请求操作是一个很复杂的操作(我们必须考虑多种情况,如重定向、Socket连接、TLS安全、Cache复用、连接池复用等等)
      3. 为了使复杂操作的逻辑相对清晰,OkHttp采用了分层设计的思想,使用多层拦截器,每个拦截器解决一个问题。
      
      • 这部分最后的拦截器(CallServerInterceptors)才 真正的进行了网络请求 并获取返回数据

2.OkHttp 类图

image

  1. 可以看到,异步请求类(AyncCall)继承了一个 Runnable ,当 AsyncCall 被 线程池拿去执行时,最终会走到它的execute()方法
    • 为什么呢?
    • 因为 线程池 拿去执行时,会走到它父类的run()方法里,这个run()方法里执行的是抽象方法execute(),这个抽象方法最终会交由其子类进行实现(也就是AsyncCall)
  2. 外观类(OkHttpClient)维护了一个调度器(Dipatcher),这个调度器维护了一个 线程池(ExecutorService)。
    • 得出结论:平常使用时最好维护一个全局的 OkHttpClient (因为我们只需要一个线程池)

五、okhttp同步请求流程和源码分析

1. 同步请求流程图

image

  1. 与整体流程图是一样的,先 生成请求(RealCall),然后 管理请求(扔给 Dispatcher 维护的 同步请求队列),最后 执行请求(getResponseWithInterceptorChain,也就是一系列 拦截器
  2. 需要注意的一点是,网络请求失败是一个很常见的结果,如何保证失败后,网络请求能一定从同步请求队列移除而不被阻塞呢?
    • OkHttp 使用了 try,finally ,在 finally 里进行 网络请求的移除,确保不被阻塞

2. 同步请求源码

  • RealCall

image

  • 可以看到源码里对上面两点的体现

六、okhttp异步请求流程和源码分析

1. 异步请求流程图

image

  1. 可以看到,一开始的部分与同步请求一样,直到Dispatcher。是封装成了一个 异步请求类(AsyncCall)再扔给 Dispatcher 管理调度

  2. 扔给 Dispatcher 管理时,要考虑是扔给 正在执行的异步请求队列,还是扔给 准备执行的异步请求队列

    • 默认的规则是:当 正在执行的异步请求队列的请求数 不超过 最大请求数(64)。并且 正在执行的相同主机的请求 不超过 最大主机数(5) -> 则表示可以马上执行,就扔给 正在执行的队列、否则给 准备执行的队列
    1. 这里为何要限制 “最大主机数” 呢?
    2. 当 “相同主机的某个请求失败了” ,“则同一个主机的其他请求” 也极有可能失败
    3. 限制 “最大主机数” 可以避免全都 “卡死在某个Host” 上
    
  3. 扔给队列进行管理之后,就使用 线程池 去执行 异步请求(AsyncCall),之前说过,AsyncCall 是一个 Runnable,线程池执行它最终会跑到 AsyncCallexecute方法

  4. AsyncCallexecute方法 里最终会通过 回调 把数据返回出去

    • 重点:这就是为什么,我们平常使用OkHttp时,在获取数据后如果要更新UI,需要把数据传到UI线程(因为回调是在线程池中执行的)

2. 异步请求源码

  • Dispatcher
    image
  • AsyncCall
    image

七、okhttp任务调度核心类dispatcher解析

1. 简介

  1. 用来管理分发请求
  2. 维护了管理请求的几个队列
  3. 维护了一个线程池,给异步请求使用
  4. 维护了管理请求的方法:生成异步、同步请求,取消请求、请求结束

2. 主要成员变量

private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//准备执行的异步请求队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//正在执行的异步请求队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//正在执行的同步请求队列
private ExecutorService executorService;//线程池

3. Dispatcher类图

image

  • 可以看到,既然是管理请求的,那肯定有管理的方法(生成、取消等等),如类图里的方法

4. Dispatcher的理解

  1. 意义同步请求和异步请求都为耗时操作,是个需要观测的行为(请求结束需要处理、请求有时要取消,这些都要管理起来)
  2. 只负责分发调度请求,而不参与网络请求
    • 真正执行请求的是拦截器
  3. 采用了 “生产者消费者模型” 进行请求的管理(队列+生产者+消费者
    • 这是为了解决 生产者的生产能力消费者的消费能力 不平衡 的问题
    • 生产者 可以看做是 dispatcher同步或异步请求方法(execute 或 enqueue)
    • 消费者 可以看做是 dispatcherfinished 方法
    • 缓冲区 则是 dispatcher 维护的 队列

4. 同步请求 finished 的流程图 及 源代码

image
image

  • 通过流程图可以看出,executed 方法只是加入到了队列,finished 时只是从队列移除,同步的相对简单

5. 异步请求 finished 的流程图 及 源代码

image
image
image

  1. 可以看出,同步与异步的 finished 最后走到同一个方法,区别是传进来的 参数不同,异步 promoteCalls 参数为 true,因此会执行 promoteCalls() 方法
  2. promoteCalls() 方法,其实就是 流程图 中最右边标注的 “把准备中的所有call移到运行时队列,直至队列满”
  3. 其实也就是比 同步的finished 多了一步:准备执行队列正在执行队列 的交互

八、能学到什么?

1. 构造者模式模板

  • 平常如果需要写构造者模式,可以打开OkHttpClient的源码,参照它的写法

2. 外观模式

  • 很多开源库的都用到了这个模式,可以隐藏子系统的复杂操作,减少用户使用成本

3. try、finally避免队列阻塞

  • 不止是网络请求,而是只要有想要保证一段代码一定执行,就可以使用try,finally

4. 限制最大主机数

  • 平常限制最大请求数是我们常做的事,但是 OkHttp 提供了一个思路就是 限制最大主机数,防止卡死在某一个 host 上

5. 生产者、消费者模型

6. 责任链模式

7. 最好构建一个全局的Client(每个Client都维护了一个线程池)

  • 使用时应该对 OkHttpCilent 使用单例

8. OkHttp整体流程

  • 了解OkHttp的整体流程

9. 调度器思想

  • 平常开发中,也可以写一个调度器来管理一些东西,只要维护好它的几个管理方法
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值