解密okhttp

在看源码之前相信也对okhttp使用有一定的了解了,这里不再做okhttp使用的讲解。直接以一个请求为例了解okhttp到底是怎么的一个请求流程。在这里我们首先需要了解的的几个类:OkhttpClient、RealCall、Dispatcher、AyscCall(异步任务)、CallBack(回调接口)等。
以get请求为例


Paste_Image.png

1.OkHttpClient的newCall方法
首先我们来看client.newCall(),调用newCall方法创建了一个RealCall实例传入自身引用进去(此时RealCall方法持有okhttpClient引用)并调用RealCall的enqueue()方法,下面为enqueue代码:

Paste_Image.png

在该方法中调用了okhttpClient的dispatcher方法得到一个dispatcher对象,在OkthhpClient方法中Builder内部类中new出来的。再调用Dispatcher的enqueue()方法同时new一个AsyncCall(CallBack)异步任务类传入构造方法;

2.Dispatcher的enqueue方法

Paste_Image.png

其中Dispatcher包含了线程池和三个队列(readyAsyncCalls:保存等待执行的异步请求、runningAsyncCalls:保存正在运行的异步请求、runningSyncCalls:保存正在执行的同步请求)。如下:

Paste_Image.png

线程池执行该异步任务,会执行该任务AsyncCall的run方法,该AsyncCall是RealCall内部类并且是NamedRunnable子类有Callback responseCallback属性主要用于回调,而NamedRunnable实现了Runnable接口,该类主要是给线程设置name,并调用了抽象的execute方法。该抽象方法主要在AsyncCall中实现,从这开始都是在子线程中执行了。

3.AsyncCall的execute()方法

Paste_Image.png

重点在getResponseWithInterceptorChain();方法,先看Dispatcher的finished(this)方法。

4.finished()方法

Paste_Image.png

代码解释主要看代码中的注释,这里重点看下promoteCalls():

Paste_Image.png

之后重点看getResponseWithInterceptorChain();

5.getResponseWithInterceptorChain()方法

Paste_Image.png

这个方法在call.execute(同步请求)方法也调用到了,Okhttp真正发出网络请求,解析返回结果的就是该方法。

6.getResponseWithInterceptorChain()方法

Paste_Image.png

在这里Interceptors这个拦截器集合是如何调用的呢?主要看RealInterceptorChain 中的proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection)这个方法

Paste_Image.png

该方法通过内部新建RealInterceptorChain对象传入Interceptor的intercept方法,执行该方法。intercept方法中又会调用RealInterceptorChain方法达到循环取出Interceptor执行的目的。
几个拦截器
RetryAndFollowUpInterceptor重试拦截器重试与重定向拦截器,用来实现重试和重定向功能,内部通过while(true)死循环来进行重试获取Response(有重试上限,超时会抛异常)。followUpRequest主要用来根据响应码来判断属于哪种行为触发的重试和重定向(比如未授权,超时,重定向等),然后构建响应的Request进行下一次请求。当然,如果没有触发重新请求就会直接返回Response。

BridgeInterceptor桥接拦截器桥接拦截器,用于完善请求头,比如Content-Type、Content-Length、Host、Connection、Accept-Encoding、User-Agent等等,这些请求头不用用户一一设置,如果用户没有设置该库会检查并自动完善。另外,这里会进行加载和回调Cookie。

CacheInterceptor缓存拦截器缓存拦截器,首先根据Request中获取缓存的Response,然后根据用于设置的缓存策略来进行一步判断缓存的Response是否可用以及是否发送网络请求(CacheControl.FORCE_CACHE因为不会发送网络请求,所以networkRequest一定为空)。如果从网络中读取,此时再次根据缓存策略来决定是否缓存响应。

ConnectInterceptor连接拦截器连接拦截器,用于打开一个连接到远程服务器。也就是通过StreamAllocation获取HttpStream和RealConnection对象,以便后续读写。

Paste_Image.png

实际上建立连接就是创建了一个HttpCodec对象,它将在后面的步骤中被使用,是HTTP协议操作的抽象,有两个实现:Http1Codec、Http2Codec。分别是对HTTP/1.1 和HTTP/2版本的实现。在Http1Codec中,利用Okio对Socket的读写操作进行封装。创建HttpCodec的过程里面涉及了StreamAllocation、RealConnection等,概括就是找到一个可用的RealConnection,再利用RealConnection的输入输出(Buffered和BufferedSkin)创建HttpCodec对象供后续操作。
CallServerInterceptor服务拦截器
调用服务拦截器,通过HttpStream依次进行写请求头、请求体、读响应头、读响应体。

Paste_Image.png

这里核心工作都是由HttpCodec对象完成,而HttpCodec实际上是利用Okio,Okio还是用的Socket完成网络。这里对于okhttp一个请求的基本流程已经介绍完,后续会带来更精彩分析。

两张okhttp流程图贴上

Paste_Image.png
Paste_Image.png

现有两个需求解决一下:
需求一:
假设现在后台要求我们在请求 API 接口时,都在每一个接口的请求头上添加对应的 token 。使用 Retrofit 比较多的同学肯定会条件反射出以下代码:

@POST
Call<ResponseBody> login(@Header("token") String token,@Fields("mobile")String phoneNumber,@Fields("smsCode")String smsCode)

这样的写法自然可以,无非就是每次调用 login API 接口时都把 token 传进去而已。但是需要注意的是,假如现在有十多个 API 接口,每一个都需要传入 token ,难道我们去重复一遍又一遍吗?
相信有良知的程序员都会拒绝,因为这会导致代码的冗余。
那么有没有好的办法可以一劳永逸呢?答案是肯定的,那就要用到拦截器了。
需求二:
除了增加请求头之外,拦截器还可以改变请求体。
假设现在我们有如下需求:在上面的 login 接口基础上,后台要求我们传过去的请求参数是要按照一定规则经过加密的。
规则如下:
● 请求参数名统一为content;
● content值:JSON 格式的字符串经过 AES 加密后的内容;
举个例子,根据上面的 login 接口,现有
{"mobile":"157xxxxxxxx", "smsCode":"xxxxxx"}
JSON 字符串,然后再将其加密。最后以 content=[加密后的 JSON 字符串] 方式发送给后台。

推荐阅读更多精彩内容