美文网首页
Android--OkHttp理解系列(一)

Android--OkHttp理解系列(一)

作者: 巨石强森小童鞋 | 来源:发表于2018-07-13 15:47 被阅读0次

    本篇文章主要对OkHttp进行分析,主要内容如下:

    • OkHttp初识
    • OkHttp基本执行流程(Dispatcher)
    • 拦截器(Interceptor)
    • 责任链设计模式

    1. OkHttp 初识

    OkHttp 在开发中经常使用到,常见的用法是:

    okhttpclient okhttpclient = new okhttpclient();
    request request = new request.builder()
            .url("www.baidu.com")
            .get()//get 请求
            .build();
    call call = okhttpclient.newcall(request);
    call.enqueue(new callback() {
        @override
        public void onfailure(call call, ioexception e) {
            //nop
        }
        @override
        public void onresponse(call call, response response) throws ioexception {
            //nop
        }
    });
    

    上述代码是基于异步的网络请求,同步的网络请求不同的地方是:

    try {
        response execute = call.execute();
    } catch (ioexception e) {
        //nop
    }
    

    不相同的地方点在于call对象调用的方法,那么,同步调用与异步调用主要的差别是什么?
    call.execute() 方法将直接进行网络请求,阻塞当前线程直到获得网络请求的响应。异步执行会将call放入一个异步执行队列中,由executorservice在后台进行执行。

    2. 基本执行流程

    基本执行流程主要谈的是call的执行流程,在call的执行流程中分为同步执行流程与异步执行流程。call其实是一个继承了cloneable的接口,我们调用call call = okhttpclient.newcall(request); 获取的其实是realcall这个对象,因此下文提到的call其实就是realcall。

    2.1 Call的同步执行流程

    通过realcall.excute()方法执行的流程为:

    1. client.dispatcher().executed(this);向client的dispatcher中注册当前call;
    2. getresponsewithinterceptorchain();执行网络请求,在经过一系列拦截器之后获取响应结果;
    3. client.dispatcher().finished(this);向client的dispatcher中注销当前call。

    2.2 Call的异步执行流程

    通过realcall.enqueue(callback)方法执行的流程首先是,client.dispatcher().enqueue(new asynccall(responsecallback)); 创建一个asynccall,将enqueue的callback传递过去。asynccall其实是一个runnable,当调用enqueue()方法的时候就是讲asynccall传递给dispatcher。dispatcher里面封装了一个线程池去执行这个call,executorservice().execute(call); ,这个call就是一个runnable,我们跟进这个runnable的run()方法,我们可以看到里面执行了一个execute() ;所以逻辑最终回到了asynccall的execute()方法。realcall.asynccall.execute()与同步执行的流程有些类似:

    1. response response = getresponsewithinterceptorchain(); 执行网络请求获取网络请求的结果;
    2. responsecallback 返回执行结果;
    3. client.dispatcher().finished(this); 向dispatcher中注销当前请求的call。

    2.3 Dispatcher干了些啥

    通过上面call的执行流程,我们可以看出其实okhttp的核心其实是dispatcher这个分发器,接下来我们详细分析okhttp中dispatcher在网络请求的时候做了些什么事情。

    2.3.1 同步Dispatcher

    首先看下代码:

    /** used by {@code call#execute} to signal it is in-flight. */
    synchronized void executed(realcall call) {
      runningsynccalls.add(call);
    }
    

    从上面的代码可以看出,大体的执行逻辑就是将传递过来的realcall添加进了一个队列中,那么这个runningsynccalls到底是什么?看下源码:

    /** running synchronous calls. includes canceled calls that haven't finished yet. */
    private final deque<realcall> runningsynccalls = new arraydeque<>();
    

    在call同步执行的过程中,dispatcher仅仅将call放进了runningsynccalls这个队列,其他的什么都没有做,这个队列包含了正在执行的call,而将call注册该队列主要的作用是方便全局管理运行的call。

    2.3.2 异步Dispatcher

    首先我们看看代码:

    synchronized void enqueue(asynccall call) {
      if (runningasynccalls.size() < maxrequests && runningcallsforhost(call) < maxrequestsperhost) {
        runningasynccalls.add(call);
        executorservice().execute(call);
      } else {
        readyasynccalls.add(call);
      }
    }
    

    在异步调度中,传递过来的asynccall是被放在线程池中进行处理的。这个线程池是什么?来看看代码:

    public synchronized executorservice executorservice() {
      if (executorservice == null) {
        executorservice = new threadpoolexecutor(0, integer.max_value, 60, timeunit.seconds,
            new synchronousqueue<runnable>(), util.threadfactory("okhttp dispatcher", false));
      }
      return executorservice;
    }
    

    默认该线程池是一个不限制大小的线程池。从前面代码我们可以看到,dispatcher会限制每一个host请求的最大限制,private int maxrequestsperhost = 5; 同时也会限制同时执行的最大请求数量,private int maxrequests = 64;。在runningasynccalls队列中,保留全部满足限制条件而正在被executorservice执行的所有asynccall,而不满足限制条件的则由readyasynccalls进行保存。

    在异步调度中如果当前的call不能立即入队执行的话,那么会执行readyasynccalls.add(call);这个方法不会立即执行而是需要进行等待 ,进入等待则是说明当前的情况是要么有64条线程正在并发,要么同一个地址有5 个请求,那么等待的call什么时候会被再次执行呢?
    他的触发条件为:

    1. Dispatcher 的setMaxRequestPerHost() 方法被调用时候 ;
    2. Dispatcher 的setMaxRequests() 被调用时候;
    3. 当有一条请求结束了 执行了finish()的出队操作, 这个时候会触promoteCalls()方法执行 ,从而进行调整。�
    

    2.3.3 dispatcher的finished()

    在finished()中我们看看其实现原理:

    /** used by {@code asynccall#run} to signal completion. */
    void finished(asynccall call) {
      finished(runningasynccalls, call, true);
    }
    
    /** used by {@code call#execute} to signal completion. */
    void finished(realcall call) {
      finished(runningsynccalls, call, false);
    }
    
    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) promotecalls();
        runningcallscount = runningcallscount();
        idlecallback = this.idlecallback;
      }
    
      if (runningcallscount == 0 && idlecallback != null) {
        idlecallback.run();
      }
    }
    

    同步或者异步dispatcher的finished()方法最后都会执行到finished()方法,在该方法中除了会从runningsyncalls队列中移除当前正在被执行的call,异步方法还会检查由于限制条件(这里的限制条件是指最大请求数与单个host最大的请求数量)而保存在readyasyncalls队列中的asynccall从而进行移除。在异步方法中关键代码:finished(runningasyncalls, call, true);第三个参数就是判断是否需要移除readyasyncalls中的call。

    private void promotecalls() {
      if (runningasynccalls.size() >= maxrequests) return; // already running max capacity.
      if (readyasynccalls.isempty()) return; // no ready calls to promote.
    
      for (iterator<asynccall> i = readyasynccalls.iterator(); i.hasnext(); ) {
        asynccall call = i.next();
    
        if (runningcallsforhost(call) < maxrequestsperhost) {
          i.remove();
          runningasynccalls.add(call);
          executorservice().execute(call);
        }
    
        if (runningasynccalls.size() >= maxrequests) return; // reached max capacity.
      }
    }
    

    在promotecalls()中,主要的逻辑就是如果当前线程大于maxRequest则不进行操作,如果小于maxRequest则遍历整个readyAsyncCalls,取出一个call,并把这个call放入runningAsyncCalls,然后执行execute。如果在遍历过程中runningAsyncCalls超过maxRequest则不再添加,否则一直添加。总结一下,promoteCalls()负责ready的Call到running的call的转换,在finished()方法中,所有的call,不管是realcall或者asynccall都会在执行完毕之后检测是否存在正在进行的http请求,检测的方法为:

    public synchronized int runningcallscount() {
      return runningasynccalls.size() + runningsynccalls.size();
    }
    

    当判断runningcallscount为0的时候,且该idlecallback存在的时候,回调idlecallback的run()方法。那么idlecallback什么时候存在?或者说调度器什么时候处于空闲状态?继续分析源码:

    /**
     * set a callback to be invoked each time the dispatcher becomes idle (when the number of running
     * calls returns to zero).
     *
     * <p>note: the time at which a {@linkplain call call} is considered idle is different depending
     * on whether it was run {@linkplain call#enqueue(callback) asynchronously} or
     * {@linkplain call#execute() synchronously}. asynchronous calls become idle after the
     * {@link callback#onresponse onresponse} or {@link callback#onfailure onfailure} callback has
     * returned. synchronous calls become idle once {@link call#execute() execute()} returns. this
     * means that if you are doing synchronous calls the network layer will not truly be idle until
     * every returned {@link response} has been closed.
     */
    public synchronized void setidlecallback(@nullable runnable idlecallback) {
      this.idlecallback = idlecallback;
    }
    

    从上面方法中追溯到,调度器的空闲状态在异步方法调度时,在callback的onResponse()方法或者onfailed()方法被回调的时候,调度器处于空闲状态。同步方法中只有在excute()方法返回的时候才会处于空闲状态。

    3. 拦截器

    在okhttp中真正核心的除了上面提到的dispatcher还有就是拦截器interceptor。在同步请求方法或者异步请求方法中,都会执行一条很重要的指令getresponsewithinterceptorchain()该方法就是拦截器的入口方法,接下来源码分析该方法:

    response getresponsewithinterceptorchain() throws ioexception {
      // build a full stack of interceptors.
      list<interceptor> interceptors = new arraylist<>();
      interceptors.addall(client.interceptors());
      interceptors.add(retryandfollowupinterceptor);
      interceptors.add(new bridgeinterceptor(client.cookiejar()));
      interceptors.add(new cacheinterceptor(client.internalcache()));
      interceptors.add(new connectinterceptor(client));
      if (!forwebsocket) {
        interceptors.addall(client.networkinterceptors());
      }
      interceptors.add(new callserverinterceptor(forwebsocket));
    
      interceptor.chain chain = new realinterceptorchain(interceptors, null, null, null, 0,
          originalrequest, this, eventlistener, client.connecttimeoutmillis(),
          client.readtimeoutmillis(), client.writetimeoutmillis());
    
      return chain.proceed(originalrequest);
    }
    

    从上面源码中可以看到,在一次网络请求中其实是经历了多次拦截器的调用,首先先用一个流程图来大体的分析各个拦截的调用过程:


    OkHttp拦截器.png

    从上面的流程可以看出,okhttp调用了多个拦截器来对一个请求做拦截操作,那么这样的操作是怎么完成的呢?其实在每一个拦截器后面都会调用RealInterceptorChain.proceed()来进行处理,回到上面的源码,okhttp将所有的拦截器添加进入了一个集合之中,利用自增递增来调用RealInterceptorChain.proceed() 来依次处理添加进入的拦截器。

    跟进下RealInterceptorChain的源码:

    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
    
        //...异常判断省略
    
        // 获取下一个拦截器.
        RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec
        , connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout);
        Interceptor interceptor = interceptors.get(index);
        Response response = interceptor.intercept(next);
    
        // Confirm that the next interceptor made its required call to chain.proceed().
        if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
          throw new IllegalStateException("network interceptor " + interceptor
              + " must call proceed() exactly once");
        }
    
        // Confirm that the intercepted response isn't null.
        if (response == null) {
          throw new NullPointerException("interceptor " + interceptor + " returned null");
        }
    
        if (response.body() == null) {
          throw new IllegalStateException(
              "interceptor " + interceptor + " returned a response with no body");
        }
    
        return response;
      }
    

    RealInterceptorChain 构造函数中的index与interceptor进行对应,通过interceptor.intercept(next)方法在各个拦截器里面又能通过next参数继续调用proceed()方法,完成递归调用。至于各个拦截器具体的功能,相信大家都大概清楚,这里就不多阐述了。

    4. 责任链设计模式

    在okHttp中拦截器部分设计到的核心就是责任链设计模式,首先回顾下什么是责任链模式:使多个对象都有机会处理 同一个请求,从而避免请求的发送者与接收者之间的耦合关系。将这些对象连接成一条链,并沿着这条链传递 请求,直到有一个对象处理它为止。
    现在我们来剖析OkHttp中的调用链,回顾下在OkHttp中的调用链结构:

    OkHttp拦截器责任链.png

    上面的调用模式核心是通过Interceptor来进行完成的,该接口主要的作用为添加、移除、转换请求或者回应头部信息。瞟一眼该接口的源码:

    public interface Interceptor {
    Response intercept(Chain chain) throws IOException;
    
    interface Chain {
      Request request();
    
      Response proceed(Request request) throws IOException;
      //省略
      }
    }
    

    Interceptor 中intercept(Chain chain)方法负责具体的过滤,而它子类中又调用了Chain,
    该Chain其实是指RealInterceptorChain ,在RealInterceptorChain中持有了一个interceptor
    的集合 ,通过递归调用该List中的拦截器, 对发起的请求进行层层处理,最后递归结束返回请求结果。

    4.1 具体分析责任链模式

    上面大体的说明了在OkHttp中责任链的调用,现在详细分析该责任链怎么完成调用。责任链模式其实分为完全责任链模式与不完全责任链模式。完全责任链模式是指: 当请求到达某个处理类的时候,要么处理,要么传递给下个处理类不完全责任链模式: 当请求到达时候, 处理一部分( 可以配置一些参数或者 增加请求头等),然后扔给下一个处理类进行处理。在Okhttp中,调用的责任链就是不完全的责任链模式。责任链模式的特点:

    1. 抽象一个处理任务类;
    2. 任务处理类持有下一个任务对象;
    3. 完全责任链模式处理请求任务时类似于if-else,不完全责任链模式则对一个请求进行分步处理。
    

    在OkHttp中,发起的请求经过拦截器的层层处理最终返回结果,它具体是怎么操作的? 回溯到Okhttp的 getResponseWithInterceptorChain()该方法在okHttp中同步或者异步最终调用的方法,也是责任链调用的起始方法。

    Response getResponseWithInterceptorChain() throws IOException {
     // Build a full stack of interceptors.
     List<Interceptor> interceptors = new ArrayList<>();
     // 添加各种拦截器
    
     Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
         originalRequest, this, eventListener, client.connectTimeoutMillis(),
         client.readTimeoutMillis(), client.writeTimeoutMillis());
     return chain.proceed(originalRequest);
    }
    

    接下来介绍核心类RealInterceptorChain ,该类是整个责任链的调度中心,该类继承了Interceptor的内部接口Chain

    interface Chain {
    Request request();
    Response proceed(Request request) throws IOException;
    //省略...
    
    

    request() 获取的是当前的请求,而proceed() 就是负责具体的转发任务, 看看RealInterceptorChain中的proceed() 方法:

    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
       RealConnection connection) throws IOException {
     // 省略..
    
     // Call the next interceptor in the chain.
     RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
         connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
         writeTimeout);
     Interceptor interceptor = interceptors.get(index);
     Response response = interceptor.intercept(next);
    
     //省略..
    
     return response;
    }
    

    该方法核心的就这么几步:

    1. 根据传入进来的责任链集合和索引获取下一个责任链(next) ,即每一个拦截器会持有下一个链对象;
    2. 从拦截器集合中获取当前的拦截器;
    3. 拦截处理下一个责任链(该链中传入当前的请求,只是索引+1 ,那么执行的逻辑就是当前的拦截器如果处理了就直接返回,如果没有执行或者执行了部分,那么就封装一个新的请求对象,由下一个链进行处理,下一个链又对应一个拦截器,同理,如果处理了就直接返回,没有处理了话,下一个拦截器处理一部分再次扔给拦截器集合中的下一个拦截器,这样进行递归调用,直到返回结果)。
    

    具体调用如下:


    责任链调用过程.gif

    整体流程可以看下图:


    OkHttp责任链执行流程.png

    上述图片描述的就是责任链的调度过程,关于OkHttp的后续内容,后面文章继续。

    如果文章中有什么疏漏或者错误的地方,还望各位指正,你们的监督是我最大的动力,谢谢!

    相关文章

      网友评论

          本文标题:Android--OkHttp理解系列(一)

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