OKHttp全解析系列(二) -- OkHttp 简介

OKHttp的使用和类关系

本文目的是对OkHttp 框架及其中的常用概念(类)做简单性介绍。

简介

OKHttp 是 Square 公司开发的一款网络框架,其设计和实现的目的就是高效。OkHttp框架完整的实现了 HTTP 协议,支持的协议有 HTTP/1.1、SPDY、HTTP/2.0 。在 Android 4.4 的源码中 HttpURLConnection 已经被替换成OkHttp。

既然 OkHttp 的招牌是高效,那它是如何实现的呢?

  • 采用连接池技术减少
  • 默认使用 GZIP 数据压缩格式,降低传输内容的大小
  • 采用缓存避免重复的网络请求
  • 支持SPDY、HTTP/2.0,对于同一主机的请求可共享同一socket连接
  • 若SPDY或HTTP/2.0 不可用,还会采用连接池提高连接效率
  • 网络出现问题、会自动重连(尝试连接同一主机的多个ip地址)
  • 使用 okio 库简化数据的访问和存储

简单使用

下面是一个使用 OkHttp 的同步 GET 请求的简单例子,这个例子要做的就是用 GET 请求 https://publicobject.com 域名上的一个名叫helloworld.txt 的文本资源,并将其内容打印(实际上打印文本前先打印的响应头)。那么它是怎么做的呢?第一步,用 URL 描述该资源,并用其构建一个 Request 对象;第二步,OkClient 把 Request 封装成一个 Call 对象,第三步执行Call 的execute()方法,得到 Response 响应;第四步,打印响应头和响应体。整体逻辑还是比较清楚的,如果你对 Request、Response、Call等感到困惑,继续看下文就对了。
本文不对使用教程做更多介绍,更多范例可参考官方wiki或者OkHttp使用教程

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
      System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
    }

    System.out.println(response.body().string());
}

Okhttp常用概念

OkHttpClient

OkHttpClient 可以说是整个OkHttp 框架的一个门面(Facade 模式),在使用 OkHttp 框架时只需同OkHttpClient 打交道,而不用考虑其内部复杂的构成。这个类的职责就是配置参数和生成 Call 对象,配置参数包括:超时时间(连接、读、写)、代理服务器、Dns、协议和连接规范、缓存配置,拦截器、证书验证等。

官方建议是一个工程只配置一个 OKHttpClient 来处理所有的网络请求。

Request & Response& Call

Request

Request 是 一个基本的 HTTP 网络请求,包括要请求的URL,同 HTTP 规范一样,它需要包含请求行,请求头,请求体(可以为空)。

Response

An HTTP response. Instances of this class are not immutable: the response body is a one-shot value that may be consumed only once. All other properties are immutable.

Response 即为网络响应,包括响应行,响应头和响应体(允许为空), 响应体只能被消费一次,且除响应体外的属性不可变。

Call & RealCall

A call is a request that has been prepared for execution. A call can be canceled. As this object represents a single request/response pair (stream), it cannot be executed twice.

Call 是一个接口,实现了对 Request 的封装,可以看做是一次请求任务,可以同步(execute)、异步(enqueue)执行,也可以取消 (cancel) 或者查询当前任务状态 。RealCall 是 Call 的一个子类,对其接口中定义的方法做了实现。

Connections

OKHttp 对用户很友好,使用时只需传入一个 URL 就可以了。但是对于OkHttp 内部实现来说,它需要3种类型的数据:URL、Address、Route。

URLs

URLs (like https://github.com/square/okhttp) are fundamental to HTTP and the Internet. In addition to being a universal, decentralized naming scheme for everything on the web, they also specify how to access web resources.

URLs (例如 https://github.com/square/okhttp) 是 HTTP 和互联网的基础。除了为互联网上的事物提供通用的命名方案之外(如 http、ftp、smtp 等),还规定了如何访问网络资源。

URLs are abstract:

  • They specify that the call may be plaintext (http) or encrypted (https), but not which cryptographic algorithms should be used. Nor do they specify how to verify the peer's certificates (the HostnameVerifier) or which certificates can be trusted (the SSLSocketFactory).
  • They don't specify whether a specific proxy server should be used or how to authenticate with that proxy server.

URLs 是抽象的,他们规定一次 Call 可以是明文的(http)也可以是加密的(https),但是既不指定加密算法,也不指定如何校验对方证书; 既不指定是否使用特定的代理服务器,也不指定如何认证到代理服务器。

They're also concrete: each URL identifies a specific path (like /square/okhttp) and query (like ?q=sharks&lang=en). Each webserver hosts many URLs.

URLs 同时也是具体的,每一个URL 标识一个特定的路径(如 /square/okhttp)和查询(? q = sharks&lang = en)。每一个Web 服务器 可以持有很多URLs。

小结:以上是对官方 WiKi 对 URL 的解释,其实就是在讲 URI(URL 是 URI 的子集) 的语法,明白语法就明白一切了。语法可参考 URI详解

Address
  • Address 指定了一个 web 服务器(如 github.com)的地址和所有连接到此服务器所必须的静态配置: 端口号、https设置,首选网络协议(如 HTTP/2.0、SPDY)。
  • 持有同一Address的 URLs 可以共享底层的 socket 连接,共享连接在性能上有巨大的优势: 低延迟、高吞吐量(主要由于 TCP 的慢启动)、低耗电。OkHttp 采用连接池自动重用 HTTP/1.x 的连接和复用 HTTP/2.0 和 SPDY 的连接。
  • 在 OkHttp 中,Address 的一些字段来自 URL(如协议名、主机名、端口号),其余配置信息来自 okHttpClient 。
Route

Route 提供了连接到 Web 服务器所需要的动态信息,包括特定的 IP 地址(如经过 DNS 查询后返回的地址队列),要使用的代理服务器,TLS 版本信息等。
对于一个 Address 可能有多个 Route 信息。

Connection

Connection 代表指向源服务器或代理服务器的一个真实连接,在这个连接上可以传输 HTTP 请求、响应。
当用 OkHttp 请求一个 URL 的时候,它将会:

  • 1、它会用 URL 和已经配置好的 okhttpClient 创建一个 Address 对象,这个 Address 指定我们将如何连接到 web服务器。
  • 2、尝试从连接池中检索与此 Address 对应的连接是否存在
  • 3、如果该连接不存下,则它将尝试选择一个 Route 去连接。这也就意味着 从发送一个请求去解析服务器的 IP 地址,如果有必要的话选择一个代理服务器和 TLS 版本。
  • 4、如果这是一个新的 Route,则有三种连接方式:直接的 Socket 连接;TLS 隧道(在代理服务器上使用 HTTPS 的时候);直接的 TLS 连接。必要的时候会进行 TLS 握手。
  • 5、连接建立后便可以发送请求和读取响应了。

当 Connection 发生问题的时候,OkHttp 会重新选择一个 Route 并且重试。这意味着当服务器的IP 地址的一个子集不可用的时候,OkHttp 可以重新尝试连接。并同样适用于ConnectionPool 中的连接失效或者所选 TLS 版本不受支持的情况。

ConnectionPool

Manages reuse of HTTP and SPDY connections for reduced network latency. HTTP requests that share the same Address may share a Connection. This class implements the policy of which connections to keep open for future use.
The system-wide default uses system properties for tuning parameters:

  • http.keepAlive true if HTTP and SPDY connections should be pooled at all. Default is true.
  • http.maxConnections maximum number of idle connections to each to keep in the pool. Default is 5.
  • http.keepAliveDuration Time in milliseconds to keep the connection alive in the pool before closing it. Default is 5 minutes. This property isn’t used by HttpURLConnection.

The default instance doesn’t adjust its configuration as system properties are changed. This assumes that the applications that set these parameters do so before making HTTP connections, and that this class is initialized lazily.

由于持有同一 Address 的 Request 可以共享一个 Connection,为了减少网络延迟而选择了重用 Connection。ConnectionPool 就是实现管理这些连接的一个策略类,它规定了连接池中哪些类可以重用。
HTTP 或者 SPDY 连接默认是长连接,且默认会添加到连接池中;连接池有最大空连接数限制,默认值为5;一个空连接能保持的最大时长为 5 分钟,超过这个时间这个 Connection 对象将会被回收。

StreamAllocation

字面意思为“流分配”,这个流指的是在 物理连接上传输的数据流 ,数据流就是逻辑上的就是 HTTP Request/Response 键值对。
这个类的职责是协调三个实体之间的关系:Connections、Streams、Calls。

  • Connection 的建立有可能会很慢,因此有必要在建立 Connection 的过程中可以取消。
  • Streams,每个 Connection 有自己的流分配策略:在一个连接上最大可承载多少 Streams.HTTP/1.x 只能承载一个,SPDY 和 HTTP/2.0 可以由于可多路复用可承载多个。
  • Calls ,是 Streams 组成的逻辑序列,典例就是最初的 Request 请求和 该请求的一系列重定向请求。

Interceptors

拦截器是一个功能强大的机制,可以监视,重写和重试 Call。通常情况下,拦截器用来添加、移除和转换 Request 和 Response 的头部信息。

在 OkHttp 中,对于对请求 Request 的不同处理,细化出多个拦截器,例如
BridgeInterceptor:负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为对用户友好的响应。
RetryAndFollowUpInterceptor:负责连接失败后重试和重定向。
CacheInterceptor:缓存拦截器,负责读取缓存和更新缓存。
ConnectInterceptor:连接拦截器,负责从客户端到服务器的连接工作。
CallServerInterceptor:负责向服务器发送请求数据,并从服务器读取响应数据。

networkInterceptors:网络拦截器,配置 OkClient 时设置的拦截器,主要应用于真正发生网络请求的地方。

拦截器可以分为Application Interceptors (应用拦截器)和Network Interceptors(网络拦截器)。

先看图中的数据流向,从应用层看,只需关注上半部分即可,Application 发送一个 Request 给 OkHttp core , 再收取由 OkHttp core 返回的 Responses 。但是对于 OkHttp core 内部实现来说,这个 Response 有可能是从缓存获取的,也有可能是实际从服务器端拉取的数据。当从服务器拉取数据时,网络拦截器便派上用场了。

Interceptor.Chain

拦截器链,顾名思义就是由许多拦截器组成的链,对于一个 Request 从发送到接收 Response 过程中,许多拦截器都要参与其中。对其工作方式的理解可以参考责任链模式,因为 OkHttp 中的拦截器链正是这一设计模式的实现。在链上拦截器是有处理顺序的,它们的排列顺序就是拦截器处理 Request 的顺序,如代码所示。

在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

下图是拦截器链的工作方式,拦截器链是对 AOP(Aspect Oriented Programming,面向切面编程) 设计的一种实现,针对 Request 和 Response 做了切面处理。在拦截前,主要针对Request 做相关处理;拦截后,对 Response 做相关处理。拦截器N 多为实际发生网络请求的地方,对应的拦截器为 CallServerInterceptor 。

Cache & CacheStrategy

Cache 即缓存,是对于某一特定 Request 的缓存,缓存的内容为该 Request 的 Response ,缓存多以<请求、响应>键值对的形式存在。缓存可以为内存,也可以为硬盘,缓存可以进行失效时间、最大容量、缓存目录等设置。
CacheStrategy 即缓存策略,即当用 OkHttp 请求一个 Request 时,是采用缓存还是网络还是两者皆用。

参考文献

https://github.com/square/okhttp/wiki

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,764评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,235评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,965评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,984评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,984评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,471评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,844评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,818评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,359评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,385评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,515评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,114评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,836评论 3 338
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,291评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,422评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,064评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,581评论 2 365

推荐阅读更多精彩内容