连接拦截器原理剖析:
在上一次文末提到了ConnectInterceptor这个拦截器,因为它比较重要,所以接下来分析一下它的整个流程。
做个小实验:
这里做一个socket的小实验,来获取一个网页的返回内容,很简单,就是通过Socket来连接到中国天气的服务器,将其网页的内容给正常请求下来,这里先用一个请求工具来查看一下整个请求的格式,采用Charles抓包工具【有其它好用的网络请求工具都可以】来查看一下:
其实我们可以通过Socket来照着这个请求头来请求也能获取这个网页返回的信息:
那咱们来试一下:
此时运行:
妥妥的,为啥要做这个简单的实验呢?因为OkHttp的底层就是通过Socket的方式来实现的,好,接下来则通过分析这个连接拦截器揭开它神秘的面纱。
正式原理分析:
而它是在我们创建Call的时候生成的:
好,继续往下分析:
获得一个Exchange对象,具体细节是?
对于请求头和响应数据都需要进行解析,莫非这个解码器是用来干这个的,带着猜测跟进去瞅一下:
那咱们来看一下这个connectionPool对象是啥?
好,回到主流程继续往下看:
好!!以上两次都没能从复用连接池中找到,接下来则需要发起一次真正的连接了:
接下来看一下连接的细节:
接下来则集中看一下连接的实现代码:
往里再跟一下:
所以此时连接的细节就得跑到AndroidPlatform里面去了:
好,这个迷底揭晓了之后,继续往上回到主流程往下分析,接下来则就需根据连接器来创建相应的解码器了:
有了解码器之后,接下来就可以用它对整个通信数据进行使用了,具体的通信步骤则是最后一个拦截器所承担的:
关于这个拦截器就不细究了,至此整个连接拦截器的细节就分析完了,下面总结一下刚才分析的流程图:
其中里面用到的复用连接池,对于这块再陈述一下,我们知道对于一次响应过程会经历如下过程:
而对于打开连接和释放连接是一个比较重的操作,如果对于一个完全一样的连接每次都得经历上述四个过程,那性能肯定不是太好,所以就需要加入一个复用缓存池来解决这样的问题,有了复用池之后,对于能复用的连接打开和释放这俩操作就可以省了,所以流程为:
其中有一个wait(),这是啥意思呢?其实照理当一个连接处理完之后不是要立马释放该链接嘛,但是为了能复用,所以在完成连接之后做了一次的延时,在一定时间内如果该链接还没有被复用则就开始释放了,如果被复用了,那很显然释放操作就省了。
开启手写OkHttp核心流程:
经过了上面的原理剖析之后,接下来则开启又惊险又刺激的撸码环节,其实这才是最透彻的学习方法,看得再多都不好自己动手来敲一遍来得实在,下面手写会以在之前分析整个OkHttp调用流程源码时的那张时序图为基准进行超级模仿,回忆一下:
而编写思路跟之前手写开源框架雷头,基本上是完全照抄官方的框架源码来,好,下面开始,先来新建一个包,里面存放手写框架的代码:
初始化OkHttpClient:
新建个类,它里面当然得要有Builder模式的身影了:
接下来就是Builder中的一些参数了,源码里面这块参数太多了,这里只弄一些核心的:
package com.android.okhttpstudy2.net;
import java.util.ArrayList;
public class MyOkHttpClient {
//分发器
private Dispatcher dispatcher;
//连接池
private ConnectionPool connectionPool;
//重试连接次数
private int retrys;
//客户端拦截器集合
private List<Interceptor> interceptors;
public MyOkHttpClient() {
this(new Builder());
}
public MyOkHttpClient(Builder builder) {
dispatcher = builder.dispatcher;
connectionPool = builder.connectionPool;
retrys = builder.retrys;
interceptors = builder.interceptors;
}
public static final class Builder {
Dispatcher dispatcher = new Dispatcher();
ConnectionPool connectionPool = new ConnectionPool();
int retrys = 3;
List<Interceptor> interceptors = new ArrayList<>();
public Builder retrys(int retrys) {
this.retrys = retrys;
return this;
}
public Builder addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
return this;
}
}
public int retrys() {
return retrys;
}
public Dispatcher dispatcher() {
return dispatcher;
}
public ConnectionPool connectionPool() {
return connectionPool;
}
public List<Interceptor> interceptors() {
return interceptors;
}
}
其中这里涉及到了三个类,所以下面来创建一下:
package com.android.okhttpstudy2.net;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Dispatcher {
//最多同时请求
private int maxRequests;
//同一个host同时最多请求
private int maxRequestsPerHost;
//线程池,发送异步请求
private ExecutorService executorService;
public Dispatcher() {
this(64, 2);
}
public Dispatcher(int maxRequests, int maxRequestsPerHost) {
this.maxRequests = maxRequests;
this.maxRequestsPerHost = maxRequestsPerHost;
}
/**
* 线程池
*
* @return
*/
public synchronized ExecutorService executorService() {
if (executorService == null) {
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread result = new Thread(runnable, "OkHttp Dispatcher");
return result;
}
};
/**
* 1、corePoolSize:线程池中核心线程数的最大值
* 2、maximumPoolSize:线程池中能拥有最多线程数
* 3、keepAliveTime:表示空闲线程的存活时间 60秒
* 4、表示keepAliveTime的单位。
* 5、workQueue:它决定了缓存任务的排队策略。
* SynchronousQueue<Runnable>:此队列中不缓存任何一个任务。向线程池提交任务时,
* 如果没有空闲线程来运行任务,则入列操作会阻塞。当有线程来获取任务时,
* 出列操作会唤醒执行入列操作的线程。
* 6、指定创建线程的工厂
*/
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), threadFactory);
}
return executorService;
}
}
接下来再来创建一个连接池类:
接下来再来新建一个拦截器:
此时咱们就可以应用一下创建这个OkHttpClient对象了:
初始化Requst:
接下来则需要初始化Request了,先创建一个类:
里面的内容就不一一说明了,比较容易理解:
package com.android.okhttpstudy2.net;
import android.text.TextUtils;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
public class Request {
//请求头
public Map<String, String> headers;
//请求方式 get/post
public String method;
//请求体
public RequestBody body;
//解析url 成HttpUrl 对象
public HttpUrl url;
public Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers;
this.body = builder.body;
}
public String method() {
return method;
}
public HttpUrl url() {
return url;
}
public RequestBody body() {
return body;
}
public Map<String, String> headers() {
return headers;
}
public final static class Builder {
HttpUrl url;
Map<String, String> headers = new HashMap<>();
String method;
RequestBody body;
public Builder url(String url) {
try {
this.url = new HttpUrl(url);
return this;
} catch (MalformedURLException e) {
throw new IllegalStateException("Failed Http Url", e);
}
}
public Builder addHeader(String name, String value) {
headers.put(name, value);
return this;
}
public Builder removeHeader(String name) {
headers.remove(name);
return this;
}
public Builder get() {
method = "GET";
return this;
}
public Builder post(RequestBody body) {
this.body = body;
method = "POST";
return this;
}
public Request build() {
if (url == null) {
throw new IllegalStateException("url == null");
}
if (TextUtils.isEmpty(method)) {
method = "GET";
}
return new Request(this);
}
}
}
其中还涉及到两个相关类,如下:
package com.android.okhttpstudy2.net;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
public class RequestBody {
/**
* 表单提交 使用url encoded编码
*/
private final static String CONTENT_TYPE = "application/x-www-form-urlencoded";
private final static String CHARSET = "utf-8";
Map<String, String> encodedBodys = new HashMap<>();
public String contentType() {
return CONTENT_TYPE;
}
public long contentLength() {
return body().getBytes().length;
}
public String body() {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, String> entry : encodedBodys.entrySet()) {
sb.append(entry.getKey())
.append("=")
.append(entry.getValue())
.append("&");
}
if (sb.length() != 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
public RequestBody add(String name, String value) {
try {
encodedBodys.put(URLEncoder.encode(name, CHARSET), URLEncoder.encode(value, CHARSET));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return this;
}
}
package com.android.okhttpstudy2.net;
import android.text.TextUtils;
import java.net.MalformedURLException;
import java.net.URL;
public class HttpUrl {
String protocol; //协议http https
String host; //192.6.2.3
String file; // 文件地址
int port; //端口
/**
* scheme://host:port/path?query#fragment
* @param url
* @throws MalformedURLException
*/
public HttpUrl(String url) throws MalformedURLException {
URL url1 = new URL(url);
host = url1.getHost();
file = url1.getFile();
file = TextUtils.isEmpty(file) ? "/" : file;
protocol = url1.getProtocol();
port = url1.getPort();
port = port == -1 ? url1.getDefaultPort() : port;
}
public String getProtocol() {
return protocol;
}
public String getHost() {
return host;
}
public String getFile() {
return file;
}
public int getPort() {
return port;
}
}
此时咱们就可以创建Requst对象了:
创建Call对象:
所以先来创建一个Call类:
package com.android.okhttpstudy2.net;
public class Call {
Request request;
MyOkHttpClient client;
/**
* 是否执行过
*/
boolean executed;
//取消
boolean canceled;
public Call(Request request, MyOkHttpClient client) {
this.request = request;
this.client = client;
}
public void cancel() {
canceled = true;
}
public boolean isCanceled() {
return canceled;
}
}
此时需要在MyHttpClient中增加一个方法:
在源码中的实现是对其又做了一层封装,如下:
咱们这里简化一下,直接将实现就放到Call类中了:
好,此时咱们就可以创建Call了:
发起请求:
这里只实现异步请求,所以需要在Call中增加一个enququq就去,其中有一个Callback对象,先来新建这个回调类:
其中有个Response对象定义一下:
package com.android.okhttpstudy2.net;
import java.util.HashMap;
import java.util.Map;
public class Response {
int code;
int contentLength = -1;
Map<String, String> headers = new HashMap<>();
String body;
//保持连接
boolean isKeepAlive;
public Response(int code, int contentLength, Map<String, String> headers, String body,
boolean isKeepAlive) {
this.code = code;
this.contentLength = contentLength;
this.headers = headers;
this.body = body;
this.isKeepAlive = isKeepAlive;
}
public int getCode() {
return code;
}
public int getContentLength() {
return contentLength;
}
public Map<String, String> getHeaders() {
return headers;
}
public String getBody() {
return body;
}
public boolean isKeepAlive() {
return isKeepAlive;
}
}
好,接下来则来定义enqueue方法:
此时需要先定义这个AsyncCall,从之前的源码分析也能晓得它是一个线程:
package com.android.okhttpstudy2.net;
import java.io.IOException;
public class Call {
Request request;
MyOkHttpClient client;
/**
* 是否执行过
*/
boolean executed;
//取消
boolean canceled;
public Call(Request request, MyOkHttpClient client) {
this.request = request;
this.client = client;
}
public Call enqueue(Callback callback) {
//不能重复执行
synchronized (this) {
if (executed) {
throw new IllegalStateException("Already Execute");
}
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(callback));
return this;
}
public void cancel() {
canceled = true;
}
public boolean isCanceled() {
return canceled;
}
final class AsyncCall implements Runnable {
private final Callback callback;
public AsyncCall(Callback callback) {
this.callback = callback;
}
@Override
public void run() {
//是否已经通知过callback
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (canceled) {
signalledCallback = true;
callback.onFailure(Call.this, new IOException("Canceled"));
} else {
signalledCallback = true;
callback.onResponse(Call.this, response);
}
} catch (IOException e) {
if (!signalledCallback) {
callback.onFailure(Call.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
public String host() {
return request.url().host;
}
}
private Response getResponseWithInterceptorChain() throws IOException {
//TODO:添加拦截器
return null;
}
}
其中分发器中需要定义一个请求结束的方法,此时先来完善一下分发器,里面都是维护各请求状态:
package com.dn_alan.myapplication.net;
import com.android.okhttpstudy2.net.Call;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Dispatcher {
//最多同时请求
private int maxRequests;
//同一个host同时最多请求
private int maxRequestsPerHost;
//线程池,发送异步请求
private ExecutorService executorService;
/**
* 等待执行队列
*/
private final Deque<Call.AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/**
* 正在执行队列
*/
private final Deque<Call.AsyncCall> runningAsyncCalls = new ArrayDeque<>();
public Dispatcher() {
this(64, 2);
}
public Dispatcher(int maxRequests, int maxRequestsPerHost) {
this.maxRequests = maxRequests;
this.maxRequestsPerHost = maxRequestsPerHost;
}
/**
* 线程池
*
* @return
*/
public synchronized ExecutorService executorService() {
if (executorService == null) {
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread result = new Thread(runnable, "OkHttp Dispatcher");
return result;
}
};
/**
* 1、corePoolSize:线程池中核心线程数的最大值
* 2、maximumPoolSize:线程池中能拥有最多线程数
* 3、keepAliveTime:表示空闲线程的存活时间 60秒
* 4、表示keepAliveTime的单位。
* 5、workQueue:它决定了缓存任务的排队策略。
* SynchronousQueue<Runnable>:此队列中不缓存任何一个任务。向线程池提交任务时,
* 如果没有空闲线程来运行任务,则入列操作会阻塞。当有线程来获取任务时,
* 出列操作会唤醒执行入列操作的线程。
* 6、指定创建线程的工厂
*/
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), threadFactory);
}
return executorService;
}
/**
* 同一host 的 同时请求数
*
* @param call
* @return
*/
private int runningCallsForHost(Call.AsyncCall call) {
int result = 0;
//如果执行这个请求,则相同的host数量是result
for (Call.AsyncCall c : runningAsyncCalls) {
if (c.host().equals(call.host())) {
result++;
}
}
return result;
}
/*
*请求结束 移出正在运行队列
*并判断是否执行等待队列中的请求
*/
public void finished(Call.AsyncCall asyncCall) {
synchronized (this) {
runningAsyncCalls.remove(asyncCall);
//判断是否执行等待队列中的请求
promoteCalls();
}
}
/**
* 判断是否执行等待队列中的请求
*/
private void promoteCalls() {
//同时请求达到上限
if (runningAsyncCalls.size() >= maxRequests) {
return;
}
//没有等待执行请求
if (readyAsyncCalls.isEmpty()) {
return;
}
for (Iterator<Call.AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
Call.AsyncCall call = i.next();
//同一host同时请求为达上限
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
//到达同时请求上限
if (runningAsyncCalls.size() >= maxRequests) {
return;
}
}
}
}
上面的细节不多说了,一看就能明白,接下来继续回到Call类中的equeue()方法:
好,整个框架代码现在就差这个拦截器链的方法实现了:
关于这块最最核心的逻辑放下次再来编写,目前咱们可以来调用发起请求的代码了: