连接拦截器原理剖析:
在上一次https://www.cnblogs.com/webor2006/p/12362710.html文末提到了ConnectInterceptor这个拦截器,因为它比较重要,所以接下来分析一下它的整个流程。
做个小实验:
这里做一个socket的小实验,来获取一个网页的返回内容,很简单,就是通过Socket来连接到http://www.weather.com.cn/weather/101010100.shtml中国天气的服务器,将其网页的内容给正常请求下来,这里先用一个请求工具来查看一下整个请求的格式,采用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()方法:
好,整个框架代码现在就差这个拦截器链的方法实现了:
关于这块最最核心的逻辑放下次再来编写,目前咱们可以来调用发起请求的代码了: