zoukankan      html  css  js  c++  java
  • 吊打面试官——史上最详细【OkHttp】一

    简介:大三学生党一枚!主攻Android开发,对于Web和后端均有了解。

    个人语录:取乎其上,得乎其中,取乎其中,得乎其下,以顶级态度写好一篇的博客。

    前言:OkHttp源码是面试中常问的,在腾讯二面中,被面试官追着问Okhttp的原理,当时只是面试前看了几篇Okhttp的分析博客,自然也就禁不住拷问,这次我深入底层源码看了两三遍,看完以后就一个感受,妙哉,不愧是顶级工程师写出来的代码!本章节将会分为几篇进行讲解,希望诸君有所收获!

    在这里插入图片描述

    一.从基础说起

    在官网的源码中有这样一段注释

    OkHttp performs best when you create a single 
    {@code OkHttpClient} instance and reuse it for all of your HTTP calls. 
    This is because each client holds its own connection pool and thread pools. 
    Reusing connections and threads reduces latency and saves memory.
    Conversely, creating a client for each request wastes resources on idle pools.

    英文好的盆友应该知道了,官方建议我们使用Okhttp的单例模式,原因是每一个OkhttpClient内部都维护了一个连接池,使用单例模式可以减少延迟,节省内存。

    作为演示,我们就用最简单的建造者模式吧!

    OkHttpClient okHttpClient=new OkHttpClient.Builder().build();
     Request request=new Request.Builder().url(Constant.SEARCH).get().build();
     Call call=okHttpClient.newCall(request);

    上面涉及到三个对象,我们就从这三个对象展开我们对OkHttp源码的解析!

    1.1 OkHttpClient

    OkHttpClientOkhttp网络请求框架的客户端类,可以认为,所有的网络请求都是通过这个类发出去的!结合我们实际项目的需求,可以通过设置各种参数定制最适合项目的OkHttpClient,来看看他内部的一些参数。

    //调度器,内部维护了三个请求队列和一个线程池,负责调度任务的执行
        //执行请求,移除已经执行完的请求
        Dispatcher dispatcher;
        @Nullable Proxy proxy;
        //protocols默认支持的Http协议版本,Http 1.1,Http 2.0,不支持Http1.0
        List<Protocol> protocols;
        //okHttp连接配置,配置诸如TLS的版本号
        List<ConnectionSpec> connectionSpecs;
        //拦截器链,这里暂时理解为通过拦截器链就得到Response
        final List<Interceptor> interceptors = new ArrayList<>();
        //网络拦截器链
        final List<Interceptor> networkInterceptors = new ArrayList<>();
        //一个Call的状态监听器
        EventListener.Factory eventListenerFactory;
        //使用默认的代理选择器
        ProxySelector proxySelector;
        //默认是没有cookie的
        CookieJar cookieJar;
        //缓存,Okhttp默认是不开启缓存的,只可以缓存GET方法
        @Nullable Cache cache;
        // internalCache 用来操作cache,比如从cache中获取和清除缓存
        @Nullable InternalCache internalCache;
        //使用默认的Socket工厂产生Socket
        SocketFactory socketFactory;
        @Nullable SSLSocketFactory sslSocketFactory;
        @Nullable CertificateChainCleaner certificateChainCleaner;
        //安全相关的配置
        HostnameVerifier hostnameVerifier;
        CertificatePinner certificatePinner;
        // 验证身份的
        Authenticator proxyAuthenticator;
        Authenticator authenticator;
        //连接池,他来负责连接的销毁和复用
        ConnectionPool connectionPool;
        //域名解析系统
        Dns dns;
        boolean followSslRedirects;
        boolean followRedirects;
        boolean retryOnConnectionFailure;
        //各种超时时间的设置,有默认值
        int callTimeout;
        int connectTimeout;
        int readTimeout;
        int writeTimeout;
        //WebSocket相关,为了保持长连接,必须间隔一段时间发送一个ping指令进行保活
        int pingInterval;

    看完上面的参数,是不是有点懵了?来捋一捋!

    作为优秀的网络框架,必须要支持常用的协议http,https,而支持的协议版本都放在protocols中,当然与网络协议相关的内容不止这一点,还需要通过connectionSpec进行配置更多连接的细节,如TLS的版本号等。有时候我们还需要支持Cookies,对于常用的GET请求需要考虑缓存,避免重复请求。其他的参数暂时无需关注,最重要的就下面三个内容

    • Dispatcher分发器:负责调度任务
    • Interceptor拦截器:通过一个个拦截器最终得到Response
    • ConnectionPool 连接池:负责复用连接和销毁无用连接

    1.2 Request

    对Http协议熟悉的童鞋都知道,Request对应请求报文。

    在这里插入图片描述
    我们在Request中设置请求方法,请求的URL,使用的协议版本,还有请求体中的请求数据。了解了这些,再来看看Request内部的构造。

    final HttpUrl url;//请求的url
      final String method;//请求方法,默认为get
      final Headers headers;//请求头部信息,如Content-type,Content-length等
      final @Nullable RequestBody body;//请求体,如携带的数据存放在请求体中
      final boolean duplex;
      final Map<Class<?>, Object> tags;//使用此API将时序、调试或其他应用程序数据附加到请求中,以便可以在拦截器、事件侦听器或回调中读取它。
      private volatile @Nullable CacheControl cacheControl; // 缓存控制指令

    Request简单多了,简而言之就是封装了一个网络请求所必要的一些参数。

    1.3 Call

    Call在官方文档上的描述是:

    A call is a request that has been prepared for execution. 
    A call can be canceled. 
    As this object represents a single request/response pair (stream),
    it cannot be executed twice.

    Call可以看做一个准备执行的请求,他可以被取消,但是他不能被执行两次!!!
    Call中有两个重要的方法,同步执行与异步执行!

    Response execute() throws IOException;//同步执行,阻塞当前线程,知道返回结果或者出现异常
    void enqueue(Callback responseCallback);//异步执行,在子线程中执行,不确定会在什么时候被执行,但是当执行成功或者出现异常时会回调结果。

    前面的OkhttpClientRequest是所有网络请求所通用的,但是到了Call这里,发生了分叉,对于同步执行和异步执行,Okhttp做了不同的处理。

    1.4 RealCall

    Call只是一个接口,真正的实现类是RealCall,这里重点分析RealCallexcute()enqueue()方法。

    1.4.1 excute

    excute同步执行,会阻塞当前线程,所以不可以在UI线程中执行!

    @Override
        public Response execute() throws IOException {
            synchronized (this) {
                //是否执行过,如果执行过,抛出异常,一个call对象只能被执行一次
                if (executed) throw new IllegalStateException("Already Executed");
                executed = true;
            }
            captureCallStackTrace();//捕捉堆栈信息
            timeout.enter();
            eventListener.callStart(this);//开始监听
            try {
                //调用Dispatcher的executed方法将该Call加入到一个队列里
                client.dispatcher().executed(this);
                Response result = getResponseWithInterceptorChain();//通过拦截器链获取Response
                if (result == null) throw new IOException("Canceled");
                  //返回本次的请求结果
                return result;
            } catch (IOException e) {
                e = timeoutExit(e);
                eventListener.callFailed(this, e);
                throw e;
            } finally {
                //从已经执行的双端队列中移除本次Call
                client.dispatcher().finished(this);
            }
        }

    其中就两句涉及到核心

    client.dispatcher().executed(this);
    client.dispatcher().finished(this);

    第一句最后调用的是Dispatcher中的executed方法,

    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);//runningSyncCalls是保存同步请求的队列
      }

    第二句最后吊用的是Dispatcher的finshed方法

    private <T> void finished(Deque<T> calls, T call) {
        Runnable idleCallback;
        synchronized (this) {
          //将请求Call从队列中移除
          if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
          idleCallback = this.idleCallback;
        }
        boolean isRunning = promoteAndExecute();
        if (!isRunning && idleCallback != null) {
          idleCallback.run();
        }
      }

    一个请求的执行可以简单描述为入队列——》立即执行——》得到返回Response——》移除队列。

    1.4.2 enqueue

    异步执行,Dispatcher分发器会根据当前的情况决定如何调度!

    @Override
        public void enqueue(Callback responseCallback) {
            synchronized (this) {
                //首选检查是否执行,如果执行过,抛出异常。
                if (executed) throw new IllegalStateException("Already Executed");
                executed = true;
            }
            captureCallStackTrace();
            eventListener.callStart(this);
            //没有执行过,调用enqueue,将任务放进队列中
            client.dispatcher().enqueue(new AsyncCall(responseCallback));
        }

    上面的代码就一句是关键

    client.dispatcher().enqueue(new AsyncCall(responseCallback));
    //首先把Call请求封装成Runnable,然后调用Dispatcher的enqueue方法
    void enqueue(AsyncCall call) {
        synchronized (this) {
          //加入准备队列
          readyAsyncCalls.add(call);
        }
        promoteAndExecute();//调度执行
      }

    这里插一段简单介绍Dispatcher中的三个队列和连接池,后面会有详细介绍

    //最大并发数。
      private int maxRequests = 64;
      //每个主机的最大请求数。
      private int maxRequestsPerHost = 5;
      //Runnable对象,在删除任务时执行
      private @Nullable Runnable idleCallback;
      //消费者池(也就是线程池)
      /** Executes calls. Created lazily. */
      private @Nullable ExecutorService executorService;
      //准备好等待被执行的异步任务队列
      /** Ready async calls in the order they'll be run. */
      private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
      //正在运行的异步任务,包含取消尚未完成的调用
      /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
      private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
      //正在运行的同步任务,包含取消尚未完成的调用
      /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
      private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

    可见enqueue方法先把call封装的Runnable对象加入了准备执行的异步队列
    最后调用promoteAndExecute()去调度执行,接着看这个方法做了什么。

    /**
       * Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
       * them on the executor service. Must not be called with synchronization because executing calls
       * can call into user code.
       *
       * 将符合条件的calls从readyAsyncCalls移动到runningAsyncCalls里面,在executor service上执行它们。
       * @return true if the dispatcher is currently running calls.
       */
      private boolean promoteAndExecute() {
        assert (!Thread.holdsLock(this));
    
        List<AsyncCall> executableCalls = new ArrayList<>();
        boolean isRunning;
        synchronized (this) {
          //循环判断准备请求队列是否还有请求
          for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
            //如果执行请求的队列数量大于等于最大并发请求数,中止循环
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            //大于最大主机请求限制,该请求不能被加入执行队列,继续遍历下一个
            if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
            //如果两个条件都不满足,表示目前可以执行该请求,先从准备队列里移除自身。
            i.remove();
            
            executableCalls.add(asyncCall);
            //添加到执行队列中。
            runningAsyncCalls.add(asyncCall);
          }
          isRunning = runningCallsCount() > 0;
        }
        //对新添加的任务尝试进行异步调用。
        for (int i = 0, size = executableCalls.size(); i < size; i++) {
          AsyncCall asyncCall = executableCalls.get(i);
          asyncCall.executeOn(executorService());//执行该请求
        }
    
        return isRunning;
      }

    经过一些判断以后,重点还是这句

    asyncCall.executeOn(executorService());//执行该请求

    executrorService()方法返回一个线程池

    public synchronized ExecutorService executorService() {
        if (executorService == null) {
        //该线程池,核心线程为0,最大线程数不限制,线程空闲60秒会被关闭
          executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
              new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
        }
        return executorService;
      }

    所以这个异步请求命运多舛,终于要被线程池执行了!!!越是重要时刻,越要保持思路清醒!!!

    asyncCall是一个RunnableRunable内部有run()方法,当executorService执行该Runnable时实际上就是执行这个run()方法,而AsyncCall是继承自NamedRunnable,AsyncCall内部并没有run()方法,所以还要看它的父类做了什么!

    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();//在run方法内部调用了execute方法,而execute是抽象方法,子类必须要实现,所以也就是调用了AsyncCall内部的execute方法!
        } finally {
          Thread.currentThread().setName(oldName);
        }
      }
    
      protected abstract void execute();
    }

    现在我们终于知道,异步任务兜兜转转以后也来到execute方法这里了

    (step 1)  asyncCall.executeOn(executorService());
    
     (step 2)  void executeOn(ExecutorService executorService) {
                assert (!Thread.holdsLock(client.dispatcher()));
                boolean success = false;
                try {
                    //执行任务
                    executorService.execute(this);
    				//执行一个Runnable,回去执行它的run方法,
    				//执行任务,返回结果在哪
                    success = true;
                } catch (RejectedExecutionException e) {
                    InterruptedIOException ioException = new InterruptedIOException("executor rejected");
                    ioException.initCause(e);
                    eventListener.callFailed(RealCall.this, ioException);
                    responseCallback.onFailure(RealCall.this, ioException);
                } finally {
                    if (!success) {
                        client.dispatcher().finished(this); // This call is no longer running!
                    }
                }
            }
    
            @Override
      (step 3)  protected void execute() {
                boolean signalledCallback = false;
                timeout.enter();
                try {
                    //返回本次的Response对象
                    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) {
                    e = timeoutExit(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 {
                    client.dispatcher().finished(this);
                }
            }
        }

    原来如此,同步任务和异步任务最终都是要经过execute方法得到Response的!你男朋友恍然大悟,庆幸有你这么优秀的女朋友!!!

    最后还是用两一张图总结一下执行同步任务与异步任务的流程

    call.execute
    在这里插入图片描述





    call.enqueue

    在这里插入图片描述

    1.5 小结

    第一讲主要介绍四大基本类的作用和同步异步请求的源码解析,下一篇博客开始介绍拦截器链的第一个重定向和重试拦截器。

    相关阅读:

    抖音牛逼姐

    抖音创始人

    不愁销路的小型加工厂

  • 相关阅读:
    EasyHLS实现将IPCamera摄像机的RTSP流转成HLS(ts+m3u8)直播输出
    EasyHLS实现将IPCamera摄像机的RTSP流转成HLS(ts+m3u8)直播输出
    EasyHLS实现将IPCamera摄像机的RTSP转HLS直播输出
    基于EasyDarwin云视频平台的幼儿园视频直播(手机直播/微信直播)解决方案
    基于EasyDarwin云视频平台的幼儿园视频直播(手机直播/微信直播)解决方案
    EasyRTMP实现RTMP异步直播推送之环形缓冲区设计
    EasyRTMP实现RTMP异步直播推送之环形缓冲区设计
    EasyRTMP实现的rtmp推流的基本协议流程
    EasyRTMP实现的rtmp推流的基本协议流程
    EasyRTMP实现对接海康、大华等IPCamera SDK进行RTMP推送直播功能
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13309131.html
Copyright © 2011-2022 走看看