【问题标题】:Centralized error handling retrofit 2?集中式错误处理改造 2?
【发布时间】:2016-05-03 23:11:21
【问题描述】:

在改造 2 之前,有一种集中处理错误的方法 -

new retrofit.RestAdapter.Builder()
        .setEndpoint(apiUrl)
        .setLogLevel(retrofit.RestAdapter.LogLevel.FULL)
        .setErrorHandler(new CustomErrorHandler(ctx))

但现在在 Retrofit 2 中,RestAdapter 已重命名为 Retrofit,并且没有 setErrorHandler()。有没有办法使用Retrofit.Builder() 进行集中式错误处理?

【问题讨论】:

    标签: android retrofit2


    【解决方案1】:

    好吧,您所要做的就是使用您的自定义回调创建一个自定义CallAdapter,以应对失败的情况。 Retrofit repo 有一个显示自定义CallAdapter 的示例。你可以在retrofit/samples找到它。

    这是一个显示自定义 CallAdapter 的示例(不适用于 2.2.0 之前的版本):

    /*
     * Copyright (C) 2015 Square, Inc.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.example.retrofit;
    
    import java.io.IOException;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.concurrent.Executor;
    import javax.annotation.Nullable;
    import retrofit2.Call;
    import retrofit2.CallAdapter;
    import retrofit2.Callback;
    import retrofit2.converter.gson.GsonConverterFactory;
    import retrofit2.Response;
    import retrofit2.Retrofit;
    import retrofit2.http.GET;
    
    /**
     * A sample showing a custom {@link CallAdapter} which adapts the built-in {@link Call} to a custom
     * version whose callback has more granular methods.
     */
    public final class ErrorHandlingAdapter {
      /** A callback which offers granular callbacks for various conditions. */
      interface MyCallback<T> {
        /** Called for [200, 300) responses. */
        void success(Response<T> response);
        /** Called for 401 responses. */
        void unauthenticated(Response<?> response);
        /** Called for [400, 500) responses, except 401. */
        void clientError(Response<?> response);
        /** Called for [500, 600) response. */
        void serverError(Response<?> response);
        /** Called for network errors while making the call. */
        void networkError(IOException e);
        /** Called for unexpected errors while making the call. */
        void unexpectedError(Throwable t);
      }
    
      interface MyCall<T> {
        void cancel();
        void enqueue(MyCallback<T> callback);
        MyCall<T> clone();
    
        // Left as an exercise for the reader...
        // TODO MyResponse<T> execute() throws MyHttpException;
      }
    
      public static class ErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
        @Override public @Nullable CallAdapter<?, ?> get(
            Type returnType, Annotation[] annotations, Retrofit retrofit) {
          if (getRawType(returnType) != MyCall.class) {
            return null;
          }
          if (!(returnType instanceof ParameterizedType)) {
            throw new IllegalStateException(
                "MyCall must have generic type (e.g., MyCall<ResponseBody>)");
          }
          Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
          Executor callbackExecutor = retrofit.callbackExecutor();
          return new ErrorHandlingCallAdapter<>(responseType, callbackExecutor);
        }
    
        private static final class ErrorHandlingCallAdapter<R> implements CallAdapter<R, MyCall<R>> {
          private final Type responseType;
          private final Executor callbackExecutor;
    
          ErrorHandlingCallAdapter(Type responseType, Executor callbackExecutor) {
            this.responseType = responseType;
            this.callbackExecutor = callbackExecutor;
          }
    
          @Override public Type responseType() {
            return responseType;
          }
    
          @Override public MyCall<R> adapt(Call<R> call) {
            return new MyCallAdapter<>(call, callbackExecutor);
          }
        }
      }
    
      /** Adapts a {@link Call} to {@link MyCall}. */
      static class MyCallAdapter<T> implements MyCall<T> {
        private final Call<T> call;
        private final Executor callbackExecutor;
    
        MyCallAdapter(Call<T> call, Executor callbackExecutor) {
          this.call = call;
          this.callbackExecutor = callbackExecutor;
        }
    
        @Override public void cancel() {
          call.cancel();
        }
    
        @Override public void enqueue(final MyCallback<T> callback) {
          call.enqueue(new Callback<T>() {
            @Override public void onResponse(Call<T> call, Response<T> response) {
              // TODO if 'callbackExecutor' is not null, the 'callback' methods should be executed
              // on that executor by submitting a Runnable. This is left as an exercise for the reader.
    
              int code = response.code();
              if (code >= 200 && code < 300) {
                callback.success(response);
              } else if (code == 401) {
                callback.unauthenticated(response);
              } else if (code >= 400 && code < 500) {
                callback.clientError(response);
              } else if (code >= 500 && code < 600) {
                callback.serverError(response);
              } else {
                callback.unexpectedError(new RuntimeException("Unexpected response " + response));
              }
            }
    
            @Override public void onFailure(Call<T> call, Throwable t) {
              // TODO if 'callbackExecutor' is not null, the 'callback' methods should be executed
              // on that executor by submitting a Runnable. This is left as an exercise for the reader.
    
              if (t instanceof IOException) {
                callback.networkError((IOException) t);
              } else {
                callback.unexpectedError(t);
              }
            }
          });
        }
    
        @Override public MyCall<T> clone() {
          return new MyCallAdapter<>(call.clone(), callbackExecutor);
        }
      }
    }
    

    以下是您可以如何使用它:
    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://httpbin.org")
        .addCallAdapterFactory(new ErrorHandlingCallAdapterFactory())
        .addConverterFactory(GsonConverterFactory.create())
        .build();
    
    HttpBinService service = retrofit.create(HttpBinService.class);
    MyCall<Ip> ip = service.getIp();
    
    ip.enqueue(new MyCallback<Ip>() {
    
        @Override public void success(Response<Ip> response) {
        System.out.println("SUCCESS! " + response.body().origin);
        }
    
        @Override public void unauthenticated(Response<?> response) {
        System.out.println("UNAUTHENTICATED");
        }
    
        @Override public void clientError(Response<?> response) {
        System.out.println("CLIENT ERROR " + response.code() + " " + response.message());
        }
    
        @Override public void serverError(Response<?> response) {
        System.out.println("SERVER ERROR " + response.code() + " " + response.message());
        }
    
        @Override public void networkError(IOException e) {
        System.err.println("NETWORK ERROR " + e.getMessage());
        }
    
        @Override public void unexpectedError(Throwable t) {
        System.err.println("FATAL ERROR " + t.getMessage());
        }
    });
    

    【讨论】:

      【解决方案2】:

      Retrofit 2.0 移动了 ErrorHandler 并使用了新的 Callback,其中包括两种方法:

      /** Successful HTTP response. */
      public void onResponse(Response<T> response, Retrofit retrofit)````
      
      /** Invoked when a network or unexpected exception occurred during the HTTP request. */
      public void onFailure(Throwable t)
      

      Retrofit2.x 会收到onResponse 中的所有HTTP 响应,即使http 代码不是2xx 或3xx,这里您需要检查您的onResponse 方法中的响应状态码,并检查响应是否成功响应(通常为 2xx 或 3xx)并进行正确的逻辑处理。

      我已经升级了 retrofit2.x,我关于集中错误处理的解决方案是: 创建一个抽象类,用两个方法 onSuccess 和 onFailed 扩展 Retrofit.Callback,onFailed 不是抽象的,因为我总是在业务逻辑失败时执行相同的过程,而在请求成功时执行不同的操作。 可以参考示例代码here

      那么,当你需要发送http请求时,你需要实现onSuccess方法,并且在某些情况下你也可以重写onFailed方法,正如我在我的项目中提到的,我在大多数情况下以相同的方式处理失败。 你可以参考我使用retrofit2发送post请求的例子here

      希望对你有帮助!

      【讨论】:

        【解决方案3】:

        我使用了与 Amir 建议的类似解决方案,但我只是想知道这是否可以变得更容易。我尝试了以下方法:

        public void onResponse(Response<T> response) {
                if (response.isSuccess()) {
                    T resp = response.body();
                    handleSuccessResponse(resp);
        
                } else {
                    Response<StatusResponse> statusResponse = response.error(response.code(), response.errorBody());
                    handleHttpErrorResponse(statusResponse);
                }
            }
        

        这样我就不需要传递 Retrofit 实例了。但是我遗漏了一些东西,因为错误正文没有成功解析为 StatusResponse。我不确定这在实践中意味着什么:

        2.0.0-beta2 中为 Callback 的 onResponse 回调提供 Retrofit 实例的更改已恢复。为了允许对错误主体进行反序列化,提供 Retrofit 对象有太多的边缘情况。为了适应这种用例,请手动传递 Retrofit 响应或实现自定义 CallAdapter.Factory 会自动执行此操作。

        2.0.0-beta3

        【讨论】:

        • 阿米尔是谁?如果发生异常(例如超时),则没有响应。如果你使用接口中的注释函数(不记得你怎么称呼它^^)来处理同步请求,你就没有像onResponse这样的回调。我正在尝试使用拦截器,但我还不能用它处理异常......
        • 也许您必须在每个请求上捕获异常,并且无法集中处理异常。
        【解决方案4】:

        对于集中处理特定于 401 的案例以及使用新的身份验证令牌重试请求,请参阅此stack overflow answer

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-06-30
          • 2014-12-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-12-27
          • 2015-03-06
          • 2016-12-20
          相关资源
          最近更新 更多