Android 进阶之探索 OkHttp 原理

前言

1. OkHttp 请求处理流程概述

OkHttp 请求处理流程png

当我们发起同步请求时,请求会被 Dispatcher 放到同步请求队列中,然后直接执行请求。

当我们发起异步请求时,Dispatcher 会把请求放到异步请求队列,然后在合适的时机把异步请求提交到线程池中执行。

请求的执行由拦截器链负责,处理的顺序为:重试与重定向拦截器首部构建拦截器缓存拦截器连接拦截器数据交换拦截器

当数据交换拦截器 CallServerInterceptor 接收到请求时,会通过数据交换器 Exchange 写入请求信息,而 Exchange 会通过 Socket 提供的的输出流写入请求信息,通过输入流读取响应信息。

当 CallServerInterceptor 读取完了响应信息后,就会往上传递,直到把响应信息返回给最开始发起请求的地方。

2. OkHttp 基本用法

接下来将基于 OkHttp 4.9.0 进行讲解,演示代码是用 Kotlin 写的。

1. 添加依赖

2. 发起请求

3. 内容概览

探索 OkHttp 原理png

1. 请求信息 Request

Requestpng

Request 包含了请求相关信息,比如请求方法和请求地址和请求头等信息。

1.1.1 统一资源定位器 HttpUrl

Request 会把我们传入 url() 函数中的请求地址转化为 HttpUrl 对象。

HttpUrlpng

1. 协议

使用 http: 或 https 等协议方案名获取访问资源时,要指定协议类型,不区分字母大小写,最后加一个冒号( : )。

2. 登录信息(认证)

指定用户名和密码作为从服务端获取资源时必要的登录信息(身份认证),这是可选项。

3. 主机

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

4. 端口号

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

5. 查询路径

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

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

6. 查询参数

比如数据库服务是可以通过提供查询参数缩小请求资源范围的,传入页码和页大小查询列表 http://www.xxx.com/?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 请求报文和 HTTP 响应报文中包含的首部字段。

在请求中,HTTP 报文由方法、URI、HTTP 版本、HTTP 首部字段等部分构成。

1.1.3 请求体 RequestBody

RequestBodypng

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<User>

然后在拦截器中,就能根据 tag 的类型来获取 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 后,我们要用 OkHttpClient.newCall() 创建一个 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() 方法关闭连接,关于 AsyncTimeout 的实现后面会进一步讲解。

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

2. 发起异步请求 enqueue()

当该请求还没有被执行时,execute() 方法会创建一个异步请求操作 AsyncCall,并把它交给 Dispatcher 处理。

RealCallenqueue2png

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

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

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

RealCallenqueue

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

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

2.2 请求分发器 Dispatcher

OkHttp 请求处理流程2png

当我们调用了 RealCall 的 execute() 或 enqueue() 方法后,RealCall 会调用 Dispatcher 对应的 execute() 和 enqueue() 方法,请求分发器 Dispatcher 的 execute() 方法只是简单地把同步请求加入同步请求队列,下面我们来看下 Dipsatcher 中比较重要的 enqueue() 方法的实现。

Dispatcher 的 enqueue() 方法首先会把请求加入到待运行请求队列,然后重用 AsyncCall 的 callsPerHost 字段,callsPerHost 表示当前请求的主机地址的已执行请求数量

在我们把某个异步请求加入队列时,Dispatcher 会从已运行异步请求队列和待运行异步请求队列中找出与该请求主机地址相同的请求,找到主机相同的请求的话,就重用该请求的 callsPerHost 字段,也就是是每执行一个该主机地址的请求时,这个值就会加 1 。

如果我们的应用中经常会发起多个请求,并且请求的主机地址不多时,我们可以修改 Dispatcher 中的 maxRequestsPerHost 的值,比如下面这样。

okHttpClient.dispatcher.maxRequestsPerHost = 10

这个值默认为 5 ,也就是单个主机地址在某一个时刻的并发请求只能是 5 个。

Dispatcherenqueue

做完重用操作后,Dispatcher 就会创建一个可执行异步请求列表 executableCalls ,然后遍历待运行异步请求队列。

在遍历时,Dispatcher 会判断已运行的异步请求数量是否超出了允许的并发请求的最大值 maxRequests ,这个值默认为 64 ,也是可以被修改的。

当异步请求数量不超过最大值,并且对应主机地址的请求数量不超过最大值时,就会把该异步请求加入到 executableCalls ,然后把 executableCalls 中的请求都提交到线程池中执行

2.3 拦截器链 RealInterceptorChain

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
OkHttp 是一个开源的 HTTP 客户端,用于 Android 平台和 Java 应用。它建立在 Java 的 HttpURLConnection 类之上,并提供了更简洁、更强大的 API。 OkHttp 的工作原理主要涉及以下几个关键组件: 1. `OkHttpClient`:这是 OkHttp 的核心类,负责配置和创建请求、设置拦截器、管理连接池等。你可以通过构建 OkHttpClient 实例来自定义请求的行为和参数。 2. `Request`:表示一个 HTTP 请求,包括 URL、请求方法(如 GET、POST)、请求体、请求头等信息。你可以通过 Request.Builder 构建一个 Request 实例。 3. `Response`:表示一个 HTTP 响应,包括响应码、响应体、响应头等信息。OkHttp 会将服务器返回的数据解析成 Response 对象。 4. `Interceptor`:拦截器用于在发送请求和接收响应之前进行一些额外的处理。OkHttp 提供了很多内置的拦截器,如重试拦截器、缓存拦截器等,同时也支持自定义拦截器。 5. `Dispatcher`:调度器负责管理请求的调度和执行。它可以控制同时并发执行的请求数量,还可以设置请求超时时间等。 6. `ConnectionPool`:连接池用于管理 HTTP 连接的复用和回收。OkHttp 会自动复用连接以减少网络延迟,提高性能。 7. `Cache`:缓存可以保存服务器返回的响应,以便在后续的请求中复用。OkHttp 支持对响应进行缓存,并提供了灵活的配置选项。 当你使用 OkHttp 发起一个网络请求时,它会通过 OkHttpClient 来创建一个 Request 对象,并通过 Dispatcher 来执行这个请求。在执行过程中,OkHttp 会根据设置的拦截器进行一系列的处理,如添加请求头、重试、缓存等。最终,OkHttp 将返回一个 Response 对象,你可以从中获取到服务器返回的数据。 总体来说,OkHttp 的工作原理是通过封装底层的 HttpURLConnection,提供了简洁易用的 API,并通过拦截器和连接池等机制优化了网络请求的性能和可定制性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值