美文网首页
笔记--OkHttp拦截器

笔记--OkHttp拦截器

作者: zhangyu1991 | 来源:发表于2020-07-04 12:42 被阅读0次

    今天捋清楚了OkHttp的拦截器机制,并且理解之后试着模仿着写了一套拦截器的架构。把笔记记下来,方便以后查看。

    OkHttp拦截器

    用OkHttp做网络请求的时候,可以很方便的根据自身需要构造很多拦截器,以实现不同的功能,对请求体和返回信息进行处理。甚至可能这个机制太好用了,连OkHttp本身最后对服务器的请求也是通过定义一个拦截器来实现的,这个拦截器叫CallServerInterceptor,是拦截器链条里面的最后一个拦截器。
    我们来看一个基本的网络请求,它的流程很简单:请求,等待响应,拿到响应信息,结束。
    大致如下图:


    基本网络请求流程

    但是很多时候我们有需求在这个流程中做很多自己需要的操作,比如打印日志啦,添加统一的请求消息头啦,根据返回信息的共同特征做特定的统一操作啦(比如token过期跳登录页面),还有序列化对象啦等等等等很多各式各样的需求。这个时候拦截器就派上用场了,它的功能极其强大,基本可以满足你在从请求到返回响应过程中想做的一切骚操作。
    我们看看加入拦截器之后流程的变化:


    添加一个拦截器
    我们捋一捋拦截器做的这几件事

    [1] 首先,拦截器接收一个request,拿到这个request之后,可以决定要不要做加工处理,然后把加工好的request交给下一个拦截器,这是第一件事。
    [2] 自己处理完了之后,要驱动下一个拦截器执行它的操作,不能一层一层处理到自己这里就停下来了,导致链条运行中断,这一步必须做。这是第二件事。
    [3] 处理完request,还要看看下一层返回来的response自己要不要处理一下,加工好了之后再把它丢给上一层。这是第三件事。
    [4] 第四件事其实包含在1和3里面,就是根据上一层传下来的request和下一层返回的response信息决定要不要做额外的操作,这个其实是附带的操作,和整体拦截器的运行流程相关性不大。

    根据上面几条,依次来看,我们来看看实现一个拦截器要实现的基本方法:

    class InterceptorA implements Interceptor{
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                //处理requeset...
                Response response = chain.proceed(request);
                //处理response...
                return response;
            }
        }
    

    实现拦截器需要实现一个方法,就是intercept方法,但是传入的参数却不是Request,而是一个Chain对象,这个对象,是一个灵魂中枢。
    通过上面的描述,我们可以知道,拦截器机制其实是通过责任链模式来实现的,所以这个灵魂中枢对象起名为Chain(链条)。
    Chain里面保存了传入的Request对象,可以随时取出来进行加工处理。
    Chain里面保存了整个的拦截器列表,可以取出下一个拦截器,执行intercept方法,也因此,Chain可以从下一个拦截器的intercept方法中获取下一层返回回来的Response,然后对Response进行加工处理,再返回给上一层。
    这就是上面的拦截器要做的三件事,都依赖Chain这个对象。
    然后我们来看看Chain的实现,看看它都做了那些事...
    我们知道,责任链模式中每一个链块执行完自己的操作之后都要继续驱动下一个链块继续执行操作,这就要求每一个链块需要保存有下一个链块的执行方法入口,有的人通过一个公共的管理方法来驱动执行,也有的直接保存下一个链块的对象,拦截器这里的处理方式是,让每一个链块都持有了整个拦截器列表的实体,执行完这一个之后,从列表中取出下一个,继续执行。

    /**
     * A concrete interceptor chain that carries the entire interceptor chain: all application
     * interceptors, the OkHttp core, all network interceptors, and finally the network caller.
     */
    public final class RealInterceptorChain implements Interceptor.Chain {
      private final List<Interceptor> interceptors;//重点
      private final StreamAllocation streamAllocation;
      private final HttpCodec httpCodec;
      private final RealConnection connection;
      private final int index;//重点
      private final Request request;
      private final Call call;
      private final EventListener eventListener;
      private final int connectTimeout;
      private final int readTimeout;
      private final int writeTimeout;
      private int calls;
      ...
      ...
    }
    

    上面的 List<Interceptor> interceptors是整个所有的拦截器列表,index是当前的执行位置,根据index,可以从list中取出下一个有效的拦截器,继续执行。
    它的构造方法里有11个参数:

    public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
          HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
          EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
        this.interceptors = interceptors;
        this.connection = connection;
        this.streamAllocation = streamAllocation;
        this.httpCodec = httpCodec;
        this.index = index;
        this.request = request;
        this.call = call;
        this.eventListener = eventListener;
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
        this.writeTimeout = writeTimeout;
      }
    

    其中和拦截器运行机制相关的关键参数有三个:

    List<Interceptor> interceptors;//完整的拦截器列表
    int index;//当前的执行位置
    Request request;//上一层传入的Request
    

    这就是它驱动下一层拦截器继续执行所依赖的基本信息。
    那么具体怎么取出下一个拦截器继续执行下一层的呢?
    我们都知道,在实现Interceptor接口的时候,去实现它的intercept(Chain chain)方法,有一行代码是必须要执行的,那就是chain.proceed(request),哪怕你啥也不做,你也得写成下面这个样子:

    class InterceptorB implements Interceptor{
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                return chain.proceed(request);
            }
        }
    

    为啥必须执行这行代码呢,因为就是它负责去驱动下一层拦截器执行操作,不写这行代码,拦截器链条走到这一层,就断了,整个链条的处理就会停在这里。
    我们来看一下proceed方法的具体实现:

    @Override 
    public Response proceed(Request request) throws IOException {
        return proceed(request, streamAllocation, httpCodec, connection);
     }
     public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();
    
        calls++;
    
        // If we already have a stream, confirm that the incoming request will use it.
        if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
          throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
              + " must retain the same host and port");
        }
    
        // If we already have a stream, confirm that this is the only call to chain.proceed().
        if (this.httpCodec != null && calls > 1) {
          throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
              + " must call proceed() exactly once");
        }
    
        // 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);
    
        // 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;
      }
    

    取出里面关键的代码出来看:

    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);
        ...
        ...
    }
    

    上面的注释也写的很清晰了:调用链条中的下一个拦截器。第一行代码,构造下一个拦截器的Chain,传入本层的request,index+1,以及拦截器列表。然后从拦截器列表中取出下一个拦截器,传入构造好的Chain对象,执行intercept(chain)方法。下一层的方法处理完,返回response,本层通过proceed()方法取得response,再决定要不要继续做处理,再继续返回给上一层。
    到这一步,基本就把拦截器的工作流程梳理清楚了,再回头来看实现拦截器的方法里做的事情,就清晰很多了:

    class InterceptorA implements Interceptor{
            @Override
            public Response intercept(Chain chain) throws IOException {
                //上一层构造好的chain对象,传入本层,里面包含了整个的拦截器列表和上一层传入的request对象
                Request request = chain.request();//chain里面可以随时取出request对象进行加工处理
                //处理requeset...
    
                //porceed方法负责将本层加工处理好的request对象构造出下一层的chain对象
                //并驱动执行下一层拦截器的intercept(Chain chain) 方法,获取下一层返回的response
                Response response = chain.proceed(request);
                
                //获取到下一层返回回来的response之后,根据自身需要来决定是否要处理response或者根据
                //response的信息决定要不要做一些额外的事情
                //处理response...
    
                return response;//返回response给上一层
            }
        }
    

    这就是单个拦截器所做的所有事情。
    然后,当加入多个拦截器之后,处理的大致流程就变得如下图所示:


    多层拦截器

    以上,拦截器整个的运行机制基本就梳理完了。

    ...@Y(^ _ ^)Y@ ...

    相关文章

      网友评论

          本文标题:笔记--OkHttp拦截器

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