Okhttp做为目前Android使用最为普遍的网络框架之一,咱们有必要去深刻了解一下,本文是Okhttp解析的第一篇,主要是从宏观上认识Okhttp整个架构是如何实现的。git
HTTP是当今应用程序经过网络交换数据和媒体的方式。 有效地使用 HTTP 可使应用加载得更快并节省带宽。
Okhttp是一个高效的HTTP Client,高效性体如今:github
当网络出现问题时,OkHttp 不会当即结束: 它会默默地从常见的链接问题中恢复过来。 若是您的服务有多个 IP 地址,若是第一次链接失败,OkHttp 将尝试替代地址。 这对于 IPv4 + IPv6和承载于冗余数据中心的服务是必要的。 Okhttp 支持现代 TLS 特性(TLS 1.三、 ALPN、证书ping)。 它能够配置为回退到可用的链接。
而且Okhttp是易用的,其经过Builder模式设计请求 / 响应 API,支持同步阻塞调用和带回调的异步调用。web
首先咱们来了解下HTTP client、request、response。
HTTP client的做用就是接受咱们的request并返回response。
request一般包含一个 URL, 一个方法 (好比GET/POST), 以及一个headers列表还可能包含一个body(特定内容类型的数据流)。
response则一般用响应代码(好比200表示成功,404表示未找到)、headers和可选的body来回答request。缓存
咱们平常使用http都是按如下步骤:
一、建立httpClient
二、建立request
三、使用httpClient请求request而后获取respone安全
使用Okhttp也是如此,咱们建立OkhttpClient而后把Reques交给它,最后拿到Respone,可是Okhttp在内部实际进行http请求时并非这样简单的拿Request去请求而后得到Resopne返回。服务器
下面就来看下Okhttp的请求机制,能够归纳为如下流程:cookie
Okhttp 能够添加原始请求中缺乏的headers,包括Content-Length,Transfer-Encoding,User-Agent ,Host ,Connection , 和Content-Type。 除非Accept-Encoding头已经存在,不然它将添加一个用于透明响应压缩的 Accept-Encoding 头。 若是你有 cookies,OkHttp 会添加一个 Cookie 头。网络
有些请求会有一个缓存response。 当这个缓存过时,OkHttp 能够执行一个有条件的 GET 来下载新的response,这须要添加如 If-Modified-Since 和 If-None-Match 这样的headers。架构
Okhttp链接webserver时使用了URL、Address和Route。并发
Url是 HTTP 和互联网的基础,每一个 URL 标识一个特定的路径。它是一个通用的,分散的网络命名方案,它指定了如何访问网络资源、指定调用是纯文本(http) 或加密(https)方式。它们没有指定是否应该使用特定的代理服务器或者如何经过该代理服务器的身份验证
Address指定一个 web 服务器(好比 github. com)和链接到该服务器所需的全部静态配置: 端口号、 HTTPS 设置和首选网络协议(好比 http / 2或 SPDY)。
具备相同Address的 url 也可能有相同的底层 TCP 套接字链接。 共享一个链接有很大的性能优点好比更低的延迟,更高的吞吐量(因为 TCP 缓慢启动)和节省电池。 Okhttp 使用一个 ConnectionPool 自动重用 http / 1.x 链接以及多路传输 http / 2和 SPDY 链接。
在 OkHttp 中,Address的一些字段来自 URL (scheme, hostname, port) ,其他字段来自 OkHttpClient。
Route提供实际链接到网络服务器所需的动态信息。 它会尝试的特定 IP 地址(由 DNS 查询发现)、使用的准确代理服务器(若是使用 ProxySelector)以及协商的 TLS 版本(用于 HTTPS 链接)。
一个地址可能有多条Route。 例如,承载于多个数据中心的 web 服务器在其 DNS 响应中可能会产生多个 IP 地址。
当你使用 OkHttp 请求一个 URL 时,它是这样作的:
若是链接有问题,OkHttp 会选择另外一条route,再试一次。 这让 OkHttp 在服务器地址的一个子集没法访问时从错误中恢复。 当池链接过期或者不支持当前使用的 TLS 版本时,它也颇有用。
一旦接收到respone,链接将返回到池中,以即可以在未来的请求中重用它。 链接在一段时间的不活动会被从链接池中清除。
若是使用透明压缩,OkHttp 将删除相应的 Content-Encoding 和 Content-Length,由于它们不适用于解压缩的响应体。
若是条件 GET请求 成功,来自网络和缓存的respone将按照规范的指示进行合并。
当你请求的 URL 被重定向,webserver 将返回一个响应代码,好比302来指示新 URL。Okhttp将会重定向检索最终的respone。
若是respone发出了一个受权验证,OkHttp 将要求 Authenticator (若是配置了一个)知足这个验证。 若是身份验证者提供了凭据,那么request会携带该凭据去重试。
有时候链接会失败(好比池链接过期并断开链接或没法链接到网络服务器自己)若是此时有一个可用的Route,OkHttp 会用该Route重试请求。
Okhttp在实现流程的时候还引入了一些概念好比Call、Interceptors、ConnectionSpec、Events等等。
通过重写、重定向、重试等操做,简单请求可能会产生许多请求和响应。 Okhttp 使用 Call 来封装表示request,Call就是一个已经准备好能够执行的请求。若是 url 被重定向,或者故障转移到另外一个 IP 地址,那么代码将继续工做,直至返回最终respone。
Call有两种调用方式:
同步:线程阻塞直到响应可读为止
异步: 能够在任何线程上对请求进行排队,当响应可读时在另外一个线程上被调回
能够从任何线程取消Call调用, 这将致使调用失败。在写入请求body或读取响应body时调用cancel会抛出IOException。
Dispatcher调度器,它实际就是负责Okhttp请求策略。
对于同步调用,调用请求的线程本身负责管理同时发出的请求数量。可是要注意的是太多的并发请求会浪费资源; 太少也很差。
对于异步调用,Dispatcher 实现最大并发请求的策略。 它设置了每一个 web 服务器的最大值并发请求数为5,总并发数为64。固然咱们也能够自行设置并发数。
拦截器能够说是Okhttp的精髓之一,它是一种强大的机制,它能够监测、重写和重试Call调用。系统提供了5种已经定义好的拦截器,上面说的request/respone 重写,失败重试等都是在拦截器中完成的。
对 chain.proceed (request)的调用是每一个拦截器实现的关键部分。 这个简单的外观方法是全部拦截器完成其功能的地方,而且还生成知足请求的响应。 注意若是 chain.proceed (request)被调用屡次,则必须关闭之前的响应body。
实际使用过程是经过拦截器链把拦截器链起来而后按顺序调用拦截器。
Okhttp拦截器分为2类:Application Interceptors和Network Interceptors。
这两类拦截器本质上没有区别只是它们做用的时机不一样。由上图咱们能够看出Application Interceptors做用于Okhttp Core以前而Network Interceptors则做用于Okhttp Core以后。个人理解就是Application Interceptors调用是在请求发出以前,Network Interceptors则是在请求发出后与webserver链接的过程。
每种拦截器链都有其优势:
Application interceptors
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */ final class GzipRequestInterceptor implements Interceptor { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Request originalRequest = chain.request(); if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) { return chain.proceed(originalRequest); } Request compressedRequest = originalRequest.newBuilder() .header("Content-Encoding", "gzip") .method(originalRequest.method(), gzip(originalRequest.body())) .build(); return chain.proceed(compressedRequest); } private RequestBody gzip(final RequestBody body) { return new RequestBody() { @Override public MediaType contentType() { return body.contentType(); } @Override public long contentLength() { return -1; // We don't know the compressed length in advance! } @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); body.writeTo(gzipSink); gzipSink.close(); } }; } }
使用Interceptor重写响应
/** Dangerous interceptor that rewrites the server's cache-control header. */ private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { @Override public Response intercept(Interceptor.Chain chain) throws IOException { Response originalResponse = chain.proceed(chain.request()); return originalResponse.newBuilder() .header("Cache-Control", "max-age=60") .build(); } };
ConnectionSpec的引入是为了HTTPS,在协商与 HTTPS 服务器的链接时,OkHttp 须要知道要提供哪些 TLS 版本和密码套件。Okhttp为了与尽量多的主机链接的同时保证链接的安全性引入ConnectionSpec,它实现了特定的安全性和链接性决策,Okhttp 包括四个内置的链接规范RESTRICTED_TLS、MODERN_TLS、COMPATIBLE_TLS、CLEARTEXT。
默认状况下,OkHttp 将尝试创建一个 MODERN_TLS 链接。 可是,经过配置client的 connectionSpecs,若是 MODERN_TLS失败,回退到 COMPATIBLE_TLS 链接。
OkHttpClient client = new OkHttpClient.Builder() .connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS)) .build();
Events的引入是为了监控call请求,由于咱们有必要了解咱们应用的HTTP请求。具体来讲就是监控如下内容:
应用程序 HTTP 请求调用的频率和请求大小。
基础网络的性能监控,若是网络差你应该减小请求或者改善网络。
Okhttp提供了EventListener,咱们能够继承它并重写咱们感兴趣的方法来进行Event的监控。
上图是在没有重试和重定向的状况下EventListener所能监控的Events流。下面是对应的代码
class PrintingEventListener extends EventListener { private long callStartNanos; private void printEvent(String name) { long nowNanos = System.nanoTime(); if (name.equals("callStart")) { callStartNanos = nowNanos; } long elapsedNanos = nowNanos - callStartNanos; System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name); } @Override public void callStart(Call call) { printEvent("callStart"); } @Override public void callEnd(Call call) { printEvent("callEnd"); } @Override public void dnsStart(Call call, String domainName) { printEvent("dnsStart"); } @Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) { printEvent("dnsEnd"); } ... }
上面Eventlistener只适用于没有并发的状况,若是有多个请求并发执行咱们须要使用Eventlistener. Factory来给每一个请求建立一个Eventlistener。
下面是一个给每一个请求建立一个带惟一ID的Eventlistener的示例:
class PrintingEventListener extends EventListener { public static final Factory FACTORY = new Factory() { final AtomicLong nextCallId = new AtomicLong(1L); @Override public EventListener create(Call call) { long callId = nextCallId.getAndIncrement(); System.out.printf("%04d %s%n", callId, call.request().url()); return new PrintingEventListener(callId, System.nanoTime()); } }; final long callId; final long callStartNanos; public PrintingEventListener(long callId, long callStartNanos) { this.callId = callId; this.callStartNanos = callStartNanos; } private void printEvent(String name) { long elapsedNanos = System.nanoTime() - callStartNanos; System.out.printf("%04d %.3f %s%n", callId, elapsedNanos / 1000000000d, name); } @Override public void callStart(Call call) { printEvent("callStart"); } @Override public void callEnd(Call call) { printEvent("callEnd"); } ... }
上面说的都是请求正常时event,接下来讲下非正常状况下的event流程。
当请求失败时,将调用一个失败方法 connectFailed () ,用于在创建到服务器的链接时发生故障,当 HTTP 请求永久失败时调用 callFailed ()。 当发生故障时,有可能开始事件没有相应的结束事件。
Okhttp是健壮的,能够从一些链接故障中自动恢复。 在这种状况下,connectFailed ()事件不是终结符,后面不跟 callFailed ()。 当尝试重试时,事件侦听器将收到多个相同类型的事件。
单个 HTTP 调用可能须要发出后续请求来处理身份验证、重定向和 HTTP 层超时。 在这种状况下,能够尝试多个链接、请求和响应。 重定向是单个请求可能触发同一类型多个事件的另外一个缘由。
以上就是本文所有内容,接下来就是源码分析了,不过建议在看源码前把Okhttp的整个运行过程以及其中涉及的概念搞懂这样看源码才会事半功倍。