OKHttp 解析

Python025

OKHttp 解析,第1张

分析网络访问框架的三个步骤,三个主线:

1、请求发送到哪去了?

call.enqueue()------equeue()

访问统一目标机器请求个数值,默认为5

<  5 时 加入运行状态队列 ,否则加入等待状态队列

2、请求是被谁处理了?

executorService 线程池,核心线程是0,最大数是maxValue,线程池内部维护等待队列,OKHTTP中是一个无容量队列,相当于来到请求就必须处理,没有线程时就会创建,所以就会是maxValue。

然后就到AsyncCall类,调用里边的execute()方法,这是真正处理请求的地方,然后走到getResponsewithInterceptorChain()方法,里边是一个责任链模式,

责任链模式:有一条链子,链子上有多个节点,每个节点都有成功或失败两个结果,这样做的好处是,我们在访问服务器的时候,可以在里边进行过滤,拦截无效请求。

重试拦截器、桥拦截器、缓存拦截器、连接拦截器、网络拦截器、。。。

经过拦截过滤后,才会发送到服务器的请求。

3、请求是怎么被维护的?

client.dispatch

HTTP请求开始:

client=new   OkHttpClient()

Request   request=new   Request.Builder().url("your url").build()

//同步发起请求

Response    syncResponse=client.newCall(request).execute()

//异步发起请求

client.newCall(request).enqueue(newCallback(){

@Override

public   void   onFailure(@NotNullCallcall,@NotNullIOExceptione){

}

@Override

public   void   onResponse(@NotNullCallcall,@NotNullResponseresponse)throwsIOException{

}

}

)

调用过程:

new OKHTTPClient()-----》client.newCall(request)----->execute()同步请求------->RealCall#getResponseWithInterceptorChain( )--------->RealInterceptorChain#proceed(request)

new OKHTTPClient()-----》client.newCall(request)----->enqueue()异步请求-----》Dispatcher.equeue( )---

---->Dispatcher#promoteAndExecute( )------>executeOn( )----->run( )----------------------------------------------------------------->RealCall#getResponseWithInterceptorChain( )--------->RealInterceptorChain#proceed(request)

OKHTTP中有五种拦截器   :缓存拦截、网络连接拦截、

自定义拦截器的三个步骤:

chain.request()

chain.proceed()

return response

OKHTTP可以配置x509trustManager,设置TLS信任证书:

这块有一个坑,就是必须先要接受系统的证书,然后接受本地证书。否则访问一些链接会出现异常。

OKHTTP的请求队列使用的是SynchronousQueue,这个队列的特点是容量为0,来到请求就马上处理

面试题1:OKHTTP中的责任链是怎样串联起来的?请求回来的数据进行怎样线程切换?

答:串联方式,通过将各种拦截器加入一个集合中,创建一个RealInterceptorChain对象,传入这个拦截器集合,传入一个index为0,他本质上是一个Chain子类,会内部回调proceed()回调方法,在proceed()方法中,循环创建RealInterceptorChain对象,index每次加1,每次取出一个拦截器,拦截器调用回调方法intercept(),内部进行拦截执行,完成自己能干的事,然后返回一个response,经过这种方式将各个拦截器的执行串联起来。

异步方式会调用enqueue()方法,里边client.dispatcher().enqueue(new AsyncCall(responseCallback))这个AsyncCall实质上是一个Runable,在Runable的run方法中,调用execute(),他是一个待实现的方法,在execute()的实现方法中,调用getResponseWithInterceptorChain(),里边就是对于请求的拦截,通过回调将结果返回给onResponse(),注意,这里的返回结果,OKHTTP并没有给我们切回主线程。接下来的操作就是由我们自己完成了,可以使用handler完成接下来的操作、或者runOnUIThread()切回主线程。

由子线程得到结果后,想要在主线程更新UI,可以使用三种方式:

(1)runOnUiThread():内部实际上是做了线程判断,如果是主线程,那么直接执行,否则的话,会利用Activity中的默认创建的handler来执行,会调用handler.post(),而这个post方法也会调用sendMessage()

(2)handler.post(new Runnable{}),使用handler方法切回主线程时,注意handler的实例化要放在主线程中,而不能在新开的子线程中,否则报错,这是因为,Handler在哪里创建,就获得哪里的Looper。主线程创建的Handler,即默认使用主线程的Looper。即能否更新UI主要是看Looper是否在主线程。因为Looper是在创建它的线程执行的。

handler.post()----->sendMessageDelayed(getPostMessage(r), 0)本质上和sendMessage()一样,都是发送一条消息。

(3)view.post(new Runnable{}),回判断view的attachInfo字段是否为空,如果不为空,则获取attachInfo中的Handler对象,然后调用handler的post方法来进行线程切换。

面试题2:Handler sendMessageDelay()  是怎样执行的?

sendMessageDelay()方法,会在当前时间加上延时时间,然后在MessageQueue类的enqueueMessage()方法中,将这个时间赋值给msg.when ,代表消息执行时间,然后轮询器Looper调用loop循环的时候,循环取出消息队列中的msg,然后调用messageQueue的next()方法取下一条消息。方法内部会用当前时间与msg.when对比,如果小于,说明未到执行时间,则拿到两者的差值,下一次循环的时候调用nativePollOnce()方法,Linux内部会对各个消息重排序。等到执行时间到了之后,将消息返回给Looper。

面试题3:Glide缓存,该怎样存?

面试题4:Retrofit实现机制,数据回来之后的线程切换?

Retrofit内部使用动态代理、注解、反射来实现接口的封装,使用okhttp实现实现请求操作,实际上,在Retrofit创建过程中,在build方法里,

if (callbackExecutor ==null) {

callbackExecutor =platform.defaultCallbackExecutor()

}

而这个platform.defaultCallbackExecutor()调用了,

static final class Androidextends Platform {

Android() {

super(Build.VERSION.SDK_INT >=24)

  }

@Override

  public ExecutordefaultCallbackExecutor() {

return new MainThreadExecutor()

  }

也就是创建了MainThreadExecutor()对象,

static final class MainThreadExecutor   implements Executor {

private final Handlerhandler =new Handler(Looper.getMainLooper())

  @Override

  public void execute(Runnable r) {

handler.post(r)

  }

}

MainThreadExecutor   是一个静态内部终类,实际上创建了一个Handler,,里边传入的是主线程的Looper,前面创建的callbackExecutor,就是实现请求数据返回的接口类,从而就可以实现将请求数据通过handler传回主线程。

面试题5:HTTPS   中的TLS属于哪层协议?

答:属于应用层与传输层之间的协议,

Okhttp底层是通过java的socket请求与接受响应,(因为HTTP就是基于TCP的),但是okhttp实现了连接池的概念,即对于同一主机的多个请求,其实可以共用一个socket连接,而不是每次发送完HTTP请求就关闭底层socket,这样就实现了连接池的概念,而okhttp对于socket的读写操作使用的OKIO库进行的一层封装。

okhttp是square公司贡献的一个处理网络请求的开源框架,是目前Android开发使用最广泛的一个网络框架,从Android4.4开始,httpURLconnection的底层实现采用的就是okhttp。内部实现就是利用java基础,对socket进行封装,实现http通信。最重要的两个关键点就是分发器和5个拦截器。

分发器 就是内部维护队列和线程池,完成请求分配,总结就是用于对异步任务加入队列管理,然后判断条件,控制数量,加入线程池执行异步请求任务。

五个默认拦截器 就是利用责任链模式对网络请求进行层层处理,完成整个请求过程,简单总结如下。

1.桥接拦截器对用户发出的请求添加缺少的请求配置字段,比如keep-alive等

2.缓存拦截器就是查询有没有符合判断条件的已缓存的网络请求,执行复用,直接返回response

3.连接拦截器就是创建请求,加入连接器 或者访问连接池,根据条件判断,是否能怼已创建的tcp请求进行复用

4.请求服务器拦截器就是对scoket进行操作,请求网络访问服务器,返回response,

5.重试和重定向拦截器就是对返回的response进行code判断,决定是否要重试或者重定向操作。

1.支持http2.0版本,并且允许对同一主机的所有请求共享一个套接字

2.即使不是http2.0版本,通过连接池,减少请求延迟

3.默认使用Gzip 压缩数据

4.响应缓存,避免重复请求网络

最简单的http请求案例

1.利用建造者模式构建okHttpClient实例对象,构建过程中可以动态配置参数,请求时间,响应时间,缓存信息等。

2.创建Request对象,设置请求方式,链接地址,参数等信息。

3.把request对象,传给client,通过newCall函数,得到RealCall对象。

4.RealCall 分为同步和异步执行

5.同步执行时,分发器只是做个记录,把请求任务加到队列中,然后直接通过拦截器访问服务器,返回response。

6.异步执行

6.1先对异步任务进一步封装,把任务放到AsyncCall对象中

2.分发器 把 封装后的异步任务 添加到等待运行的队列中

7.getResponseWithInterceptorChain 通过拦截器,获取response

okhttp 默认提供5个拦截器 重试重定向拦截器,桥接拦截器,缓存拦截器,连接拦截器,访问服务器拦截器。还可以自定义拦截器。

自定义拦截器分为应用拦截器(通过addInterceptor 添加)和网络拦截器(通过addNetworkInterceptor拦截)

拦截器采用责任链的设计默认,让请求者和处理者解耦,最终请求从前往后,响应从后往前。

首先先判断用户是否取消了请求,如果没有取消,就把请求交个桥接拦截器。

在获得响应结果response的时候根据响应码,判断是否需要重试或者重定向, 重试不限制次数,重定向最多20次 ,如果需要重试或者重定向,那么会再一次重新执行所有拦截器。

有如下几种情况不会重试:IO异常,线路异常,配置client实例时配置不允许重试,协议异常,证书异常等等。

先获取用户发送的请求,判断条件用户是否已经配置过请求头字段,若用户没有配置,则将http协议必备的请求头字段补齐,比如Content-Type,Content-Length等,然后交给下一个拦截器。

在获得响应结果response之后,调用保存cookie的接口(也可以在配置client的时候,设置cookjar进行cookie回调数据),并且解析gzip数据

获取结果之后,对cookie进行保存,对返回的数据进行gzip解压

就是根据缓存策略从缓存中查找是否有合适的缓存response,如果有合适的缓存,直接返回给请求任务,不在继续执行后面的拦截器。

获得响应结果response后,根据条件判断,决定是否要缓存。

维护一个连接池,负责对连接的服务。在把请求交给下一个拦截器之前。会先在连接池中找到一个合适的连接(满足适配条件相同,并且没有正在被使用)或者新建一个连接,并且接入连接池,获得对应的socket流,把请求交给下一个拦截器。获得response结果后不会进行额外的处理。

连接池, 也称之为对象池,主要用来存放request请求连接,内部维护了一个LinkedQueue队列用来存放请求。在添加新的请求对象时,都会执行一个周期性任务,用以对连接池进行清理操作。

1.队列长度超过5,清理最近未被使用连接,LRE算法

2.存储的连接,5分钟未被复用,清理

拿到上一个拦截器返回的请求,真正的与服务器进行通信,向服务器发送数据,解析读取响应的数据,返回给上一个拦截器。

1.创建request =>OkHttpClient=>RealCall()

2.同步执行 ,分发器添加同步任务,执行拦截器,访问服务器,返回reponse,触发异步分发流程。

3.异步执行 ,封装任务= >AsyncCall ,实现runnable接口。添加任务到异步任务等待队列,执行分发任务,判断异步任务是否能加入正在执行的异步任务队列,满足两个条件

同时执行的异步任务数量不得大于64个

对同一个主机的访问任务,最多不得大于5个

4.加入正在执行的异步任务队列,通过线程池执行任务,经过5个默认拦截器访问服务器,返回response,执行异步任务分发。

分发器工作 分为同步任务和异步任务两种

同步任务 就是把任务加入同步任务队列,加个标记,执行结束之后,触发异步任务的分发操作。

异步任务 先封装任务到asyncCall对象,实现了runnable接口。把任务加入等待执行队列,执行分发操作。

先遍历等待任务队列,判断是否符合加入正在运行的异步任务队列,要同时满足两个条件。

同时执行的异步任务数量不得大于64个

对同一个主机的访问任务,最多不得大于5个

当满足条件后,从等待队列中删除任务,把任务加入正在执行的队列中,通过自定义的线程池,执行任务,任务执行结束后,再次执行分发操作。

拦截器采用了责任链设计默认,让请求者和执行者解耦,请求者只需要将请求发给责任链即可,无需关心请求过程和细节。okHttp 默认有5个拦截器,重试重定向拦截器,桥接拦截器,缓存拦截器,连接拦截器,请求服务拦截器。工作细节参考上面拦截器原理分析部分

1.位置的关系,应用拦截器 放在责任链最顶端,网络拦截器放在责任链倒数第二的位置。所以应用拦截器 最先拦截,最后响应,网络拦截器 倒数第二拦截,第二响应。如果打印请求日志的情况,应用拦截器打印的是用户请求信息,经过重试重定向,桥接,缓存,链接 等拦截器的层层包装,网络拦截器打印的是实际请求的信息。

2.应用拦截器一定会被执行,网络拦截器不一定被执行。

利用连接池,缓存所有的有效连接对象。

清理机制:垃圾连接

1.超过5分钟没有用过的链接

2.超过5个闲置链接后,从最久闲置的链接开始执行清理(LRU)

以 3.14.9版本为例,这应该是最后一个java版本了,后面的版本都是kotlin开发的。

implementation 'com.squareup.okhttp3:okhttp:3.14.9'

一个完整的url可以包含哪些内容?

看一下这个类的构造方法

前面几个数据拆解了url中的不同部分,包括:协议、用户名、密码、域名、端口、路径、参数、锚位。

builder.toString() 将各个部分组合为一个整体,如下:

http://username:[email protected]:80/path1/path2?Key1=value1&key2=value2#anchor

域名查询服务,通过域名获取主机IP地址。

InetAddress 表示一个IP地址,这个类没有开放的构造方法,必须使用getXX静态方法创建。

套接字工厂,SSL套接字工厂, java SDK 内容。

抢先验证和反应式验证

抢先验证:用http代理服务器实现https请求时,需要进行抢先验证。

反应式验证:服务端反馈401或407时,需要提交账号信息用于验证。

401: 用户没有[访问权限, 需要进行身份认证。

407:客户应首先通过代理服务器验证。

OkHttpClient 包含两个实例用于验证,分别为authenticator用于验证原始服务器账号、proxyAuthenticator 用于验证代理服务器账号。

这是个枚举类, 包含以下数据

http/1.0 默认情况下不使用持久套接字,明文请求,过时

http/1.1 包含持久连接,明文请求

spdy/3.1 OkHttp不再支持该协议,使用http2.0

h2 就是http2.0,支持请求头压缩、多路复用、服务推送。

h2_prior_knowledge 明文http2.0

quic 快速udp网络连接,okhttp不支持,但可以通过拦截器实现支持。

连接规定。

指定ssl的加密算法 - 或不指定加密算法。

加密算法

这个类里面只有一个算法的名称,没有具体的算法实现逻辑。

类会缓存算法名称对应的CipherSuite对象

TLS的版本号

代理服务器设置和代理服务器选择器,java SDK内容。

参考: https://blog.csdn.net/qq_33022345/article/details/53453585

锁定SSL证书。

给指定的域名提供固定的证书,那该域名只有使用这个固定证书时才能正常访问。如果服务端证书改变,将无法访问。

一个服务器地址的表示。

本文以上提到的所有类,都被用于Address类中。