几分钟带你搞懂OkHttp3(一)

作者: 键盘上的麒麟臂 | 来源:发表于2020-11-16 11:37 被阅读0次

    没想到我竟然更新得这么频繁,之前有写个一篇OkHttp相关的文章,只不过那篇是带着问题去看源码,只是为了解决问题。这次是带着好奇心来看,想知道它内部到底做了什么。
    主要还是以分析源码来分析整个流程,大致的流程我能保证是能讲对的,不会坑爹,但是一些细节上的思想我不敢保证是不是那么一回事。
    这份源码是基于okhttp 3.11.0 不保证以后的流程不会改动

    一. 调用过程

    写个最简单调用Okhttp的方法

            OkHttpClient okHttpClient = new OkHttpClient();
            Request request = new Request.Builder()
                    .url("https://www.baidu.com/")
                    .get()
                    .build();
            Call call = okHttpClient.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
    
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
    
                }
            });
    

    1. 设计分析

    (1)OkHttpClient是一个客户端,一个抽象的客户端,所以它内部有引用很多对象,就像我们一个请求的客户端有很多东西。
    (2)Request是一个请求数据对象,可以当成一个快递,这个快递有快递内容、寄件人信息、收件人信息等等。
    (3)Response 是一个响应对象,和上面差不多,就只包含了响应的内容。
    (4)Call 是请求,是一个行为。
    那么客户端发送一个请求,这个抽象用代码表示就是okHttpClient.newCall(request)。
    请求数据能有很多个,所以Request每次都是new出来,就像寄很多快递每个都不同,请求也有很多个,因为每个请求都是独立的,就像每个快递员帮你寄一个快递,所以Call也是每次newCall都生成不同的对象。但是客户端从抽象的层面讲只有一个,你就是你,是独一无二的,所以从理论上来讲OkHttpClient应该是单例,并且okhttp在使用的时候OkHttpClient确实是全局共用一个,但是我不知道为什么设计者没有把它设计成单例,而是让使用者用单例的形式去使用。

    2.简单的引用关系图

    二. 从源码分析内部流程

    OkHttpClient okHttpClient = new OkHttpClient(); 就不用说了,获取客户端对象。
    Request request = ...... ; 也不用说了,打包请求数据

    1. newCall方法

    Call call = okHttpClient.newCall(request);
    

    往下看

        public Call newCall(Request request) {
            return RealCall.newRealCall(this, request, false);
        }
    

    看出RealCall是Call的具体实现类,可以参考上面的流程图。

        static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
            RealCall call = new RealCall(client, originalRequest, forWebSocket);
            call.eventListener = client.eventListenerFactory().create(call);
            return call;
        }
    

    嗯,每次newCall都是new 一个新的对象,没毛病,符合上面的分析。
    构造方法就是一些赋值操作,就不贴代码了
    eventListener 是一个监听器,通过钩子告诉调用者当前的流程走到哪步了,是用一个工厂来创建

    从这里就能看出newCall方法就只是创建一个RealCall并且做了一些初始化的操作。

    2. enqueue

    这是具体的请求操作

    call.enqueue(callback);
    

    往下看

        public void enqueue(Callback responseCallback) {
            // 防止同一个call对象重复调用enqueue方法
            synchronized(this) {
                if (this.executed) {
                    throw new IllegalStateException("Already Executed");
                }
                this.executed = true;
            }
    
            this.captureCallStackTrace();
            this.eventListener.callStart(this);  // 这里调用钩子通知整个call流程开始
            this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
        }
    

    我在想这里的synchronized换成使用Atomic是不是会好一点,欢迎评论告诉我。
    发现调用了OkHttpClient的Dispatcher的enqueue方法,传了一个AsyncCall对象。
    这个AsyncCall是一个CallBack,所以我们可以先不用管它,再回调之后再去看它内部的代码

        final class AsyncCall extends NamedRunnable {
    
    public abstract class NamedRunnable implements Runnable {
        protected final String name;
    
        public NamedRunnable(String format, Object... args) {
            this.name = Util.format(format, args);
        }
    
        public final void run() {
            String oldName = Thread.currentThread().getName();
            Thread.currentThread().setName(this.name);
    
            try {
                this.execute();
            } finally {
                Thread.currentThread().setName(oldName);
            }
    
        }
    
        protected abstract void execute();
    }
    

    这个execute是AsyncCall 的方法,我们回调的地方再看,先继续看Dispatcher的enqueue

    3.enqueue

     this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
    

    跳到这个方法里面

        synchronized void enqueue(AsyncCall call) {
            if (this.runningAsyncCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
                this.runningAsyncCalls.add(call);
                this.executorService().execute(call);
            } else {
                this.readyAsyncCalls.add(call);
            }
    
        }
    

    这代码很简单,从它的设计角度出发,它是设计了一个“生产—消费者模型”,至于这个是什么,这里不讲了,自己去找,很简单的。所以设置了两个队列,一个队列runningAsyncCalls表示正在运行,一个队列readyAsyncCalls表示等待运行。如果运行队列满了,那这个请求就进入等待队列。从maxRequests 看出,他设计的默认的队列的大小是64,当然也有提供方法给调用者去改。
    放到队列之后执行 this.executorService().execute(call);

        public synchronized ExecutorService executorService() {
            if (this.executorService == null) {
                this.executorService = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
            }
    
            return this.executorService;
        }
    

    这是一个没有核心线程的线程池,也不用多说了吧,这篇主要讲okhttp,线程池这些就不细说了,也不难。但是从这里能看出,okhttp这个请求流程的核心就是这两个队列+线程池进行调度。
    然后等线程被cpu调度之后,就会去执行Runnable的run方法,也就是上面说的AsyncCall的execute方法。

    4. AsyncCall 的 execute

            protected void execute() {
                boolean signalledCallback = false;
    
                try {
                    Response response = RealCall.this.getResponseWithInterceptorChain();
                    if (RealCall.this.retryAndFollowUpInterceptor.isCanceled()) {
                        signalledCallback = true;
                        this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
                    } else {
                        signalledCallback = true;
                        this.responseCallback.onResponse(RealCall.this, response);
                    }
                } catch (IOException var6) {
                    if (signalledCallback) {
                        Platform.get().log(4, "Callback failure for " + RealCall.this.toLoggableString(), var6);
                    } else {
                        RealCall.this.eventListener.callFailed(RealCall.this, var6);
                        this.responseCallback.onFailure(RealCall.this, var6);
                    }
                } finally {
                    RealCall.this.client.dispatcher().finished(this);
                }
    
            }
    

    Response response = RealCall.this.getResponseWithInterceptorChain(); 这是具体的网络请求的过程。所以说,Dispatcher只是负责调度的工作,Call才是具体负责请求操作的。简单来说就是你去食堂打饭,Call才是负责打饭的食堂大妈,Dispatcher只是负责管理排队的保安,你敢插队砍洗你。
    然后我们先不管这个请求具体做了什么操作,因为里面的拦截器那些代码逻辑不那么简单,总之先当成做了某部操作得到结果Response ,详解的操作封装得很好,十分的解耦,放心。

    得到结果Response 之后调responseCallback的onFailure或者onResponse方法通知调用者网络请求的结果。并且
    RealCall.this.client.dispatcher().finished(this); 告诉调度者,这个请求流程到这里已经执行完了。

    5. Dispatcher的finished

        void finished(AsyncCall call) {
            this.finished(this.runningAsyncCalls, call, true);
        }
    
        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) {
                    this.promoteCalls();
                }
    
                // 后面的idleCallback是一个扩展吧,暂时不用管
                runningCallsCount = this.runningCallsCount();
                idleCallback = this.idleCallback;
            }
    
            if (runningCallsCount == 0 && idleCallback != null) {
                idleCallback.run();
            }
    
        }
    

    可以看出这里从运行队列中移除请求之后,调用promoteCalls方法

        private void promoteCalls() {
            if (this.runningAsyncCalls.size() < this.maxRequests) {
                if (!this.readyAsyncCalls.isEmpty()) {
                    Iterator i = this.readyAsyncCalls.iterator();
    
                    do {
                        if (!i.hasNext()) {
                            return;
                        }
    
                        AsyncCall call = (AsyncCall)i.next();
                        if (this.runningCallsForHost(call) < this.maxRequestsPerHost) {
                            i.remove();
                            this.runningAsyncCalls.add(call);
                            this.executorService().execute(call);
                        }
                    } while(this.runningAsyncCalls.size() < this.maxRequests);
    
                }
            }
        }
    

    这里的操作就是判断移除之后的运行队列是否小于最大长度,如果小于,则从准备队列中将第一个请求放到运行队列中然后运行,就永动机了。
    这里整个过程就结束了

    6. runningCallsForHost方法

    单独把这个方法拿出来讲是因为我不敢保证我当前理解就是对的。请求运行之前的enqueue方法,和请求结束之后的finished方法,都有调用这个方法

    this.runningCallsForHost(call) < this.maxRequestsPerHost
    
        private int runningCallsForHost(AsyncCall call) {
            int result = 0;
            Iterator var3 = this.runningAsyncCalls.iterator();
    
            while(var3.hasNext()) {
                AsyncCall c = (AsyncCall)var3.next();
                if (!c.get().forWebSocket && c.host().equals(call.host())) {
                    ++result;
                }
            }
    
            return result;
        }
    

    (1)首先forWebSocket 这个参数,上面源码中显示传进来的是false,我没具体理解是什么意思,讲真从这命名真看不出来,我感觉是一个开关,提供给扩展用的。
    (2)c.host().equals(call.host()))
    这个host是HttpUrl的,我的理解是判断相同主机的请求是否一样,一样的话就+1,并且限制最大是5个,也就是说你的url如果是同一个Host,只能同时请求5个,第6个排队去。我不知道这个判断的设计思想是什么,限制这个是为了保证什么?

    总结

    本来是想一次性写完整个流程的,但我最近在在找工作,这段时间一直在准备比较忙,所以没能写完。所以这次打算分开写,不然我这篇写到一半就要搁着了。这篇文章先介绍了整个okhttp并发的一个过程,具体的请求操作,拦截器那块等总结好了再发出来,只能先说一声抱歉了。

    相关文章

      网友评论

        本文标题:几分钟带你搞懂OkHttp3(一)

        本文链接:https://www.haomeiwen.com/subject/tmewmktx.html