admin管理员组

文章数量:1558696

主机组件标识了因特网上能够访问资源的宿主机器,比如 www.xxx192.168.1.66

4. 端口号

端口组件标识了服务器正在监听的网络端口,对下层使用了 TCP 协议的 HTTP 来说,默认端口为 80

5. 查询路径

服务器上资源的本地名,由斜杠( / )将其与前面的 URL 组件分隔开来,路径组件的语法与服务器的方案有关。

路径组件说明了资源位于服务器的什么地方,类似于分级的文件系统路径,比如 /goods/details

6. 查询参数

比如数据库服务是可以通过提供查询参数缩小请求资源范围的,传入页码和页大小查询列表 http://www.xxx/?page=1&pageNum=20

7. 片段标识符

片段(fragment)表示一部分资源的名字,该字段不会发送给服务器,是在客户端内部使用的,通过井号(#)将其与 URL 其余部分分割开来。

1.1.2 首部字段 Headers

Header 用于存放 HTTP 首部,Headers 中只有一个 namesAndValues 字段,类型为 Array ,比如 addHeader(a, 1) 对应的 namesAndValues 为 [a, 1]

HTTP 协议的请求和响应报文中必定包含 HTTP 首部,首部内容为客户端和服务器分别处理请求和响应提供所需要的信息,HTTP 报文由方法、URI、HTTP 版本、HTTP 首部字段等部分构成。

1.1.3 请求体 RequestBody

RequestBody 是一个抽象类,有下面 3 个方法。

  1. 内容类型 contentType()

比如 application/x-www-form-urlencoded

  1. 内容长度 contentLength()

  2. 写入内容 writeTo()

把请求的内容写入到 okio 提供的 Sink 中;

RequestBody 中还有 4 个用于创建 RequestBody 的扩展方法 xxx.toRequestBody() ,比如 Map.toString().toRequestBody()。

1.1.4 标签

我们可以用 tag() 方法给请求加上标签,然后在拦截器中根据不同的标签栏做不同的操作。

val request = Request.Builder()
.url(…)
.tag(“666”)
.build()

在 Retrofit 中用的则是 @Tag 注解,比如下面这样。

@POST(“app/login”)
suspend fun login(
@Query(“account”) phone: String,
@Query(“password”) password: String,
@Tag tag: String
) : BaseResponse

然后在自定义拦截器中,就能根据 tag 的类型来获取标签。

override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val tag = request.tag(String::class.java)
Log.e(“intercept”, “tag: ${tag}”)
return chain.proceed(request)
}

2. OkHttp 请求分发机制

2.1 请求操作 Call

在我们创建请求 Request 后,要用 OkHttpClientnewCall() 方法创建一个 RealCall 对象,然后调用 execute() 发起同步请求或调用 enqueue() 发起异步请求。

RealCall 实现了 Call 接口,也是这个接口唯一的实现类,按注释来说,RealCall 是一个 OkHttp 应用与网络层之间的桥梁,该类暴露了高级应用层的原语(primitives):连接、请求、响应与流,你也可以把 RealCall 理解为同步请求操作,而 RealCall 的内部类 AsyncCall 则是异步请求操作

下面但是 RealCall 中比较中要的两个方法的实现:execute()enqueue()

1. 发起同步请求 execute()

当我们调用 RealCall 的 execute() 方法发起同步请求时,如果该请求已执行,那么会抛出非法状态异常,所以发起同步请求时要注意捕获异常

如果请求没有被执行的话,execute() 方法则会调用 AsyncTimeout 的 enter() 方法让 AsyncTimeout 做请求超时判断,AsyncTimeout 中有一个继承了 Thread 的内部类 WatchDog,而 AsyncTimeout 会用 Object.wait()/notify() 阻塞和唤醒 Watchdog 线程。

当请求超时时,AsyncTimeout 会调用 RealCall 中实现的 timeOut() 方法关闭连接。

RealCall 的 execute() 方法调用完 enter() 方法后,会调用 Dispatcher 的 executed() 把请求加入同步请求队列,然后调用 getResponseWithInterceptorChain() 方法获取响应,获取到响应后就会让 Dispatcher 把请求从同步请求队列中移除。

2. 发起异步请求 enqueue()

RealCall 的 execute() 方法会创建一个异步请求操作 AsyncCall,并把它交给 Dispatcher 处理。

AsyncCall 实现了 Runnable 接口,Dispatcher 接收到 AsyncCall 后,会把 AsyncCall 添加到待执行异步请求队列 readyAsyncCalls 中,然后调用自己的 promoteAndExecute() 方法。

把 AsyncCall 加入到异步请求队列后,Dispatcher 会看情况决定什么时候执行该异步请求,要执行的时候就会把请求任务提交到线程池 ExecutorService 中。

和同步请求一样,在 AsyncCall 的 run() 方法中做的第一件事情就是让 AsyncTimeout 进入超时判断逻辑,然后用拦截器链获取响应。

当请求的过程中没有遇到异常时,AsyncCall 的 run() 方法就会调用我们设定的 Callback 的 onResposne() 回调,如果遇到了异常,则会调用 onFailure() 方法。

不论异步请求是成功还是失败,RealCall 最后都会调用 Dispatcher 的 finished() 方法把请求从已运行异步请求队列 runningAsyncCalls 中移除。

2.2 请求分发器 Dispatcher

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sPFS1yfl-1649672141943)(https://s3.jpg.cm/2021/06/09/ILfs5O.png)]

请求分发器 Dispatcher 做的事情并不多,只是维护了三个队列和一个线程池,这三个队列分别是待执行异步请求队列运行中异步请求队列以及运行中同步请求队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OIClUhGr-1649672141944)(https://s3.jpg.cm/2021/06/09/ILf9VD.png)]

Dispatcher 的 enqueue() 方法首先会把 AsyncCall 加入到待执行请求队列,然后从待运行和已运行请求队列中找出与当前请求的主机地址相同的其他请求,找到的话就找到的请求的重用 AsyncCall 的 callsPerHost 字段,callsPerHost 表示当前请求的主机地址的已执行请求数量,每执行一个相同主机地址的请求时, callsPerHost 的值就会加 1 ,如果我们的应用中经常会发起多个请求,并且不会请求多个不同的主机地址的话,我们就可以修改 Dispatcher 中的 maxRequestsPerHost 的值,maxRequetsPerHost 表示单个主机地址在某一个时刻的并发请求的最大值,修改方式如下。

okHttpClient.dispatcher.maxRequestsPerHost = 10

maxRequestsPerHost 默认为 5 ,如果对应主机地址的请求数量没有超过最大值的话,Dispatcher 就会遍历待运行异步请求队列,在遍历时,Dispatcher 会判断已运行的异步请求数量是否超出了允许的并发请求的最大值 maxRequests ,这个值默认为 64 ,也是可以被修改的,当异步请求数量不超过最大值,并且对应主机地址的请求数量不超过最大值时,就会把待运行请求提交到线程池中执行

当同步请求或异步请求执行时,RealCall 就会调用getResponseWithInterceptorChain() 方法发起请求,在 getResponseWithInterceptorChain() 方法中,首先会创建一个 interceptors 列表,然后按下面的顺序添加拦截器

本文标签: 原理OkHttp