Okhttp基本用法和流程分析
OkHttp的时序图本文基于compile 'com.squareup.okhttp3:okhttp:3.0.1'
1.基本用法
这里我是将Okhttp和Retrofit一起使用的
Okhttp执行底层的网络请求,Retrofit负责网络调度,有关Retrofit的更多可参考我的另一篇博文:Retrofit基本用法和流程分析
1.初始化
public static void init() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
sClient = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.writeTimeout(300, TimeUnit.SECONDS)
.cache(new Cache(Constants.HTTP_CACHE_DIR, Constants.CACHE_SIZE))
.addInterceptor(logging)//第三方的日志拦截器
.addInterceptor(appIntercepter)//自定义的应用拦截器
.addNetworkInterceptor(netIntercepter)//自定义的网络拦截器
.build();
}
2.添加应用拦截器
//应用拦截器:主要用于设置公共参数,头信息,日志拦截等,有点类似Retrofit的Converter
private static Interceptor appIntercepter = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = processRequest(chain.request());
Response response = processResponse(chain.proceed(request));
return response;
}
};
//访问网络之前,处理Request(这里统一添加了Cookie)
private static Request processRequest(Request request) {
String session = CacheManager.restoreLoginInfo(BaseApplication.getContext()).getSession();
return request
.newBuilder()
.addHeader("Cookie", "JSESSIONID=" + session)
.build();
}
//访问网络之后,处理Response(这里没有做特别处理)
private static Response processResponse(Response response) {
return response;
}
这里再奉上一个缓存拦截器:离线读取本地缓存,在线获取最新数据(读取单个请求的请求头,亦可统一设置)。
这里用到了一个网络状态工具类
//应用拦截器:设置缓存策略
private static Interceptor cacheIntercepter = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//无网的时候强制使用缓存
if (NetUtil.getNetState() == NetUtil.NetState.NET_NO) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
//有网的时候读接口上的@Headers里的配置,你可以在这里进行统一的设置
if (NetUtil.getNetState() != NetUtil.NetState.NET_NO) {
String cacheControl = request.cacheControl().toString();
return response.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
} else {
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
return response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
}
};
OkHttp3中有一个Cache类是用来定义缓存的,此类详细介绍了几种缓存策略,具体可看此类源码。
- noCache :不使用缓存,全部走网络
- noStore : 不使用缓存,也不存储缓存
- onlyIfCached : 只使用缓存
- maxAge :设置最大失效时间,失效则不使用
- maxStale :设置最大失效时间,失效则不使用
- minFresh :设置最小有效时间,失效则不使用
- FORCE_NETWORK : 强制走网络
- FORCE_CACHE :强制走缓存
关于max-age和max-stale
max-stale在请求头设置有效,在响应头设置无效。
max-stale和max-age同时设置的时候,缓存失效的时间按最长的算。
我这里借用了别人的一个测试(太懒了,你有兴趣可以自己测试下):
测试结果:
在请求头中设置了:Cache-Control: public, max-age=60,max-stale=120,响应头的Cache-Control和请求头一样。
- 在第一次请求数据到一分钟之内,响应头有:Cache-Control: public, max-age=60,max-stale=120
- 在1分钟到3分钟在之间,响应头有:Cache-Control: public, max-age=60,max-stale=120
Warning: 110 HttpURLConnection "Response is stale"
可以发现多了一个Warning。 - 三分钟的时候:重新请求了数据,如此循环,如果到了重新请求的节点此时没有网,则请求失败。
另外关于缓存有一个rxcache也可以试试。
3.添加网络拦截器
//网络拦截器:主要用于重试或重写
private static Interceptor netIntercepter = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
int tryCount = 0;
while (!response.isSuccessful() && tryCount < sMaxTryCount) {
tryCount++;
response = chain.proceed(request);
}
return response;
}
};
关于OkHttp的拦截机制,我觉得这是OkHttp最牛逼的地方之一!
先给大家看个概览图,之后会在OkHttp的特性中详细介绍下。
OkHttp的拦截机制
4.简单的异步请求
private static void testAsync() {
Request request = new Request.Builder()
.url("http://baidu.com")
.build();
sClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
LogUtil.print(e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
LogUtil.print(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
LogUtil.print(response.body().string());
}
});
}
2.OkHttp的特性
Q1:为什么使用OkHttp?
- 支持HTTP2/SPDY黑科技
- socket自动选择最好路线,并支持自动重连
- 拥有自动维护的socket连接池,减少握手次数
- 拥有队列线程池,轻松写并发
- 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
- 基于Headers的缓存策略
Q2:应用拦截器和网络拦截器的区别?
OkHttp的应用拦截器和网络拦截器addInterceptor:设置应用拦截器,主要用于设置公共参数,头信息,日志拦截等
addNetworkInterceptor:设置网络拦截器,主要用于重试或重写
例如:初始请求http://baidu.com 会重定向1次到https://baidu.com 。
则应用拦截器会执行1次,返回的是https的响应
网络拦截器会执行2次,第一次返回http的响应,第二次返回https的响应
应用拦截器:
- 不需要担心中间过程的响应,如重定向和重试.
- 总是只调用一次,即使HTTP响应是从缓存中获取.
- 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
- 允许短路而不调用 Chain.proceed(),即中止调用.
- 允许重试,使 Chain.proceed()调用多次.
网络拦截器
- 能够操作中间过程的响应,如重定向和重试.
- 当网络短路而返回缓存响应时不被调用.
- 只观察在网络上传输的数据.
- 携带请求来访问连接.
3.OkHttp的流程分析
二话不说,先上几张图:
OkHttp的请求流程 OkHttp的总体设计
1.先看初始化时,通过建造者模式创建OkHttpClient
2.再看执行请求时,创建Request的方法
3.再看异步请求Request的方法,先通过newCall(),将Request转成Call
4.然后加入请求队列
5.Call.enqueue()是抽象方法,实现在这:
6.调用了Dispatcher.enqueue(),通过一个线程池来执行,超过最大请求数后则先加入准备请求的队列中
7.这里的call是一个AsynCall,继承自NamedRunnable
8.在NamedRunnable的run()方法中,调用了execute()抽象方法,于是我们去找它的实现类AsyncCall的execute()方法
9.调用了getResponseWithInterceptorChain()方法
10.调用了ApplicationInterceptorChain.proceed(),如果有其他的应用拦截器的话,就会遍历拦截器集合,执行每一个拦截的intercept()方法
而通过前面的自定义应用拦截器,我们知道intercept()中其实也会调用proceed(),这样迭代多次后,最终还是会执行getResponse方法()
13.getResponse()这个方法有点长,一次截图截不完
14.调用httpEngine.sendRequest()方法,这个方法有点长,一次截图截不完
15.先从 Cache 中判断当前请求是否可以从缓存中返回
16.没有Cache则连接网络
17.调用streamAllocation.newStream()
18.寻找可用的socket连接
19.连接到socket连接层
20.创建了FramedConnection的实例
21.调用了Reader的execute()
苍天!越往下面越难找到头绪了,先暂且告一段落吧,以后再来好好深入。