Android面试有迹可循(一)OkHttp3.9拦截器原理与区别

接上回 传送门

上回我们讲到,OkHttp的请求过程中有个非常重要的东西-“拦截器”,而且拦截器又分为interceptors和networkInterceptors两种,那它们具体有何区别呢?又要怎么来使用?现在来一探究竟

拦截器工作原理

在弄清楚区别之前,要先知道他们工作的原理,还是来到RealCall.execute方法里面的getResponseWithInterceptorChain:

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

其实可以看出来,这个方法主要就是添加各种interceptor,然后文章最开始提到的第一个interceptors其实是自定义的拦截器,添加到了最前面,然后依次添加了四种拦截器过后,第五个是networkInterceptors,最后一共加了七种拦截器。

拦截器责任链

在这之后他们是怎么执行的?这就要说说Interceptor.Chain这个类了,其实从这个getResponseWithInterceptorChain方法名也可知一二,划重点了!这里用到了责任链模式,在请求的各个阶段都有相应的拦截器来负责,这些拦截器组成了一个链。在这里首先实例化成了RealInterceptorChain,来到这个类的构造方法来看看:

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

这里主要是初始化了各种属性,这里面的参数大多数和OkHttp的属性无异,但需要注意的是这里的interceptors不同于OkHttp构造里面的那个interceptors,这里是表示刚才组成的所有拦截器的数组,包含interceptors和networkInterceptors在内的七种拦截器,然后index这个属性初始化为“0”。

接下来在getResponseWithInterceptorChain最后一句执行了它的proceed方法,在这个方法里有很多抛出异常的语句,这里不去深究,主要看看中间主要的一两句:

RealInterceptorChain.proceed

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

    // 重点!!!主要就看看这几句
    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,取名为next(从名字真的可以看出很多蹊跷),构造参数没变化,只是把index+1

然后:从interceptors数组中用index来依次取出Interceptor(每次执行index都会+1,所以是依次取出来);

最后:调用interceptor.intercept(next),把next传入调用intercept方法

完;

这里如果直接点进去intercept方法会发现,这个方法在接口中:

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

所以要去它的具体实现类去看看这个方法,从一开始的getResponseWithInterceptorChain方法可知,我们首先加入的拦截器是自定义的interceptors,第二个是retryAndFollowUpInterceptor,这里我们对拦截器的具体作用按下不表,先看看责任链的执行,找到RetryAndFollowUpInterceptor的intercept方法,方法很长,只截取了关于chain操作的部分:

RetryAndFollowUpInterceptor.intercept(Chain chain)

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    
    response = realChain.proceed(request, streamAllocation, null, null);
}

这个方法里面会对一些参数做修改,又调用了proceed方法,在proceed方法里我们知道index又会+1,然后取出下一个拦截器并且传入修改后的参数。这就完成了这个链式结构的运转,一步一步的往下传递,然后到了最后又依次返回Response

各种拦截器

看到这里,不禁会问,上面看到的那么多种拦截器到底分别是用来干啥的呢,在这里来总结一下(来自网络,只是做个归纳):

RetryAndFollowUpInterceptor

用来实现连接失败的重试和重定向

BridgeInterceptor

用来修改请求和响应的 header 信息

CacheInterceptor

用来实现响应缓存。比如获取到的 Response 带有 Date,Expires,Last-Modified,Etag 等 header,表示该 Response 可以缓存一定的时间,下次请求就可以不需要发往服务端,直接拿缓存的

ConnectInterceptor

用来打开到服务端的连接。其实是调用了 StreamAllocation 的newStream 方法来打开连接的。建联的 TCP 握手,TLS 握手都发生该阶段。过了这个阶段,和服务端的 socket 连接打通

CallServerInterceptor

用来发起请求并且得到响应。上一个阶段已经握手成功,HttpStream 流已经打开,所以这个阶段把 Request 的请求信息传入流中,并且从流中读取数据封装成 Response 返回

上一张图,看着直观一点(话说这个其实看着跟Android的点击事件分发机制很像啊)

Interceptor

最后来说说这个面试题

看到这里,对OkHttp的拦截器机制也了解了个大概,但是开篇提到的那个问题的答案到底是什么呢?

interceptors和networkInterceptors分别位于拦截器链的第一个和第六个,从每个拦截器的作用大致猜到一些。interceptors是肯定每次都会执行的,但是,在networkInterceptors之前有个ConnectInterceptor,这个拦截器的作用是用于建立跟服务器的连接。那如果我们现在设备离线,直接读取缓存呢?对,那这样的话networkInterceptors就不会执行了。

当然,这只是他们的一个最容易理解的区别,这个问题在OkHttp的官方文档已经给了我们答案 OkHttp文档中对于这两个拦截器的解释,下面来结合资料做个翻译

OkHttp文档图片

Application interceptors

  • Don't need to worry about intermediate responses like redirects and retries.
  • Are always invoked once, even if the HTTP response is served from the cache.
  • Observe the application's original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
  • Permitted to short-circuit and not call Chain.proceed().
  • Permitted to retry and make multiple calls to Chain.proceed().
  • 不需要担心中间过程的响应,如重定向和重试
  • 只会被调用一次,即使这个响应是从缓存中获取的
  • 只关注最原始的请求, 不关心OkHttp注入的头信息如: If-None-Match
  • 可以中断调用过程,有权决定是否要执行Chain.proceed()
  • 允许重试,可以多次调用Chain.proceed()

Network Interceptors

  • Able to operate on intermediate responses like redirects and retries.

  • Not invoked for cached responses that short-circuit the network.

  • Observe the data just as it will be transmitted over the network.

  • Access to the Connection that carries the request.

  • 能够操作中间过程的响应,如重定向和重试.

  • 当返回缓存时不被调用.(也就是我们刚才举的例子)

  • 观察在网络上传输的数据变化,比如重定向

  • 携带请求来访问连接(这里已经建立了连接)

结题

看懂了拦截器其实也就是看懂了OkHttp,它的整个工作原理其实就是以拦截器责任链模式为核心,这种模式之下,我们可以很方便的来定制我们自己拦截器,比如可以改变请求头,处理缓存等等等等,不知道还有没有什么关于OkHttp的知识点呢?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容