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

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

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

每日一问-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?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

构建之法

构建之法

邹欣 / 人民邮电出版社 / 2014-9 / 49.00元

内容简介: 软件工程牵涉的范围很广, 同时也是一般院校的同学反映比较空洞乏味的课程。 但是软件工程的技术对于投身IT 产业的学生来说是非常重要的。作者邹欣有长达20年的一线软件开发经验,他利用业余时间在数所高校进行了长达6年的软件工程教学实践,总结出了在16周的时间内让 同学们通过 “做中学 (Learning By Doing)” 掌握实用的软件工程技术的教学计划,并得到高校师生的积极反馈......一起来看看 《构建之法》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具