为什么 HTTP Code 204 会导致 Retrofit 出现 NullPointerException?

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介:今天在做网络接口的时候, 一个返回结果应该是根据错误信息, 进入代码可得:此处代码, 说明服务器返回的成功,

每日一问-Android-20181031

为什么 HTTP Code 204 会导致 Retrofit 出现 NullPointerException?

答:

今天在做网络接口的时候, 一个返回结果应该是 Map 的接口出现了异常:

java.lang.NullPointerException: The mapper function returned a null value.
        at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
        at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
        at retrofit2.adapter.rxjava2.CallExecuteObservable.subscribeActual(CallExecuteObservable.java:44)
        at io.reactivex.Observable.subscribe(Observable.java:12030)
        at io.reactivex.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java:33)
        at io.reactivex.Observable.subscribe(Observable.java:12030)
        at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
        at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:579)
        at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
        at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)

根据错误信息, 进入代码可得:

static final class MapObserver<T, U> extends BasicFuseableObserver<T, U> {
    final Function<? super T, ? extends U> mapper;
    //省略代码

    @Override
    public void onNext(T t) {
        if (done) {
            return;
        }

        if (sourceMode != NONE) {
            actual.onNext(null);
            return;
        }

        U v;

        try {
            v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value.");
        } catch (Throwable ex) {
            fail(ex);
            return;
        }
        actual.onNext(v);
    }
    //省略代码
}

此处代码, 说明服务器返回的成功, 已经进入 onNext 回调了, 但是其 value 是 null , 我们在往上看调用信息, 在实际的  subscribeActual 方法中将 Response 进行传递.

//retrofit2.adapter.rxjava2.CallExecuteObservable#subscribeActual
@Override protected void subscribeActual(Observer<? super Response<T>> observer) {
    // Since Call is a one-shot type, clone it for each new observer.
    Call<T> call = originalCall.clone();
    CallDisposable disposable = new CallDisposable(call);
    observer.onSubscribe(disposable);

    boolean terminated = false;
    try {
      Response<T> response = call.execute();
      if (!disposable.isDisposed()) {
        observer.onNext(response);
      }
      if (!disposable.isDisposed()) {
        terminated = true;
        observer.onComplete();
      }
    } catch (Throwable t) {
      //省略代码
    }
  }

这里的 Response 是 retrofit 内置的 Resonse 不是 Okhttp 里面的 Response, 部分代码如下:

//
/** An HTTP response. */
public final class Response<T> {
  //省略代码

  /**
   * Create a successful response from {@code rawResponse} with {@code body} as the deserialized
   * body.
   */
  public static <T> Response<T> success(@Nullable T body, okhttp3.Response rawResponse) {
    checkNotNull(rawResponse, "rawResponse == null");
    if (!rawResponse.isSuccessful()) {
      throw new IllegalArgumentException("rawResponse must be successful response");
    }
    return new Response<>(rawResponse, body, null);
  }

  //省略代码
}

这个 Response 怎么来的呢?看 subscribeActual 方法这个代码块:

Response<T> response = call.execute();
if (!disposable.isDisposed()) {
    observer.onNext(response);
}

是 Okhttp 的 Call 的执行结果, 这里的实现类是 OkHttpCall<T> :

@Override public Response<T> execute() throws IOException {
    okhttp3.Call call;
    //省略代码
    return parseResponse(call.execute());
  }

Call 执行以后对结果进行解析处理:

 Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
    ResponseBody rawBody = rawResponse.body();

    // Remove the body's source (the only stateful object) so we can pass the response along.
    rawResponse = rawResponse.newBuilder()
        .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
        .build();

    int code = rawResponse.code();
    if (code < 200 || code >= 300) {
      try {
        // Buffer the entire body to avoid future I/O.
        ResponseBody bufferedBody = Utils.buffer(rawBody);
        return Response.error(bufferedBody, rawResponse);
      } finally {
        rawBody.close();
      }
    }

    if (code == 204 || code == 205) {
      rawBody.close();
      return Response.success(null, rawResponse);
    }

    ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
    try {
      T body = serviceMethod.toResponse(catchingBody);
      return Response.success(body, rawResponse);
    } catch (RuntimeException e) {
      // If the underlying source threw an exception, propagate that rather than indicating it was
      // a runtime exception.
      catchingBody.throwIfCaught();
      throw e;
    }
  }

重点来了:在这个解析的代码中, 如果服务器返回的 HTTP Code 是 204 或者 205, 那么其 body 会置为 null, 所以会出现 NullPointException

那么, HTTP Code 204 是什么意思呢? 这里引用 Mozilla 的说明:

HTTP协议中 204 No Content 成功状态响应码表示目前请求成功,但客户端不需要更新其现有页面。204 响应默认是可以被缓存的。在响应中需要包含头信息 ETag。

其实就是, 请求服务器成功了, 但是没有数据返回给你.

实际的网络请求是什么样呢(接口已做马赛克处理)?

D/OkHttp: <-- 204 No Content https://xxxxxxxx (2241ms)
D/OkHttp: Server: nginx
D/OkHttp: Date: Wed, 31 Oct 2018 13:50:15 GMT
D/OkHttp: Connection: keep-alive
D/OkHttp: X-Application-Context: dating:publicCloud
D/OkHttp: Access-Control-Allow-Headers: DNT,X-FROM-APP,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range
D/OkHttp: Access-Control-Expose-Headers: DNT,X-FROM-APP,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range
D/OkHttp: Access-Control-Allow-Origin: *
D/OkHttp: Access-Control-Allow-Methods: GET,POST,HEAD,OPTIONS,PUT,DELETE
D/OkHttp: Access-Control-Max-Age: 1728000
D/OkHttp: <-- END HTTP

从日志上上看,服务器确实返回了 204, 那么我们怎么处理这个异常呢?

从实际的现象来看, 这个异常只是打印了异常信息, 而不会引发程序的崩溃. 个人认为可以这样处理:

  • 不做任何处理
  • 将此 204 使用 Observable 的 map 转换分发到 onException 分支

如果服务器返回的数据格式类似下面这样:

{
    "code" : 10000,
    "desc" : "success",
    "data" : "",
 }

那就在 结果返回时添加 Function 转换, 返回一个data 为 null 的 JavaBean.

操作过程:

1.将 rest service 的接口返回值改为: Observable<Response<BaseResult<Map<String, String>>>>
2.创建新的 Function类:

public class ResponseFun<T> implements Function<Response<BaseResult<T>>, BaseResult<T>> {

    @Override
    public BaseResult<T> apply(Response<BaseResult<T>> response) {
        if (response.isSuccessful()) {
            if (response.code() == HttpURLConnection.HTTP_NO_CONTENT
                    || response.code() == HttpURLConnection.HTTP_RESET
                    || response.body() == null) {
                BaseResult<T> result = new BaseResult<>();
                result.setCode(10000);
                result.setDesc("success");
                return result;
            } else {
                return response.body();
            }
        }
        throw new HttpException(response);
    }
}

3.调用处在处理数据的时候要做判 null 处理.


以上所述就是小编给大家介绍的《为什么 HTTP Code 204 会导致 Retrofit 出现 NullPointerException?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python Machine Learning

Python Machine Learning

Sebastian Raschka / Packt Publishing - ebooks Account / 2015-9 / USD 44.99

About This Book Leverage Python' s most powerful open-source libraries for deep learning, data wrangling, and data visualization Learn effective strategies and best practices to improve and opti......一起来看看 《Python Machine Learning》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具