okhttp builder_Okhttp请求流程梳理

最近在看 Okhttp 的源码。不得不说源码设计的很巧妙,从中能学到很多。其实网上关于 Okhttp 的文章已经很多了,自己也看了很多。但是俗话说得好,好记性不如烂笔头,当你动手的时候,你会发现你在看的时候没有注意到的很多细节。

本次要分析的 Okhttp 版本是 3.8.1,在 gradle 中引用如下:

之所以选择分析3.8.1,是因为最新版是采用 Kotlin 写的,因为本人 Kotlin 实力不允许,所以只能分析 Java 版本。

使用示例

1、发起一个异步 GET 请求,代码具体如下:

2、发起一个同步 GET 请求,代码具体如下:

可以看到两个请求基本大同小异,总结下过程如下:

  • 先创建 OkHttpClient 实例;
  • 构造 Request 实例,传入 url 等相关参数;
  • 通过前两步中的实例对象构建 Call 对象;
  • 异步请求通过 Call#enqueue(Callback) 方法来提交异步请求,同步请求通过 Call#execute() 直接获取 Reponse ;

通过示例,大家简单了解 Okhttp 中的一些对象,下面开始梳理整个请求逻辑。先从 OkHttpClient 开始。

OkHttpClient

当我们发起请求的时候,需要先构造 okHttpClient 对象,代码具体如下:

可以发现是使用了 builder 建造者模式;来看看里面的内容

OkHttpClient 内部已经实现了 OkHttpClient(Builder builder),如果我们不需要配置 client,okhttp 已将帮我们默认实现了配置。总结起来主要有以下几点:

  • 里面包含了很多对象,其实 OKhttp 的很多功能模块都包装进这个类,让这个类单独提供对外的 API,这种外观模式的设计十分的优雅,叫做外观模式。
  • 而内部模块比较多,就使用了 Builder 模式(建造器模式),通常用于参数比较多情况。
  • 它的方法只有一个:newCall 返回一个 Call 对象(一个准备好了的可以执行和取消的请求)。

Request

接下来,我们看 Request 请求类,主要包含下面几个属性: url,请求方法名,请求头部,请求体,从属性就可以判断出 Request 主要作用。

Call

HTTP请求任务封装。可以说我们能用到的操纵基本上都定义在这个接口里面了,所以也可以说这个类是 Okhttp 类的核心类了。我们可以通过 Call 对象来操作请求了。而 Call 接口内部提供了 Factory 工厂方法模式 (将对象的创建延迟到工厂类的子类去进行,从而实现动态配置),下面是 Call 接口的具体内容:

RealCall

RealCall 继承自 Call,是真正发起请求的的实体类。RealCall 主要方法:

  • 同步请求 :client.newCall(request).execute();
  • 异步请求: client.newCall(request).enqueue();

下面我们来看看里面具体的内容:

可以发现,其内部持有了 client,原始请求,以及请求事件回调 Listener 等。我们看下请求的回调 Listener 的具体内容:

可以看到 OkHttp 的回调做得非常细致,有各种各样的回调,不管你想不想用,都帮你考虑到了呢。这样我们可以监听具体的回调,然后做一些操作。

接下去就要开始讲异步请求的具体步骤呢。先从异步请求讲起,这也是我们最常用的。

可以看到上述代码做了几件事:

  • synchronized (this) 确保每个call只能被执行一次不能重复执行,如果想要完全相同的 call,可以调用如下方法:进行克隆
  • 利用 dispatcher 调度器,来进行实际的执行 client.dispatcher().enqueue(new AsyncCall(responseCallback)),在上面的 OkHttpClient.Builder 可以看出已经初始化了 Dispatcher。

心细的读者可能发现一个问题了,那就是这里 enqueue 明明是一个封装了 responseCallback 的 AsyncCall ,怎么就会变成加入队列执行请求了呢?这个下面我会进行解释。

Dispatcher

Dispatcher 是 Okhttp 的调度器,用来管理控制请求的队列。内部通过线程池来确保队列的有序运行。先看下 enqueue 方法的具体内容:

可以看到内部存在两个队列,一个是正在运行的队列 runningAsyncCalls,另一个是 readyAsyncCalls 队列。如果当前运行数小于最大运行数,并且当前请求的host小于最大请求个数,那么就会直接加入运行队列,并运行。如果超了,就会加入准备队列。

实际上还有一个同步队列,没有给同步队列做限制,只要一加入就开始执行请求。

当请求队列完成请求后需要进行移除,看下 finished 的代码逻辑:

可以看到,这是使用了泛型,不用关心具体传入的队列是哪一个,直接就可以移除。promoteCalls 为 true 代表是异步请求队列,还得从 readyAsyncCalls 队列里面取出一个队列添加到 runningAsyncCalls 队列里面去执行请求。

通过上述代码,关于调度器的功能作用就基本理清了。

AsyncCall

AsyncCall 是 RealCall 里面的内部类,继承自 NamedRunnable,是自定义的Runnable,可以为线程设置 name。内部代码具体如下:

可以发现,在 run 方法内部调用了execute 方法,这个方法就是真正的发起请求的逻辑。下面我们看下 AsyncCall 中的该方法得具体内容:

获取响应数据最终是是通过 getResponseWithInterceptorChain() 来获取的。然后通过回调将 Response 返回给用户。

值得注意的 finally 执行了client.dispatcher().finished(this) 通过调度器移除队列。移除得逻辑在前面也已经讲过了。

下面看下 getResponseWithInterceptorChain 方法内部的具体逻辑:

从上述代码中,可以看出都实现了 Interceptor 接口,这是 Okhttp 最核心的部分,采用责任链的模式来使每个功能分开,每个 Interceptor 自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。

RealInterceptorChain

那责任链是怎么实现的呢?下面具体分析下相关逻辑:

首先是看构造函数,内部持有了当前责任链的所有拦截器list,还包括 RealConnection,index (当前正在处理拦截器索引)等。

接下去看 proceed 方法里的逻辑,归来起来就是如下:

  1. 首先是通过 index 和 calls 来做了一些安全判断,避免重复处理,。
  2. 将索引号 index +1,新创建一个 chain。
  3. 根据目前的 index 获取拦截器,然后将新的 chain 传入到获取拦截器中。
  4. 拦截器做完自己的操作后,会调用新创建的 chain 的 proceed 方法,交由下一个拦截器来处理。
  5. 当数据返回后,从后往前,拦截器会依次对数据做一些处理,最终用户获得请求的数据。

通过上述往复循环,最终所有的拦截器都会走两遍,一次是对请求体做操作,一次是对返回体做操作,最终用户获得处理后的数据。

下面来看一个具体的拦截器 。

CacheInterceptor

CacheInterceptor 代码比较长,我们一步一步的来进行分析。

首先我们先分析上部分代码当没有网络的情况下是如何处理获取缓存的。

上述的代码,主要做了几件事:

  1. 如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的 Response,否则 cacheCandidate = null,同时从 CacheStrategy 获取cacheResponse 和 networkRequest;
  2. 如果 cacheCandidate != null 而 cacheResponse == null 说明缓存无效清楚 cacheCandidate 缓存。
  3. 如果 networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。
  4. 如果 networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。

上部分代码,其实就是没有网络的时候的处理。那么下部分代码肯定是,有网络的时候处理:

上面的代码主要做了这几件事:

  1. 执行下一个拦截器,也就是请求网络
  2. 责任链执行完毕后,会返回最终响应数据,如果缓存存在更新缓存,如果缓存不存在加入到缓存中去。

这就跟前面讲的对应上了,请求前做一些处理,比如判断缓存是否存在,网络是否可用等操作;数据回来之后,更新缓存,在传给上一个拦截器去做处理。

这样就体现出了责任链的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。这也是okhttp设计的最优雅最核心的功能。

到这里,异步请求逻辑基本就梳理完了。

同步请求

同步请求会直接调用 Call#ececute 方法,记住这个 execute 方法的返回实体是 Reponse,所以它直接返回了请求。

主要做了几件事:

  1. synchronized (this) 避免重复执行,上面的文章部分有讲。
  2. client.dispatcher().executed(this),实际上调度器只是将 call 加入到了同步执行队列中。
  3. getResponseWithInterceptorChain() 最核心的代码,相当于同步请求直接就开始运行,请求网络得到响应数据,返回给用户
  4. client.dispatcher().finished(this); 执行调度器的完成方法 移除队列

可以看出,在同步请求的方法中,涉及到 dispatcher 只是告知了执行状态,开始执行了(调用 executed),执行完毕了(调用 finished)其他的并没有涉及到。dispatcher 更多的是服务异步请求。

以上就是对 Okhttp 请求流程的梳理,后面附一张盗的流程图

f43ab849cf76f0c49fd7b612142e38bf.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值