• OkHttp框架设计<二>---连接拦截器原理剖析、开启手写OkHttp核心框架


    连接拦截器原理剖析:

    在上一次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()方法:

    好,整个框架代码现在就差这个拦截器链的方法实现了:

    关于这块最最核心的逻辑放下次再来编写,目前咱们可以来调用发起请求的代码了:

  • 相关阅读:
    linux文件操作.md
    MinIO
    快速幂和快速乘
    LCP 03.机器人大冒险
    java 实现 js 方法调用
    UNITY STANDARD ASSETS源码学习笔记(二)Sun Shafts
    下雨Shader,触摸擦除雨点,声控闪电
    Unity Standard Assets源码学习笔记(一)SSAO
    【转发】vueSSR剖析
    locust中的监听器
  • 原文地址:https://www.cnblogs.com/webor2006/p/12362753.html
Copyright © 2020-2023  润新知