@[toc]
一.Android实现网络请求的主流方法
HttpURLConnection和HttpClient:这两种方式都支持HTTPS协议、以流的形式进行上传和下载、配置超时时间、IPv6、以及连接池等功能。
1.1 HttpClient
(1)简介
DefaultHttpClient和它的兄弟AndroidHttpClient都是HttpClient具体的实现类,它们都拥有众多的API,而且实现比较稳定,bug数量也很少。 但同时也由于HttpClient的API数量过多,使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,结构复杂,维护成本高。 Android SDK中包含了HttpClient,在Android6.0版本直接删除了HttpClient类库。
(2)实现
LoginHttpClientUtils.java
public Boolean LoginGet(Context context,String username,String password){
try{
String path = "http://192.168.1.138:8080/chyWebTest/LoginServlet?username="+username+"&password="+password;
Boolean isSuccess = false;
//1.创建一个HttpClient对象
HttpClient httpClient = new DefaultHttpClient();
//2.设置请求方式
HttpGet httpGet = new HttpGet(path);
//3.执行一个Http请求(返回HttpResponse)
HttpResponse httpResponse = httpClient.execute(httpGet);
//4.获取请求的状态码
StatusLine statusLine = httpResponse.getStatusLine();
int code = statusLine.getStatusCode();
//5.判断状态吗后获取内容
if(code==200){
//获取实体内容,中封装有Http请求返回流信息
HttpEntity entity = httpResponse.getEntity();
InputStream inputStream = entity.getContent();
String result = StreamUtil.StreamToString(inputStream);
if(result.equals(("success"))){
isSuccess = true;
}
}
return isSuccess;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
LoginHttpClientActivity.java
//主线程创建一个Handler对象
handler = new Handler(){
//calledFromWrongThreadException从错误的线程调用(UI操作只能通过主线程,子线程不能更新UI空间)
@Override
public void handleMessage(Message msg) {
//重写handler的handleMessage方法,用来接受并处理子线程发来的消息,并可执行UI操作
if(msg.obj.equals("success")) {
Toast.makeText(LoginHttpClientActivity.this,"登录成功",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(LoginHttpClientActivity.this,"登录失败",Toast.LENGTH_SHORT).show();
}
}
};
//子线程创建一个Message对象,将获取的数据绑定给Msg,通过主线程中handler对象将msg发送给主线程
Message msg = Message.obtain();
if(result)msg.obj = "success";
else msg.obj = "fail";
handler.sendMessage(msg);
}
//创建一个新线程执行HttpClient网络请求
Thread getThread = new Thread(new LoginGetThread());
getThread.start();
class LoginGetThread implements Runnable{
public void run(){
login(username,password,0);
}
}
1.2 HttpURLConnection
(1)简介
HttpURLConnection是一种多用途、轻量极的HTTP客户端,使用它来进行HTTP操作可以适用于大多数的应用程序。虽然HttpURLConnection的API提供的比较简单,但是同时这也使得我们可以更加容易地去使用和扩展它。 Android2.2前有个重大BUG:调用close()函数会影响连接池,导致链接复用失效。 比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能:
private void disableConnectionReuseIfNecessary() {
// 这是一个2.2版本之前的bug
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
Android2.2后默认开启了gzip压缩、提高https性能(2.3)与缓冲机制(4.0)
(2)实现
1、创建一个UrlConnManager类,然后里面提供getHttpURLConnection()方法用于配置默认的参数并返回HttpURLConnection:
public static HttpURLConnection getHttpURLConnection(String url){
HttpURLConnection mHttpURLConnection=null;
try {
URL mUrl=new URL(url);
mHttpURLConnection=(HttpURLConnection)mUrl.openConnection();
//设置链接超时时间
mHttpURLConnection.setConnectTimeout(15000);
//设置读取超时时间
mHttpURLConnection.setReadTimeout(15000);
//设置请求参数
mHttpURLConnection.setRequestMethod("POST");
//添加Header
mHttpURLConnection.setRequestProperty("Connection","Keep-Alive");
//接收输入流
mHttpURLConnection.setDoInput(true);
//传递参数时需要开启
mHttpURLConnection.setDoOutput(true);
} catch (IOException e) {
e.printStackTrace();
}
return mHttpURLConnection ;
}
发送POST请求,所以在UrlConnManager类中再写一个postParams()方法用来组织一下请求参数并将请求参数写入到输出流中
public static void postParams(OutputStream output,List<NameValuePair>paramsList) throws IOException{
StringBuilder mStringBuilder=new StringBuilder();
for (NameValuePair pair:paramsList){
if(!TextUtils.isEmpty(mStringBuilder)){
mStringBuilder.append("&");
}
mStringBuilder.append(URLEncoder.encode(pair.getName(),"UTF-8"));
mStringBuilder.append("=");
mStringBuilder.append(URLEncoder.encode(pair.getValue(),"UTF-8"));
}
BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,"UTF-8"));
writer.write(mStringBuilder.toString());
writer.flush();
writer.close();
}
接下来我们添加请求参数,调用postParams()方法将请求的参数组织好传给HttpURLConnection的输出流,请求连接并处理返回的结果
private void useHttpUrlConnectionPost(String url) {
InputStream mInputStream = null;
HttpURLConnection mHttpURLConnection = UrlConnManager.getHttpURLConnection(url);
try {
List<NameValuePair> postParams = new ArrayList<>();
//要传递的参数
postParams.add(new BasicNameValuePair("username", "moon"));
postParams.add(new BasicNameValuePair("password", "123"));
UrlConnManager.postParams(mHttpURLConnection.getOutputStream(), postParams);
mHttpURLConnection.connect();
mInputStream = mHttpURLConnection.getInputStream();
int code = mHttpURLConnection.getResponseCode();
String respose = converStreamToString(mInputStream);
Log.i("wangshu", "请求状态码:" + code + "\n请求结果:\n" + respose);
mInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
最后开启线程请求网络
private void useHttpUrlConnectionGetThread() {
new Thread(new Runnable() {
@Override
public void run() {
useHttpUrlConnectionPost("http://www.baidu.com");
}
}).start();
}
1.3 对比
Android2.2前不建议使用HttpURLConnection,Android4.4后,底层实现被OkHttp替换 Android5.0后HttpClient被官方弃用 所以,在Android 2.2版本以及之前的版本使用HttpClient是较好的选择,而在Android 2.3版本及以后,HttpURLConnection则是最佳的选择,它的API简单,体积较小,因而非常适用于Android项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。另外在Android 6.0版本中,HttpClient库被移除了,HttpURLConnection则是以后我们唯一的选择。
二.主流网络请求库
2.1 简介
网络请求开源库是一个将 网络请求+异步+数据处理 封装好的类库(网络请求是Android网络请求原生方法HttpClient或HttpURLConnection,异步包括多线程、线程池,数据处理包括序列化和反序列化) 使用网络请求库后,实现网络请求的需求同时不需要考虑:异步请求、线程池、缓存等等;降低开发难度,缩短开发周期,使用方便
2.2 对比(Android-Async-Http、Volley、OkHttp、Retrofit)
三.okHttp
3.1 简介
(1)支持http2/SPDY黑科技,共享同一个Socket来处理同一个服务器的所有请求(同一域名的所有请求stream共享同一个tcp连接),解决了HOL Blocking SPDY(读作“SPeeDY”)是Google开发的基于TCP的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY是对HTTP协议的加强。新协议的功能包括数据流的多路复用、支持服务器推送技术、请求优先级、HTTP报头压缩以及强制使用SSL传输协议。 (2)socket自动选择最好路线,并支持自动重连 (3)拥有自动维护的socket连接池,减少握手次数,减少请求延时 (4)拥有队列线程池,轻松写并发 (5)拥有Interceptors(拦截器)轻松处理请求与响应(透明GZIP压缩,转换从而减少数据流量) (6)基于Headers的缓存策略减少重复的网络请求
3.2 使用步骤
(1)添加okHttp和okIo
(Okio是一款轻量级IO框架,是著名网络框架OkHttp的基石。Okio结合了java.io和java.nio,提供阻塞IO和非阻塞IO的功能,同时也对缓存等底层结构做了优化,能让你更轻快的获得、存储和处理数据。)
(2)创建OkHttpClient对象
(3)get/post请求数据
(4)get/post调用
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder().
url("https://github.com/cozing").
build();
Call call = client.newCall(request);
try {
//1.同步请求调用的方法是call.execute(),内部采用的是线程阻塞(一直等待直到线程返回结果)方式直接将结果返回到Response
Response response = call.execute();
//2.异步请求调用的方法是call.enqueue(Callback callback),该方法需要传入一个Callback等待结果回调的接口
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.w("cozing", "请求失败");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.w("cozing", "请求成功");
}
});
} catch (IOException e) {
e.printStackTrace();
}
3.3 源码解读
(一)整体框架
(二)工作原理
2.1 OkHttpClient 构造
定义一个OkHttpClient,OkHttpClient构造函数及其配置如下。
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.retryOnConnectionFailure(true)
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.build();
了解OkHttpClient属性
final Dispatcher dispatcher;//调度器
final @Nullable
Proxy proxy;//代理
final List<Protocol> protocols;//协议
final List<ConnectionSpec> connectionSpecs;//传输层版本和连接协议
final List<Interceptor> interceptors;//拦截器
final List<Interceptor> networkInterceptors;//网络拦截器
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;//代理选择器
final CookieJar cookieJar;//cookie
final @Nullable
Cache cache;//cache 缓存
final @Nullable
InternalCache internalCache;//内部缓存
final SocketFactory socketFactory;//socket 工厂
final @Nullable
SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https
final @Nullable
CertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名
final HostnameVerifier hostnameVerifier;//主机名字确认
final CertificatePinner certificatePinner;//证书链
final Authenticator proxyAuthenticator;//代理身份验证
final Authenticator authenticator;//本地省份验证
final ConnectionPool connectionPool;//链接池 复用连接
final Dns dns; //域名
final boolean followSslRedirects;//安全套接层重定向
final boolean followRedirects;//本地重定向
final boolean retryOnConnectionFailure;//重试连接失败
final int connectTimeout;//连接超时
final int readTimeout;//读取超时
final int writeTimeout;//写入超时
这里OkHttpClient的构造采用了建造者模式。
2.2 OkHttpClient 请求网络
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
定义一个请求Request,Request中包含客户请求的参数:url、method、headers、requestBody和tag,也采用了建造者模式。 实际请求网络的是Call接口,OkHttp实现了Call.Factory接口,其真正的实现类是RealCall,OkHttp将真正的请求交给了RealCall,RealCall实现了Call中方法完成请求。这里采用了简单工厂模式。
interface Factory {
Call newCall(Request request);
}
...
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
RealCall中主要方法有
- 同步请求:client.newCall(request).execute
- 异步请求:client.newCall(request).enqueue(常用)
异步请求 RealCall.enqueue()
RealCall.java
@Override public void enqueue(Callback responseCallback) {
//TODO 不能重复执行
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//TODO 交给 dispatcher调度器 进行调度
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
- synchronized (this) 确保每个call只能被执行一次不能重复执行
- Dispatcher 调度器 将 Call 加入队列,并通过线程池执行 Call 利用dispatcher调度器,来进行实际的执行client.dispatcher().enqueue(new AsyncCall(responseCallback));,在上面的OkHttpClient.Builder可以看出 已经初始化了Dispatcher。 Dispatcher的属性和方法
//TODO 同时能进行的最大请求数
private int maxRequests = 64;
//TODO 同时请求的相同HOST的最大个数 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
//TODO 如 https://restapi.amap.com restapi.amap.com - host
private int maxRequestsPerHost = 5;
/**
* Ready async calls in the order they'll be run.
* TODO 双端队列,支持首尾两端 双向开口可进可出,方便移除
* 异步等待队列
*
*/
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**
* Running asynchronous calls. Includes canceled calls that haven't finished yet.
* TODO 正在进行的异步队列
*/
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
Dispatcher管理两个异步请求队列,可对多个并发网络请求进行处理。 Dispatcher.enqueue方法用于执行异步请求,实现如下
//TODO 执行异步请求
synchronized void enqueue(AsyncCall call) {
//TODO 同时请求不能超过并发数(64,可配置调度器调整)
//TODO okhttp会使用共享主机即 地址相同的会共享socket
//TODO 同一个host最多允许5条线程通知执行请求
if (runningAsyncCalls.size() < maxRequests &&
runningCallsForHost(call) < maxRequestsPerHost) {
//TODO 加入运行队列 并交给线程池执行
runningAsyncCalls.add(call);
//TODO AsyncCall 是一个runnable,放到线程池中去执行,查看其execute实现
executorService().execute(call);
} else {
//TODO 加入等候队列
readyAsyncCalls.add(call);
}
}
可见Dispatcher将Call加入队列中(若同时请求数未超过最大值,则加入运行队列,放到线程池中执行;否则加入等待队列),然后通过线程池执行call。 executorService() 本质上是一个线程池执行方法,用于创建一个线程池
public synchronized ExecutorService executorService() {
if (executorService == null) {
//TODO 线程池的相关概念 需要理解
//TODO 核心线程 最大线程 非核心线程闲置60秒回收 任务队列
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
false));
}
return executorService;
}
- 在 ConnectionPool 线程池中执行请求 AsyncCall 异步请求中Call实际上为AsyncCall,继承自NamedRunnable
final class AsyncCall extends NamedRunnable
NamedRunnable
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
因此 AsyncCall 其实就是一个 Runnable,线程池实际上就是执行了execute()。 AsyncCall的execute()
final class AsyncCall extends NamedRunnable {
@Override protected void execute() {
boolean signalledCallback = false;
try {
//TODO 责任链模式
//TODO 拦截器链 执行请求
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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
//TODO 移除队列
client.dispatcher().finished(this);
}
}
}
从上述代码可以看出真正执行请求的是getResponseWithInterceptorChain(); 然后通过回调将Response返回给用户。
- 通过拦截器链 RealInterceptorChain 通过 责任链模式 真正执行网络请求 真正的执行网络请求和返回响应结果:getResponseWithInterceptorChain()
//TODO 核心代码 开始真正的执行网络请求
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
//TODO 责任链
List<Interceptor> interceptors = new ArrayList<>();
//TODO 在配置okhttpClient 时设置的intercept 由用户自己设置
interceptors.addAll(client.interceptors());
//TODO 负责处理失败后的重试与重定向
interceptors.add(retryAndFollowUpInterceptor);
//TODO 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息
//TODO 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//TODO 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应
//TODO 设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
//TODO 可配置用户自己设置的缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//TODO 连接服务器 负责和服务器建立连接 这里才是真正的请求网络
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//TODO 配置okhttpClient 时设置的networkInterceptors
//TODO 返回观察单个网络请求和响应的不可变拦截器列表。
interceptors.addAll(client.networkInterceptors());
}
//TODO 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据
//TODO 进行http请求报文的封装与请求报文的解析
interceptors.add(new CallServerInterceptor(forWebSocket));
//TODO 创建责任链
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//TODO 执行责任链
return chain.proceed(originalRequest);
}
一共执行6种拦截器,一种是用户自定义拦截器client.interceptors(),另外五种是OkHttp自带拦截器:
- RetryAndFollowUpInterceptor,重试那些失败或者redirect的请求。
- BridgeInterceptor,请求之前对响应头做了一些检查,并添加一些头,然后在请求之后对响应做一些处理(gzip解压or设置cookie)。
- CacheInterceptor,根据用户是否有设置cache,如果有的话,则从用户的cache中获取当前请求的缓存。
- ConnectInterceptor,复用连接池中的连接,如果没有就与服务器建立新的socket连接。
- CallServerInterceptor,负责发送请求和获取响应。
从上述代码中,可以看出都实现了Interceptor接口,这是okhttp最核心的部分,采用责任链的模式来使每个功能分开,每个Interceptor自行完成自己的任务,并且将不属于自己的任务交给下一个,简化了各自的责任和逻辑。
责任链模式是OkHttp中最核心的设计模式。
我们着重分析一下,okhttp的设计实现,如何通过责任链来进行传递返回数据的。上述代码中可以看出interceptors,是传递到了RealInterceptorChain该类实现了Interceptor.Chain,并且执行了chain.proceed(originalRequest)。核心代码就是chain.proceed() 通过该方法进行责任链的执行。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
//TODO 获取下一个拦截链,即链中的拦截器集合index+1
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//TODO 执行当前的拦截器-如果在配置okhttpClient,时没有设置intercept默认是先执行:retryAndFollowUpInterceptor 拦截器
Interceptor interceptor = interceptors.get(index);
//TODO 执行下一个拦截器
Response response = interceptor.intercept(next);
return response;
}
从上述代码,我们可以知道,获取下一个(index+1)RealInterceptorChain 责任链,然后 执行当前(index)责任链interceptors.get(index),最后返回下一个责任链的Response。
其实就是按责任链顺序递归执行了拦截器
这样设计的一个好处就是,责任链中每个拦截器都会执行chain.proceed()方法之前的代码,等责任链最后一个拦截器执行完毕后会返回最终的响应数据,而chain.proceed() 方法会得到最终的响应数据,这时就会执行每个拦截器的chain.proceed()方法之后的代码,其实就是对响应数据的一些操作。执行过程如下图:
CacheInterceptor 缓存拦截器就是很好的证明,我们来通过CacheInterceptor 缓存拦截器来进行分析,大家就会明白了。
CacheInterceptor 的实现如下:
首先我们先分析上部分代码当没有网络的情况下是如何处理获取缓存的。
@Override public Response intercept(Chain chain) throws IOException
{
//TODO 获取request对应缓存的Response 如果用户没有配置缓存拦截器 cacheCandidate == null
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
//TODO 执行响应缓存策略
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//TODO 如果networkRequest == null 则说明不使用网络请求
Request networkRequest = strategy.networkRequest;
//TODO 获取缓存中(CacheStrategy)的Response
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
//TODO 缓存无效 关闭资源
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
//TODO networkRequest == null 不实用网路请求 且没有缓存 cacheResponse == null 返回失败
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
//TODO 不使用网络请求 且存在缓存 直接返回响应
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
}
上述的代码,主要做了几件事: 如果用户自己配置了缓存拦截器,cacheCandidate = cache.Response 获取用户自己存储的Response,否则 cacheCandidate = null;同时从CacheStrategy 获取cacheResponse 和 networkRequest 如果cacheCandidate != null 而 cacheResponse == null 说明缓存无效清除cacheCandidate缓存。 如果networkRequest == null 说明没有网络,cacheResponse == null 没有缓存,返回失败的信息,责任链此时也就终止,不会在往下继续执行。 如果networkRequest == null 说明没有网络,cacheResponse != null 有缓存,返回缓存的信息,责任链此时也就终止,不会在往下继续执行。 上部分代码,其实就是没有网络的时候的处理。 那么下部分代码肯定是,有网络的时候处理
//TODO 执行下一个拦截器
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
//TODO 网络请求 回来 更新缓存
// If we have a cache response too, then we're doing a conditional get.
//TODO 如果存在缓存 更新
if (cacheResponse != null) {
//TODO 304响应码 自从上次请求后,请求需要响应的内容未发生改变
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//TODO 缓存Response
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
下部分代码主要做了这几件事: 执行下一个拦截器,也就是请求网络 责任链执行完毕后,会返回最终响应数据,如果缓存存在更新缓存,如果缓存不存在加入到缓存中去。 这样就体现出了,责任链这样实现的好处了,当责任链执行完毕,如果拦截器想要拿到最终的数据做其他的逻辑处理等,这样就不用在做其他的调用方法逻辑了,直接在当前的拦截器就可以拿到最终的数据。 这也是okhttp设计的最优雅最核心的功能。
- 执行调度器完成方法,移除队列 AsyncCall的execute()中(见第3步)中finally 执行了client.dispatcher().finished(this); 通过调度器移除队列,并且判断是否存在等待队列,如果存在,检查执行队列是否达到最大值,如果没有将等待队列变为执行队列。这样也就确保了等待队列被执行。
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//TODO calls 移除队列
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//TODO 检查是否为异步请求,检查等候的队列 readyAsyncCalls,如果存在等候队列,则将等候队列加入执行队列
if (promoteCalls) promoteCalls();
//TODO 运行队列的数量
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
//闲置调用
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
private void promoteCalls() {
//TODO 检查 运行队列 与 等待队列
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//TODO 将等待队列加入到运行队列中
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//TODO 相同host的请求没有达到最大,加入运行队列
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
同步请求 RealCall.excute()
//TODO 同步执行请求 直接返回一个请求的结果
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//TODO 调用监听的开始方法
eventListener.callStart(this);
try {
//TODO 交给调度器去执行
client.dispatcher().executed(this);
//TODO 获取请求的返回数据
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//TODO 执行调度器的完成方法 移除队列
client.dispatcher().finished(this);
}
}
- synchronized (this) 避免重复执行
- Dispatcher 调度器 将 Call 加入同步执行队列
//TODO 调度器执行同步请求
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
- 通过拦截器链 RealInterceptorChain 通过 责任链模式 真正执行网络请求 getResponseWithInterceptorChain()最核心的代码,请求网络得到响应数据,返回给用户。
- 执行调度器完成方法,移除队列 client.dispatcher().finished(this); 执行调度器的完成方法 移除队列
2.3 OkHttp 执行流程总结
- OkhttpClient 实现了Call.Fctory,负责为Request 创建 Call;
- RealCall 为Call的具体实现,其enqueue() 异步请求接口通过Dispatcher()调度器利用ExcutorService实现,而最终进行网络请求时和同步的execute()接口一致,都是通过 getResponseWithInterceptorChain() 函数实现
- getResponseWithInterceptorChain() 中利用 Interceptor 链条,责任链模式 分层实现缓存、透明压缩、网络 IO 等功能;最终将响应数据返回给用户。
2.4 OkHttp 设计模式总结
- 建造者模式 创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使 得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。 OkHttp中HttpClient、Request构造便是通过建造者模式 OkHttpClient
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.retryOnConnectionFailure(true)
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.build();
Request
Request request = new Request.Builder()
.url(url)
.build();
- 简单工厂模式 okhttp 实现了Call.Factory接口
interface Factory {
Call newCall(Request request);
}
我们看一下okhttpClient 如何实现的Call接口,代码如下
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
可以看出 真正的请求交给了 RealCall 类,并且RealCall 实现了Call方法,RealCall是真正的核心代码。
- 责任链模式 责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。 OkHttp责任链验证 (1)模拟拦截器接口
public interface Interceptor {
String interceptor(Chain chain);
interface Chain {
String request();
String proceed(String request);
}
}
(2)实现具体拦截器
public class BridgeInterceptor implements Interceptor {
@Override
public String interceptor(Chain chain) {
System.out.println("执行 BridgeInterceptor 拦截器之前代码");
String proceed = chain.proceed(chain.request());
System.out.println("执行 BridgeInterceptor 拦截器之后代码 得到最终数据:"+proceed);
return proceed;
}
}
public class RetryAndFollowInterceptor implements Interceptor {
@Override
public String interceptor(Chain chain) {
System.out.println("执行 RetryAndFollowInterceptor 拦截器之前代码");
String proceed = chain.proceed(chain.request());
System.out.println("执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:" + proceed);
return proceed;
}
}
public class CacheInterceptor implements Interceptor {
@Override
public String interceptor(Chain chain) {
System.out.println("执行 CacheInterceptor 最后一个拦截器 返回最终数据");
return "success";
}
}
(3)实现拦截器链接口
public class RealInterceptorChain implements Interceptor.Chain {
private List<Interceptor> interceptors;
private int index;
private String request;
public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {
this.interceptors = interceptors;
this.index = index;
this.request = request;
}
@Override
public String request() {
return request;
}
@Override
public String proceed(String request) {
if (index >= interceptors.size()) return null;
//获取下一个责任链
RealInterceptorChain next = new RealInterceptorChain(interceptors, index+1, request);
// 执行当前的拦截器
Interceptor interceptor = interceptors.get(index);
return interceptor.interceptor(next);
}
}
(4)测试及结果
List<Interceptor> interceptors = new ArrayList<>();
interceptors.add(new BridgeInterceptor());
interceptors.add(new RetryAndFollowInterceptor());
interceptors.add(new CacheInterceptor());
RealInterceptorChain request = new RealInterceptorChain(interceptors, 0, "request");
request.proceed("request");
打印出的log日志
执行 BridgeInterceptor 拦截器之前代码
执行 RetryAndFollowInterceptor 拦截器之前代码
执行 CacheInterceptor 最后一个拦截器 返回最终数据
执行 RetryAndFollowInterceptor 拦截器之后代码 得到最终数据:success
执行 BridgeInterceptor 拦截器之后代码 得到最终数据:success
这也是OkHttp的核心设计思想
(三)源码解读
3.1 重要的类
(1)Route路由:对地址Adress的一个封装类
RouteSelector路由选择器:在OKhttp中其实其作用也就是返回一个可用的Route对象
(2)Platform平台:用于针对不同平台适应性
(3)RealConnection连接:Okhttp正式发起网络请求所使用的对象
ConnectionPool连接池:管理HTTP和SPDY连接的重用,减少网络延迟。连接池是将已经创建好的连接保存在一个缓冲池中,当有请求来时,直接使用已经创建好的连接。
每次请求生成StreamAllocation对象请求链接时,首先要做的不是new 一个新的RealConnection对象,而是从链接池中获取已经存在的并且可以复用的RealConnection,如果找不到可用的链接,则才new 一个新的RealConnection,并将新的连接放入连接池,等待其他请求复用
(4)Call请求(Request\Response):代表实际的http请求,它是连接Request和response的桥梁。由于重写,重定向,跟进和重试,你简单的请求Call可能产生多个请求Request和响应Response。OkHttp会使用Call来模化满足请求的任务,然而中间的请求和响应是必要的(重定向处理和IP出错)
Call执行有两种方式:
Synchronous:线程会阻塞直到响应可读。
Asynchronous:在一个线程中入队请求,当你的响应可读时在另外一个线程获取回调。
线程中的请求取消、失败、未完成,写请求主体和读响应主体代码会遇到IOException
(5)Dispatchar调度器:Dispatcher是okhttp3的任务调度核心类,负责管理同步和异步的请求,管理每一个请求任务的请求状态,并且其内部维护了一个线程池用于执行相应的请求,Dispatcher实现框架:
Dispatcher维护了三个队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//等待执行的异步队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//正在执行的异步队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//同步队列
一个线程池
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
5.1)Deque是一个双向队列接口,Deque接口具有丰富的抽象数据形式,它支持从队列两端点检索和插入元素 5.2)当需要执行的线程大于所能承受的最大范围时,就把未能及时执行的任务保存在readyAsyncCalls队列中。当线程池有空余线程可以执行时,会调用promoteCall()方法把等待队列readyAsyncCalls中的任务放到线程池执行,并把任务转移到runningAsyncCalls队列中
(6)Interceptor拦截器:拦截器是一个强大的机制,它可以监控,重写和重试Calls,OkHttp自带拦截器有5种: 1、RetryAndFollowUpInterceptor,重试那些失败或者redirect的请求。 2、BridgeInterceptor,请求之前对响应头做了一些检查,并添加一些头,然后在请求之后对响应做一些处理(gzip解压or设置cookie)。 3、CacheInterceptor,根据用户是否有设置cache,如果有的话,则从用户的cache中获取当前请求的缓存。 4、ConnectInterceptor,复用连接池中的连接,如果没有就与服务器建立新的socket连接。 5、CallServerInterceptor,负责发送请求和获取响应。 (7)Cache缓存:响应缓存 (8)OkHttpClient客户端:OkHttpClient是okhttp3框架的客户端,用于发送http请求(Requests)和读取读取交易返回数据(Responses)
3.2 请求流程
(1)创建OkHttpClient
创建OkHttpClient对象,OkHttpClient是okhttp3框架的客户端,用于发送http请求(Requests)和读取读取交易返回数据(Responses)。 官方建议使用单例创建OkHttpClient,即一个进程中只创建一次即可,以后的每次交易都使用该实例发送交易。这是因为OkHttpClient拥有自己的连接池和线程池,这些连接池和线程池可以重复使用,这样做利于减少延迟和节省内存。
//采用构建者模式
OkHttpClient client = new OkHttpClient.Builder().build();
自定义Builder内部的每一个参数属性
(2)创建Call对象,并发起同步/异步请求
一个Call对象表示一次请求,Call其实是一个接口对象,它的具体实现类是RealCall
//采用构建者模式
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder().
url("https://github.com/cozing").
build();
Call call = client.newCall(request);
2.1)传入Request对象,表示客户请求的参数
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
2.2)Call创建,实际创建RealCall对象
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
final EventListener.Factory eventListenerFactory = client.eventListenerFactory();
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
// TODO(jwilson): this is unsafe publication and not threadsafe.
this.eventListener = eventListenerFactory.create(this);
}
okhttp3中提供了两种交易方式:一种是同步请求,第二种是异步请求。同步请求调用call.execute()方法,异步请求调用call.enqueue(Callback callback)方法
(3)Dispatcher 调度器对同步/异步请求进行处理
3.1)同步请求 call.execute()【RealCall.java】
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
//1.调用调度器dispatcher的executed方法
client.dispatcher().executed(this);
//2.通过拦截器对请求数据与拦截数据进行处理
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
//3.调用调度器dispatcher的finish方法
client.dispatcher().finished(this);
}
}
1.调用调度器dispatcher的executed方法,将请求的RealCall放入runningSyncCalls队列中【Dispatcher.java】
public final class Dispatcher {
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
3.2)异步请求 call.enqueue(Callback callback)实现方式【RealCall.java】
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//调用调度器dispatcher的enqueue方法
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
调度器dispatcher的equeue(new AsyncCall(responseCallback))方法【dispatcher.java】
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//如果当前运行中的请求数小于设定的最大请求数64,且当前运行中同一主机的请求数小于设定的最大请求数5
runningAsyncCalls.add(call); //将本次请求call加入正在执行异步队列
executorService().execute(call); //执行请求call
} else {
readyAsyncCalls.add(call); //将本次请求call加入等待执行异步队列,等到有空闲的请求线程时,会从该队列中取出请求并执行
}
}
执行请求executorService().execute(call)【dispatcher.java】 调度器Dispatcher内部维护了一个ThreadPoolExecutor线程池executorService(),并直接将call对象传入线程池执行。这里的Call对象的实现对象是AsyncCall是Call的内部类,继承自NamedRunnable,用来开启一个线程。 当网络请求线程池执行该线程的run()方法时,会调用AsyncCall的execute()的方法,最后在execute()方法内部调用了和同步请求方法一样的getResponseWithInterceptorChain()。
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
AsyncCall
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);
}
}
}
(4)getResponseWithInterceptorChain()/拦截器链
【okhttp3中的精髓设计之一】
这个方法是通过拦截器链对请求数据和返回数据进行处理,内部采用责任链模式,将每一个拦截器对应负责的处理任务进行严格分配,最后将交易结果返回并回调到暴露给调用者的接口上。
依次添加了用户自定义的interceptor、retryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、 networkInterceptors、CallServerInterceptor,并将这些拦截器传递给了这个RealInterceptorChain。拦截器之所以可以依次调用,并最终再从后先前返回Response。
getResponseWithInterceptorChain()【RealCall.java】
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);
}
调用拦截器具体处理类RealInterceptorChain的proceed()方法来实现拦截: 执行当前拦截器的Intercept方法,并调用下一个(index+1)拦截器。下一个(index+1)拦截器的调用依赖于当前拦截器的Intercept方法中,对RealInterceptorChain的proceed方法的调用 【XXInterceptor】
return realChain.proceed(request, streamAllocation, httpCodec, connection);
当前拦截器的Response依赖于下一个拦截器的Intercept的Response。因此,就会沿着这条拦截器链依次调用每一个拦截器,当执行到最后一个拦截器之后,就会沿着相反的方向依次返回Response,最终得到我们需要的“终极版”Response。 【RealInterceptorChain】
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
(5)client.dispatcher().finished(this);
将本次请求从队列中移除。若是异步请求,则在一个异步请求结束后,将一个符合条件的异步请求从等待运行队列中放入运行队列中,并开始执行
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
3.3 同步与异步(线程池)的实现
3.4 拦截器
(一)处理网络响应的拦截器机制
okHttp处理网络响应时采用的是拦截器机制,对interceptors依此调用。 okHttp自带的拦截器包括重连、组装请求头部、读/写缓存、建立socket连接、向服务器发送请求/接收响应拦截器。 用户可添加自定义Interceptor,包括应用拦截器和网络拦截器
- 调用OkHttpClient.Builder的addInterceptor()可以添加应用拦截器,只会被调用一次,可以处理网络请求回来的最终Response
- 调用addNetworkInterceptor()可以添加network拦截器,处理所有的网络响应(一次请求如果发生了redirect ,那么这个拦截器的逻辑可能会被调用两次)
public class OkHttpClient implements Cloneable, Call.Factory {
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors; ...... }
(二)Interceptor解析
拦截器链通过责任链的模式,将网络请求过程中的职责功能都分割开,分别用不同的拦截器来完成失败重连、缓存处理、网络连接等问题。而且用户还可以添加自定义的拦截器,非常灵活,满足面向对象的开闭原则。
(1)RetryAndFollowUpInterceptor
该拦截器主要的作用负责失败自动重连和必要的重定向。当一个请求由于各种原因失败了,如果是路由或者连接异常,则尝试恢复,否则,根据响应码(ResponseCode),重定向方法会对Request进行再处理以得到新的Request,然后沿着拦截器链继续新的Request。当然,如果responseCode是200的话,这些过程就结束了。
(2)BridgeInterceptor
BridgeInterceptor的主要作用就是为请求(request before)添加请求头,为响应(Response Before)添加响应头。 负责将用户的Request转换成一个实际的网络请求Request,再调用下一个拦截器获取Response,然后将Response转换成用户的Response。
(3)CacheInterceptor
负责控制缓存,缓存的逻辑就在这里面
okHttp缓冲机制:
(3.1)HTTP缓冲机制
HTTP缓存有多种规则,根据是否需要重新向服务器发起请求来分类,我将其分为两大类(强制缓存,对比缓存)
(3.1.1)强制缓存
从上文我们得知,强制缓存,在缓存数据未失效的情况下,可以直接使用缓存数据,那么浏览器是如何判断缓存数据是否失效呢?
我们知道,在没有缓存数据的时候,浏览器向服务器请求数据时,服务器会将数据和缓存规则一并返回,缓存规则信息包含在响应header中。
对于强制缓存来说,响应header中会有两个字段来标明失效规则(Expires/Cache-Control)
- Expires(失效) Expires的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。 不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。另一个问题是,到期时间是由服务端生成的,但是客户端时间可能跟服务端时间有误差,这就会导致缓存命中的误差。所以HTTP 1.1 的版本,使用Cache-Control替代。
- Cache-Control Cache-Control 是最重要的规则。常见的取值有private、public、no-cache、max-age,no-store,默认为private。 private:客户端可以缓存 public:客户端和代理服务器都可缓存(前端的同学,可以认为public和private是一样的) max-age=xxx:缓存的内容将在 xxx 秒后失效(若在xxx秒内再次请求这条数据,都会直接获取缓存数据库中的数据,直接使用。) no-cache:需要使用对比缓存来验证缓存数据 no-store:所有内容都不会缓存,强制缓存,对比缓存都不会触发
(3.1.2)对比缓存
对比缓存,顾名思义,需要进行比较判断是否可以使用缓存。
浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。
在对比缓存生效时,状态码为304,并且报文大小和请求时间大大减少。
原因是,服务端在进行标识比较后,只返回header部分,通过状态码通知客户端使用缓存,不再需要将报文主体部分返回给客户端。对于对比缓存来说,缓存标识的传递是我们着重需要理解的,它在请求header和响应header间进行传递,一共分为两种标识传递:
- Last-Modified / If-Modified-Since
- Last-Modified:
服务器在响应请求时,告诉浏览器资源的最后修改时间。
- If-Modified-Since:
再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。
服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。
若资源的最后修改时间大于If-Modified-Since,说明资源又被改动过,则响应整片资源内容,返回状态码200;
若资源的最后修改时间小于或等于If-Modified-Since,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
- Etag / If-None-Match(优先级高于Last-Modified / If-Modified-Since)
- Etag:
服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。
- If-None-Match:
再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。
服务器收到请求后发现有头If-None-Match 则与被请求资源的唯一标识进行比对,
不同,说明资源又被改动过,则响应整片资源内容,返回状态码200;
相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。 对于比较缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。
强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互。 两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则。
(3.2)重要的类 1、缓存Cache Cache来自OkHttpClient Cache中采用了DiskLruCache,以Request的URL的md5为key,相应Response为value。此外Cache中还通过外观模式对外提供了InternalCache接口变量,用于调用Cache中的方法,也满足面向对象的接口隔离原则和依赖倒置原则等。 DiskLruCache和LruCache内部都是使用了LinkedHashMap去实现缓存算法的,只不过前者针对的是将缓存存在硬盘(/sdcard/Android/data//cache),而后者是直接将缓存存在内存; 2、缓存策略CacheStrategy CacheStrategy的内部工厂类Factory中有一个getCandidate方法,会根据实际的请求生成对应的CacheStrategy类返回,是个典型的简单工厂模式。其内部维护一个request和response,通过指定request和response来告诉CacheInterceptor是使用缓存还是使用网络请求,亦或两者同时使用。 (3.2)缓存框架 (3.3)源码分析
@Override public Response intercept(Chain chain) throws IOException {
// 1.如果设置缓存并且当前request有缓存,则从缓存Cache中获取当前请求request的缓存response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 2.传入的请求request和获取的缓存response通过缓存策略对象CacheStragy的工厂类get方法根据一些规则获取缓存策略CacheStrategy(这里的规则根据请求的request和缓存的Response的header头部信息生成的,比如是否有noCache标志位,是否是immutable不可变,缓存是否过期等等)
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
// 3.生成的CacheStrategy有2个变量,networkRequest和cacheRequest,如果networkRequest为Null表示不进行网络请求,如果cacheResponse为null,则表示没有有效缓存
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
// 4.缓存不可用,关闭
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 5.如果networkRequest和cacheResponse都为Null,则表示不请求网络且缓存为null,返回504,请求失败
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 6.如果不请求网络,但存在缓存,则不请求网络,直接返回缓存,结束,不执行下一个拦截器
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// 7.否则,请求网络,并调用下一个拦截器链,将请求转发到下一个拦截器
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
//8.请求网络,并且网络请求返回HTTP_NOT_MODIFIED,说明缓存有效,则合并网络响应和缓存结果,同时更新缓存
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//9.若没有缓存,则写入缓存
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
}
return response;
}
(3.4)流程
- 如果本地没有缓存,直接发送网络请求; cacheResponse == null
- 如果当前请求是Https,而缓存没有TLS握手,则重新发起网络请求; request.isHttps() && cacheResponse.handshake() == null
- 如果当前的缓存策略是不可缓存,直接发送网络请求; !isCacheable(cacheResponse, request)
- 请求头no-cache或者请求头包含If-Modified-Since或者If-None-Match,则需要服务器验证本地缓存是不是还能继续使用,直接网络请求; requestCaching.noCache() || hasConditions(request)
- 可缓存,并且ageMillis + minFreshMillis < freshMillis + maxStaleMillis(意味着虽过期,但可用,只是会在响应头添加warning),则使用缓存;
- 缓存已经过期,添加请求头:If-Modified-Since或者If-None-Match,进行网络请求;
(4)ConnectInterceptor(核心,连接池)
okhttp的一大特点就是通过连接池来减小响应延迟。如果连接池中没有可用的连接,则会与服务器建立连接,并将socket的io封装到HttpStream(发送请求和接收response)中
(4.1)重要的类
HttpCodec(Stream):数据交换的流,对请求的编码以及对响应数据的解码
(Stream:基于Connection的逻辑Http请求/响应对)
RealConnecton(Collection):Connection实现类,主要实现连接的建立等工作;
Http中Stream和Collection关系:
Http1(Http1.0)1:1一个连接只能被一个请求流使用
Http2(Http1.1)1:n一个连接可被多个请求流同时使用,且keep-alive机制保证连接使用完不关闭,当下一次请求与连接的Host相同时,连接可以直接使用,不用再次创建
StreamAllocation(流分配):会通过ConnectPool获取或者创建一个RealConnection来得到一个连接到Server的Connection连接,同时会生成一个HttpCodec用于下一个CallServerInterceptor,以完成最终的请求;
RouteDataBase:这是一个关于路由信息的白名单和黑名单类,处于黑名单的路由信息会被避免不必要的尝试;
ConnectionPool:连接池,实现连接的复用;
(4.2)连接流程框架
(4.3)连接流程源码
创建一个StreamAllocation并且分配一个Connection和HttpCodec,为最终的请求做准备
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
1、 HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); newStream.java
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
}
findHealthyConnection.java找到一个可用连接
/**
* 找可用连接,如果连接不可用,便会一直持续查找
*/
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// 如果是新的连接,则直接返回.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
//判断连接池中连接是否可用,如果不可用,则释放该连接并从连接池中移除,并继续寻找可用连接
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
findConnection.java
/**
* 返回连接到Host的新流.首选已存在的流,再选连接池的流,最后创建一个新流
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
// 1.首选已存在的流
RealConnection allocatedConnection = this.connection;
// 2.从连接池中取得连接(以URL为Key)
Internal.instance.get(connectionPool, address, this, null);
selectedRoute = route;
}
// 3.如果当前路由为空,选择下一条路由.
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
synchronized (connectionPool) {
//4.根据IP地址和Route从连接池进行第二次查找
Internal.instance.get(connectionPool, address, this, selectedRoute);
//5.根据route创建一个RealConnection
route = selectedRoute;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
}
// 6.建立Socket连接
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
synchronized (connectionPool) {
// 6.将生成的connection放入连接池
Internal.instance.put(connectionPool, result);
// 7.多路复用
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
2、RealConnection connection = streamAllocation.connection(); (4.4)连接流程步骤 1.框架使用URL和配置好的OkHttpClient创建一个address。此地址指定我们将如何连接到网络服务器。 2.框架通过address从连接池中取回一个连接。 3.如果没有在池中找到连接,ok会选择一个route尝试连接。这通常意味着使用一个DNS请求, 以获取服务器的IP地址。如果需要,ok还会选择一个TLS版本和代理服务器。 4.如果获取到一个新的route,它会与服务器建立一个直接的socket连接、使用TLS安全通道(基于HTTP代理的HTTPS),或直接TLS连接。它的TLS握手是必要的。 5.开始发送HTTP请求并读取响应。 如果有连接出现问题,OkHttp将选择另一条route,然后再试一次。这样的好处是当服务器地址的一个子集不可达时,OkHttp能够自动恢复。而且当连接池过期或者TLS版本不受支持时,这种方式非常有用。 一旦响应已经被接收到,该连接将被返回到池中,以便它可以在将来的请求中被重用。连接在池中闲置一段时间后,它会被赶出。
(5)CallServerInterceptor
CallServerInterceptor的intercept()方法里 负责发送请求和获取响应,实际上都是由HttpStream类去完成具体的工作。
一个socket连接用来发送HTTP/1.1消息,这个类严格按照以下生命周期:
1、 writeRequestHeaders()发送request header
httpCodec.writeRequestHeaders(request);
2、打开一个sink来写request body,然后关闭sink
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
3、readResponseHeaders()读取response头部
responseBuilder = httpCodec.readResponseHeaders(true);
4、打开一个source来读取response body,然后关闭source