OkHttp 使用方法简介
1.创建⼀个 OkHttp 的实例:
1 | OkHttpClient client = new OkHttpClient.Builder().build(); |
2.创建 Request:
1 | Request request = new Request.Builder() |
3.创建 Call 并发起网络请求:
1 | client.newCall(request).enqueue(new Callback() { |
OkHttp 源码总结
- OkHttpClient 相当于配置中心,所有的请求都会共享这些配置(例如出错是否重试、共享的连接池)。OkHttpClient 中的配置主要有:
1.Dispatcher dispatcher :调度器,用于调度后台发起的网络请求(通过线程池把请求放在后台线程进⾏等等),有后台总请求数(maxRequests)和单主机总请求数(maxRequestsPerHost)的控制。
1 | public final class Dispatcher { |
1 | 如果想让所有的请求一个个按照顺序来执行,可以设置 setMaxRequests(1) |
2.List
3.List
传输(用于 HTTP)还是某个版本的 TLS(用于 HTTPS)。
4.List
1 | OkHttpClient client = new OkHttpClient.Builder() |
5.List
6.CookieJar cookieJar :管理理 Cookie 的控制器。OkHttp 提供了 Cookie 存取的判断⽀持(即什什么时候需要存 Cookie,什么时候需要读取 Cookie,但没有给出具体的存取实现。如果需要存取 Cookie,你得⾃己写实现,例如用 Map 存在内存里,或者用别的方式存在本地存储或者数据库。
1 | CookieJar cookieJar = new CookieJar() { |
7.Cache cache :Cache 存储的配置。默认是没有,如果需要用,得⾃己配置出 Cache 存储的⽂件位置以及存储空间上限。
8.CertificateChainCleaner certificateChainCleaner :从服务器获得的证书可能会包含很多内容,会包含好多无关的证书,certificateChainCleaner 会整理证书,形成一个证书序列,该序列第一个就是对方网站的证书,最后一个是信任的本地根证书。
9.HostnameVerifier hostnameVerifier :主机名验证器,⽤于验证 HTTPS 握手过程中下载到的证书所属者是否和⾃己要访问的主机名一致。
10.CertificatePinner certificatePinner :⽤于设置 HTTPS 握手过程中针对某个
Host 的 Certificate Public Key Pinner,即把网站证书链中的每一个证书公钥直接拿来提前配置进 OkHttpClient ⾥去,以跳过本地根证书,直接从代码⾥进⾏认证。这种⽤法⽐较少见,一般用于防止网站证书被⼈仿制。
certificatePinner 用来做自签名时很有用。有时候一个证书由于是自签名或者证书机构未更新等原因,在本地验证不通过,假如是网站的开发者或者工作人员,可以拿到该证书的公钥,则可以将证书的信息记录在本地,对比网站下载的证书和本地证书是否一致即可:
1 | String hostname = "hencoder.com"; |
如上代码,在 client 中增加了一个 certificatePinner ,配置了一个网站的公钥信息,声明遇到“hencoder.com”时查看它的公钥是否是“sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=”,如果是则验证通过,否则失败,日志如下:
1 | 2019-11-28 15:57:55.359 4571-4613/com.example.httptest I/System.out: Failed!!! |
可以看到 SSL 建立过程中验证失败的信息,并显示出了从网站获得的公钥信息,此处有三个:
1 | sha256/+OAwmENjrBT/pI2PSOVO/I3OtHeKk7Y0PH9h8Z2z3Nw= |
以及本地存的公钥信息:
1 | sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= |
可以将上面的三个公钥添加进去,即可验证成功,代码如下所示:
1 | String hostname = "hencoder.com"; |
1 | 经验证,上面日志中返回的三个公钥,只要有一个公钥匹配成功,即可验证通过。 |
11.Authenticator authenticator :⽤于⾃动重新认证。配置之后,在请求收到401(权限不足,例如未登录)状态码的响应时,会直接调⽤ authenticator ,手动加入 Authorization header 之后自动重新发起请求。代码如下:
1 | …… |
Basic 和 Bearer 两种授权方式请参考登录与授权中的 Authorization 。
12.boolean followRedirects :遇到重定向(状态码3xx)的要求时,是否自动跳转,默认为 true 。
13.boolean followSslRedirects 在重定向时,如果原先请求的是 http ⽽重定向的目标是 https,或者原先请求的是 https ⽽重定向的目标是 http ,是否依然自动跳转。此外,如果 followRedirects 设置为 false ,便谈不上 followSslRedirects 是否自动跳转了。
14.boolean retryOnConnectionFailure :在请求失败的时候是否自动重试。注意,⼤多数的请求失败并不不属于 OkHttp 所定义的“需要重试”,这种重试只适用于“同一个域名的多个 IP 切换重试”“Socket 失效重试”等情况。
15.int connectTimeout :建⽴立连接(TCP 或 TLS)的超时时间。
16.int readTimeout :发起请求到读到响应数据的超时时间。
17.int writeTimeout :发起请求并被⽬标服务器接受的超时时间。(为什么?因为有时候对方服务器可能由于某种原因⽽不读取你的 Request。)
newCall(Request) ⽅法会返回一个 RealCall 对象,它是 Call 接口的实现。当调用 RealCall.execute() 的时候, RealCall.getResponseWithInterceptorChain() 会被调用,它会发起⽹络请求并拿到返回的响应,装进⼀个 Response 对象并作为返回值返回;RealCall.enqueue() 被调⽤的时候⼤同小异,区别在于 enqueue() 会使用 Dispatcher 的线程池来把请求放在后台线程进⾏,但实质上使用的同样也是
getResponseWithInterceptorChain() 方法。getResponseWithInterceptorChain() 方法做的事:OkHttp 在技术上的核心。把所有配置好的 Interceptor 放在一个 List 里,然后作为参数,创建⼀个 RealInterceptorChain 对象,并调用
chain.proceed(request) 来发起请求和获取响应。在 RealInterceptorChain 中,多个 Interceptor 会依次调用⾃己的 intercept() ⽅法。这个⽅法会做三件事:
1.对请求进⾏预处理;
2.预处理之后,重新调用 RealIntercepterChain.proceed() 把请求交给下一个
Interceptor
3.在下一个 Interceptor 处理完成并返回之后,拿到 Response 进⾏后续处理。
1 | 当然了,最后⼀个 Interceptor 的任务只有一个:做真正的⽹络请求并拿到响应。 |
- 从上到下,每级 Interceptor 做的事:
首先是开发者使用 addInterceptor(Interceptor) 所设置的,它们会按照开发者的要
求,在所有其它 Interceptor 处理之前,进⾏最早的预处理工作,以及在收到 Response 之后,做最后的善后工作。如果你有统一的 header 要添加,可以在这里设置;
然后是 RetryAndFollowUpInterceptor :它负责在请求失败时的重试,以及重定向的自动后续请求。它的存在,可以让重试和重定向对于开发者是无感知的;
BridgeInterceptor :它负责⼀些不影响开发者开发,但影响 HTTP 交互的一些额外预处理。例如,Content-Length 的计算和添加、gzip 的支持(Accept-Encoding: gzip)、gzip 压缩数据的解包,都发⽣在这⾥;
CacheInterceptor :它负责 Cache 的处理。把它放在后⾯的⽹络交互相关
Interceptor 的前面的好处是,如果本地有了可用的 Cache,一个请求可以在没有发⽣实质⽹络交互的情况下就返回缓存结果,⽽完全不需要开发者做出任何的额外工作,让 Cache 更加⽆感知;
ConnectInterceptor :它负责建⽴连接。在这⾥,OkHttp 会创建出⽹络请求所需要的 TCP 连接(如果是 HTTP),或者是建立在 TCP 连接之上的 TLS 连接(如果是 HTTPS),并且会创建出对应的 HttpCodec 对象(⽤于编码解码 HTTP 请求);
然后是开发者使⽤ addNetworkInterceptor(Interceptor) 所设置的,它们的行为逻
辑和使用 addInterceptor(Interceptor) 创建的一样,但由于位置不同,所以这⾥创建的 Interceptor 会看到每个请求和响应的数据(包括重定向以及重试的⼀些中间请求和响应),并且看到的是完整原始数据,⽽不是没有加 Content-Length 的请求数据,或者 Body 还没有被 gzip 解压的响应数据。多数情况,这个⽅法不需要被使用;
CallServerInterceptor :它负责实质的请求与响应的 I/O 操作,即往 Socket ⾥写⼊请求数据,和从 Socket ⾥读取响应数据。
getResponseWithInterceptorChain()源码如下:
1 | Response getResponseWithInterceptorChain() throws IOException { |
图例:
如上图 Interceptor 组成一个链,整个链用来做网络请求,箭头1用来向网络发送信息,箭头2获取网络的返回。当结构复杂时,拆成链,每一个节点做不同的事,每一个节点就是一个拦截器(Interceptor)。源码中“ chain.proceed ”操作包含两部分( chain.proceed 代码之前的为前置工作,后面的为后置工作),以如图中的蓝色原点所示,在箭头1中,执行拦截器中的操作,并交给下一个点;在箭头2中,接受后面拦截器的返回,并继续执行一些操作,这两个部分为一个“ proceed ”操作。每一个拦截器基本上都包含两个部分:事前准备、交给下一个拦截器并等待回来,拿到返回做后续处理。当蓝色圆点的“ proceed ”操作结束,事实上它后面的圆点的“ proceed ”操作已经全部结束了。再以红色圆点为例,它先做前置工作,即“ proceed part1 ”的部分,一直需要等到后面的绿色、天蓝色、紫色圆点的前置工作和后置工作都结束( proceed part1 和 proceed part2 部分),以及粉红色圆点的工作结束,红色圆点继续做后置工作( proceed part2 )。这看上去很像一个递归操作。
OkHttp 源码结构图:
参考资料:
腾讯课堂 HenCoder