问题描述

okttp3 + rxJava 十分经典,被大量运用到Android app。作为一款网络请求利器,设计上及其巧妙,性能优越,语义规范明确,该组合得到广大开发者的欢迎和认可。

不过,在使用拦截器处理请求预处理或者嵌套请求的时候,发现了一个java.lang.NullPointerException。发生的代码逻辑是:当检查到异常就给请求框架返回一个null。然而,okttp3 并不会直接把这个null返回给上层框架处理,而是直接抛出运行时异常。

错误的信息如下:

java.lang.NullPointerException: interceptor cn.xxx.xxx.net.interceptor.RetryInterceptor@7911838 returned null
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:157)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at cn.xxx.xxx.net.interceptor.TimeCalibrationInterceptor.intercept(TimeCalibrationInterceptor.java:28)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at cn.dasyun.controlboardbusi.net.interceptor.TokenInterceptor.intercept(TokenInterceptor.java:62)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:212)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:254)
        at okhttp3.RealCall.execute(RealCall.java:92)
        at retrofit2.OkHttpCall.execute(OkHttpCall.java:188)
        at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall.execute(DefaultCallAdapterFactory.java:104)
        at cn.xxx.xxx.service.ClientService.lambda$checkCamera$0$ClientService(ClientService.java:450)
        at cn.xxx.xxx.service.-$$Lambda$ClientService$A3xHPJOiMiORStzuxUJBZ0MjDFU.run(lambda)
        at java.lang.Thread.run(Thread.java:761)

问题分析

打开处理interceptor 的okhttp源码,我们找到抛出null异常的代码块进行分析。

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

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

在代码中我们看到这一处代码:


// Confirm that the intercepted response isn't null.
  if (response == null) {
    throw new NullPointerException("interceptor " + interceptor + " returned null");
  }

如果返回的response为null,则直接抛出NullPointerException。至此为止,我们知道框架限制我们返回一个null给业务逻辑,那么如何理解这一行为呢?

刚好在前几天看到一个前辈的描述:外国人认为程序如果不能正常运行,则会使用异常机制来中断业务逻辑处理;而国内的人喜欢返回一个默认值或者null来让上一层业务进行处理,从而使业务能够进行下去。

前辈的话解答了在下的困惑,我们习惯返回一个默认值或者null给业务,业务针对各自的逻辑进行处理,而框架的制定者(外国友人)觉得这是一个异常,不应该流转到业务层再进行处理。

至于说,以上两种编程理念哪种更优,这是个仁者见仁智者见智的事情,各自按照心中的答案行事即可。

解决办法

那么针对这个异常应该如何解决呢?上文其实已经给出答案,那就是在自定义的interceptor 中使用异常来告诉框架我的处理流程已经中止,不需要进行额外的操作。

根据函数定义Response proceed(Request request) throws IOException ,我们可以在代码里面使用:

if (response == null) {
            throw new IOException();
        }