Android:安卓学习笔记之OkHttp原理的简单理解和使用
Posted JMW1407
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android:安卓学习笔记之OkHttp原理的简单理解和使用相关的知识,希望对你有一定的参考价值。
android OkHttp使用原理的简单理解和使用
OkHttp
0、前言
本文主要包括以下内容
- 1、OKHttp请求的整体流程是怎样的?
- 2、OKHttp分发器是怎样工作的?
- 3、OKHttp拦截器是如何工作的?
- 4、OKHttp如何复用TCP连接?
- 5、OKHttp空闲连接如何清除?
- 6、OKHttp有哪些优点?
- 7、OKHttp框架中用到了哪些设计模式?
Okhttp的子系统层级结构图如下所示:
在整个Okhttp的系统中,我们还要理解以下几个关键角色:
1、请求与响应流程
- (1)、当我们通过
OkhttpClient
创立一个Call
,并发起同步或者异步请求时; - (2)、
okhttp
会通过Dispatcher
对我们所有的RealCall
(Call的具体实现类)进行统一管理,并通过execute
()及enqueue
()方法对同步
或者异步
请求进行解决; - (3)、
execute
()及enqueue
()这两个方法会最终调用RealCall
中的getResponseWithInterceptorChain
()方法,从阻拦器链中获取返回结果; - (4)、阻拦器链中,依次通过
RetryAndFollowUpInterceptor(重定向阻拦器
)、BridgeInterceptor(桥接阻拦器)
、CacheInterceptor(缓存阻拦器)
、ConnectInterceptor(连接阻拦器)
、CallServerInterceptor(网络阻拦器)
对请求依次请求重试,缓存处理,与服务的建立连接后,获取返回数据,再经过上述阻拦器依次解决后,最后将结果返回给调用方。
调用流程如下:
1.1 请求的封装
请求是由Okhttp发出,真正的请求都被封装了在了接口Call的实现类RealCall中,如下所示:
Call接口如下所示:
public interface Call extends Cloneable
//返回当前请求
Request request();
//同步请求方法,此方法会阻塞当前线程知道请求结果放回
Response execute() throws IOException;
//异步请求方法,此方法会将请求添加到队列中,然后等待请求返回
void enqueue(Callback responseCallback);
//取消请求
void cancel();
//请求是否在执行,当execute()或者enqueue(Callback responseCallback)执行后该方法返回true
boolean isExecuted();
//请求是否被取消
boolean isCanceled();
//创建一个新的一模一样的请求
Call clone();
interface Factory
Call newCall(Request request);
RealCall的构造方法如下所示:
final class RealCall implements Call
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket)
//我们构建的OkHttpClient,用来传递参数
this.client = client;
this.originalRequest = originalRequest;
//是不是WebSocket请求,WebSocket是用来建立长连接的,后面我们会说。
this.forWebSocket = forWebSocket;
//构建RetryAndFollowUpInterceptor拦截器
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
RealCall实现了Call接口,它封装了请求的调用,这个构造函数的逻辑也很简单:
- 赋值外部传入的OkHttpClient、Request与forWebSocket,
- 并 创建了重试与重定向拦截器RetryAndFollowUpInterceptor。
1.2 请求的发送
Okhttp的整个请求分为同步
和异步
两种:
- 1、同步请求通过 调用
Call.exectute()
方法直接返回当前请求的Response
- 因为同步请求不需要线程池,也不存在任何限制。所以分发器仅做一下记录。后续按照加入队列的顺序同步请求即可
synchronized void executed(RealCall call)
runningSyncCalls.add(call);
final class RealCall implements Call
@Override public Response execute() throws IOException
synchronized (this)
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
captureCallStackTrace();
try
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
finally
client.dispatcher().finished(this);
- 2、异步请求调用
Call.enqueue()
方法将请求(AsyncCall)
添加到请求队列中- 当正在执行的任务未超过最大限制
64
,同时同一Host
的请求不超过5
个,则会添加到正在执行队列,同时提交给线程池。否则先加入等待队列。
- 当正在执行的任务未超过最大限制
synchronized void enqueue(AsyncCall call)
//请求数最大不超过64,同一Host请求不能超过5个
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost)
runningAsyncCalls.add(call);
executorService().execute(call);
else
readyAsyncCalls.add(call);
final class RealCall implements Call
@Override public void enqueue(Callback responseCallback)
synchronized (this)
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
AsyncCall
本质上是一个Runable
,Dispatcher
会调度ExecutorService
来执行这些Runable
。
final class AsyncCall extends NamedRunnable
private final Callback responseCallback;
AsyncCall(Callback responseCallback)
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
String host()
return originalRequest.url().host();
Request request()
return originalRequest;
RealCall get()
return RealCall.this;
@Override protected void execute()
boolean signalledCallback = false;
try
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled())
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
else
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
catch (IOException e)
if (signalledCallback)
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
else
responseCallback.onFailure(RealCall.this, e);
finally
client.dispatcher().finished(this);
不管是同步请求还是异步请求最后都会通过getResponseWithInterceptorChain()
获取Response
,只不过异步请求多了个线程调度,异步 执行的过程。
1.3 请求的调度
public final class Dispatcher
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
/** Used by @code Call#execute to signal it is in-flight. */
synchronized void executed(RealCall call)
runningSyncCalls.add(call);
synchronized void enqueue(AsyncCall call)
//正在运行的异步请求不得超过64,同一个host下的异步请求不得超过5个
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost)
runningAsyncCalls.add(call);
executorService().execute(call);
else
readyAsyncCalls.add(call);
Dispatcher是一个任务调度器,它内部维护了三个双端队列:
readyAsyncCalls
:准备运行的异步
请求runningAsyncCalls
:正在运行的异步
请求runningSyncCalls
:正在运行的同步
请求
1、同步请求就直接把请求添加到正在运行的同步请求队列runningSyncCalls中,
2、异步请求会做个判断:
- 如果正在运行的异步请求不超过
64
,而且同一个host下的异步请求不得超过5个
,则将请求添加到正在运行的同步请求队列中runningAsyncCalls
并开始执行请求,否则就添加到readyAsyncCalls
继续等待。
1.4 请求的处理
getResponseWithInterceptorChain
()这个方法才是真正发起请求并处理请求的地方
final class RealCall implements Call
Response getResponseWithInterceptorChain() throws IOException
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//这里可以看出,我们自定义的Interceptor会被优先执行
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);
Interceptor将网络请求、缓存、透明压缩等功能统一了起来,它的实现采用责任链模式,各司其职, 每个功能都是一个Interceptor
,上一级处理完成以后传递给下一级,它们最后连接成了一个Interceptor.Chain
位置决定功能,位置靠前的先执行,最后一个则复制与服务器通讯,请求从RetryAndFollowUpInterceptor
开始层层传递到CallServerInterceptor
,每一层都对请求做相应的处理,处理的结构再从CallServerInterceptor
层层返回给RetryAndFollowUpInterceptor
,最后请求的发起者获得了服务器返回的结果。
拓展:
责任链,顾名思义,就是用来处理相关事务责任的一条执行链,执行链上有多个节点,每个节点都有机会(条件匹配)处理请求事务,如果某个节点处理完了就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。
2、拦截器
如上所示责任链添加的顺序及作用如下表所示:
每个拦截器的方法都遵循这样的规则:
@Override public Response intercept(Chain chain) throws IOException
Request request = chain.request();
//1 Request阶段,该拦截器在Request阶段负责做的事情
//2 调用RealInterceptorChain.proceed(),其实是在递归调用下一个拦截器的intercept()方法
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
//3 Response阶段,完成了该拦截器在Response阶段负责做的事情,然后返回到上一层的拦截器。
return response;
从上面的描述可知:
- Request是按照interpretor的顺序正向处理,而Response是逆向处理的。这参考了OSI七层模型的原理。
CallServerInterceptor
相当于最底层的物理层,请求从上到逐层包装下发,响应从下到上再逐层包装返回。很漂亮的设计。
interceptor的执行顺序:RetryAndFollowUpInterceptor -> BridgeInterceptor ->
CacheInterceptor -> ConnectInterceptor -> CallServerInterceptor。
2.1 RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor负责失败重试以及重定向
2.2 BridgeInterceptor
重试与重定向拦截器只有在请求的过程中遇到异常或需要重定向的时候才有活干,在它收到请求后会把请求直接通过拦截器链交给下一个拦截器,也就是 BridgeInterceptor 处理。
之所以把 BridgeInterceptor 叫首部构建拦截器,是因为我们给 Request 设置的信息缺少了部分首部信息,这时就要 BridgeInterceptor 把缺失的首部放到 Request 中
。
2.3 CacheInterceptor
我们知道为了节省流量和提高响应速度
,减轻服务端的访问压力
,Okhttp是有自己的一套缓存机制的,CacheInterceptor就是用来负责读取缓存以及更新缓存的。
2.3.1、HTTP缓存原理
在HTTP 1.0时代,响应使用Expires
头标识缓存的有效期,其值是一个绝对时间
,比如Expires:Thu,31 Dec 2020 23:59:59 GMT
。当客户端再次发出网络请求时可比较当前时间和上次响应的expires时间进行比较,来决定是使用缓存还是发起新的请求。
- 使用
Expires
头最大的问题是它依赖客户端的本地时间
,如果用户自己修改了本地时间,就会导致无法准确的判断缓存是否过期。
因此,从HTTP 1.1 开始使用Cache-Control
头表示缓存状态,它的优先级高于Expires
,常见的取值为下面的一个或多个。
- 1、
private
,默认值,标识那些私有的业务逻辑数据,比如根据用户行为下发的推荐数据。该模式下网络链路中的代理服务器等节点不应该缓存这部分数据,因为没有实际意义。 - 2、
public
与private相反,public用于标识那些通用的业务数据,比如获取新闻列表,所有人看到的都是同一份数据,因此客户端、代理服务器都可以缓存。 - 3、
no-cache
可进行缓存,但在客户端使用缓存前必须要去服务端进行缓存资源有效性的验证,即下文的对比缓存部分,我们稍后介绍。 - 4、
max-age
表示缓存时长单位为秒,指一个时间段,比如一年,通常用于不经常变化的静态资源。 - 5、
no-store
任何节点禁止使用缓存。
2.3.2、强制缓存
在上述缓存头规约基础之上,强制缓存是指网络请求响应header标识了Expires或Cache-Control带了max-age信息,而此时客户端计算缓存并未过期,则可以直接使用本地缓存内容,而不用真正的发起一次网络请求。
2.3.3、协商缓存(对比缓存)
- 强制缓存最大的问题是,一旦服务端资源有更新,直到缓存时间截止前,客户端无法获取到最新的资源(除非请求时手动添加no-store头)
- 另外大部分情况下服务器的资源无法直接确定缓存失效时间,所以使用对比缓存更灵活一些。
使用Last-Modify / If-Modify-Since
头实现协商缓存,具体方法是服务端响应头添加Last-Modify
头标识资源的最后修改时间,单位为秒,当客户端再次发起请求时添加If-Modify-Since
头并赋值为上次请求拿到的Last-Modify
头的值。
服务端收到请求后自行判断缓存资源是否仍然有效,如果有效则返回状态码304同时body体为空,否则下发最新的资源数据。客户端如果发现状态码是304,则取出本地的缓存数据作为响应。
使用这套方案有一个问题,那就是资源文件使用最后修改时间有一定的局限性:
- 1、Last-Modify单位为秒,如果某些文件在一秒内被修改则并不能准确的标识修改时间。
- 2、资源修改时间并不能作为资源是否修改的唯一依据,比如资源文件是Daily Build的,每天都会生成新的,但是其实际内容可能并未改变。
因此,HTTP 还提供了另外一组头信息来处理缓存,ETag/If-None-Match
。流程与Last-Modify
一样,只是把服务端响应的头变成Last-Modify
,客户端发出的头变成If-None-Match
。ETag
是资源的唯一标识符,服务端资源变化一定会导致ETag变化。具体的生成方式有服务端控制,场景的影响因素包括,文件最终修改时间、文件大小、文件编号等等。
2.3.4、OKHttp的缓存实现
上面讲了这么多,实际上OKHttp就是将上述流程用代码实现了一下,即:
- 1、第一次拿到响应后根据头信息决定是否缓存。
- 2、下次请求时判断是否存在本地缓存,是否需要使用对比缓存、封装请求头信息等等。
- 3、如果缓存失效或者需要对比缓存则发出网络请求,否则使用本地缓存。
2.3.4.1、缓存策略
HTTP的缓存机制也是依赖于请求和响应header里的参数类实现的,最终响应是从缓存中去,还是从服务端重新拉取,HTTP的缓存机制的流程如下所示
上面提到强制缓存
使用的的两个标识:
- Expires:Expires的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。到期时间是服务端生成的,客户端和服务端的时间可能有误差。
- Cache-Control:Expires有个时间校验的问题,所有HTTP1.1采用Cache-Control替代Expires。
再来看看对比缓存
的两个标识:
1、Last-Modified/If-Modified-Since
Last-Modified 表示资源上次修改的时间。
当客户端发送第一次请求时,服务端返回资源上次修改的时间:
Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT
客户端再次发送,会在header里携带If-Modified-Since。将上次服务端返回的资源时间上传给服务端。
If-Modified-Since: Tue, 12 Jan 2016 09:31:27 GMT
服务端接收到客户端发来的资源修改时间,与自己当前的资源修改时间进行对比
- 如果自己的资源修改时间大于客户端发来的资源修改时间,则说明资源做过修改,则返回200表示需要重新请求资源,
- 否则返回304表示资源没有被修改,可以继续使用缓存。
上面是一种时间戳
标记资源是否修改的方法,还有一种资源标识码ETag
的方式来标记是否修改,如果标识码发生改变
,则说明资源已经被修改,ETag优先级高于Last-Modified
。
2、Etag/If-None-Match
ETag是资源文件的一种标识码,当客户端发送第一次请求时,服务端会返回当前资源的标识码:
ETag: "5694c7ef-24dc"
码客户端再次发送,会在header里携带上次服务端返回的资源标识码:
If-None-Match:"5694c7ef-24dc"
服务端接收到客户端发来的资源标识码,则会与自己当前的资源吗进行比较,
- 如果不同,则说明资源已经被修改,则返回200,
- 如果相同则说明资源没有被修改,返回304,客户端可以继续使用缓存。
2.3.4.2、Okhttp的缓存策略
Okhttp的缓存策略就是根据上述流程图实现的,具体的实现类是CacheStrategy
,CacheStrategy的构造函数里有两个参数:
CacheStrategy(Request networkRequest, Response cacheResponse)
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
这两个参数参数的含义如下:
- networkRequest:网络请求。
- cacheResponse:缓存响应,基于DiskLruCache实现的文件缓存,可以是请求中url的md5,value是文件中查询到的缓存,这个我们下面会说。
CacheStrategy就是利用这两个参数生成最终的策略,有点像map操作,将networkRequest
与cacheResponse
这两个值输入,处理之后再将这两个值输出,们的组合结果如下所示:
- 如果networkRequest为null,cacheResponse为null:only-if-cached(表明不进行网络请求,且缓存不存在或者过期,一定会返回503错误)。
- 如果networkRequest为null,cacheResponse为non-null:不进行网络请求,而且缓存可以使用,直接返回缓存,不用请求网络。
- 如果networkRequest为non-null,cacheResponse为null:需要进行网络请求,而且缓存不存在或者过期,直接访问网络。
- 如果networkRequest为non-null,cacheResponse为non-null:Header中含有ETag/Last-Modified标签,需要在条件请求下使用,还是需要访问网络。
那么这四种情况是如何判定的,我们来看一下。
CacheStrategy是利用Factory模式
进行构造的,CacheStrategy.Factory
对象构建以后,调用它的get()
方法即可获得具体的CacheStrategy
,CacheStrategy.Factory.get()
方法内部调用的是CacheStrategy.Factory.getCandidate()
方法,它是核心的实现。
public static class Factory
private CacheStrategy getCandidate()
//1. 如果缓存没有命中,就直接进行网络请求。
if (cacheResponse == null)
return new CacheStrategy(request, null);
//2. 如果TLS握手信息丢失,则返回直接进行连接。
if (request.isHttps() && cacheResponse.handshake() == null)
return new CacheStrategy(request, null);
//3. 根据response状态码,Expired时间和是否有no-cache标签就行判断是否进行直接访问。
if (!isCacheable(cacheResponse, request))
return new CacheStrategy(request, null);
//4. 如果请求header里有"no-cache"或者右条件GET请求(header里带有ETag/Since标签),则直接连接。
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request))
return new CacheStrategy(request, null);
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable())
return new CacheStrategy(null, cacheResponse);
//计算当前age的时间戳:now - sent + age
long ageMillis = cacheResponseAge();
//刷新时间,一般服务器设置为max-age
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1)以上是关于Android:安卓学习笔记之OkHttp原理的简单理解和使用的主要内容,如果未能解决你的问题,请参考以下文章
Android:安卓学习笔记之OkHttp原理的简单理解和使用
Android:安卓学习笔记之Binder 机制的简单理解和使用
Android:安卓学习笔记之Binder 机制的简单理解和使用
Android :安卓学习笔记之 Handler机制 的简单理解和使用