Rx 错误拦截和分发

前言

这感觉已经不对 我最后才了解 一页页不忍翻阅 的情节你好累

这次要做的事是按照业务重构网络层的错误拦截和分发,仅以这段歌词献给两位前同事。

整理下逻辑

本次的两个知识点(敲黑板

onErrorResumeNext 操作符的运用

这个操作符是干嘛的呢?当错误发生时,使用另一个数据流(Observable)继续发射数据,在返回的 Observable 中是看不到错误信息的。利用这个操作符,我们可以实现把一个异常信息包装起来再次抛出

CallAdapter 的魔改

那在哪里拦截异常,然后重新包装再抛出(分发)呢?

这里先分享下我的好 gay 友 YoKey方案。youyou 的方案非常简洁,使用一个静态方法,方法里内部根据 server 端返回的 status 来分发错误。调用的时候,就像设置哨卡一样,在每个 API 请求的后面带上 RxResultHelper.handleResult() 即可(ps:就是遇到请求比较多的时候,需要设置好几个哨卡)。

我的方案

其实大体上和 youyou 是如出一辙的,就是设置哨卡的地方有点儿不太一样,这里我们再回忆下 API 的形式吧:

/**
    * 获取验证码
    */
   @POST("/sms") Observable<ServerResult<Params>> fetchSmsCode(@Body Params params);

方法调用的返回结果是一个 Observable<ServerResult,熟悉 Retrofit 的童鞋肯定明白,Retrofit 只能返回一个 ReponseBody,为什么 ReponseBody ->Observable<ServerResult> 了呢?

聪明的童鞋肯定知道,这里辛勤的老外盆友替我们做了转换的工作,使我们得到了一个 Observable,这才使流式调用有可能。而转换的地方,那应该是一个适配器没错了,看看我们的代码:

mRetrofit = new Retrofit.Builder().baseUrl(retrofitWrapperFactory.provideBaseUrl())
        .client(retrofitWrapperFactory.createOkHttpClient())
        .addConverterFactory(retrofitWrapperFactory.createGsonConverterFactory())
        .addCallAdapterFactory(retrofitWrapperFactory.createRxJavaCallAdapter()) // 设置 CallAdapterFactory
        .build();

再仔细想想,这个 CallAdapterFactory 肯定是做了 ResponseBody -> Observable 的转换没错了,如果我们能 custom 这个 Factory,我们就能拿到 Observable,如果在这里给它设置一个错误分发器的话,岂不是美滋滋?

小课堂

自定义 CallAdapter,可能是因为我用的是最新版的 Retrofit 吧,CallAdapter 的源码已经有些改变了

/**
 * Adapts a {@link Call} with response type {@code R} into the type of {@code T}. Instances are
 * created by {@linkplain Factory a factory} which is
 * {@linkplain Retrofit.Builder#addCallAdapterFactory(Factory) installed} into the {@link Retrofit}
 * instance.
 */
public interface CallAdapter<R, T> {
  /**
   * Returns the value type that this adapter uses when converting the HTTP response body to a Java
   * object. For example, the response type for {@code Call<Repo>} is {@code Repo}. This type
   * is used to prepare the {@code call} passed to {@code #adapt}.
   * <p>
   * Note: This is typically not the same type as the {@code returnType} provided to this call
   * adapter's factory.
   */
  Type responseType();

  /**
   * Returns an instance of {@code T} which delegates to {@code call}.
   * <p>
   * For example, given an instance for a hypothetical utility, {@code Async}, this instance would
   * return a new {@code Async<R>} which invoked {@code call} when run.
   * <pre><code>
   * &#64;Override
   * public &lt;R&gt; Async&lt;R&gt; adapt(final Call&lt;R&gt; call) {
   *   return Async.create(new Callable&lt;Response&lt;R&gt;&gt;() {
   *     &#64;Override
   *     public Response&lt;R&gt; call() throws Exception {
   *       return call.execute();
   *     }
   *   });
   * }
   * </code></pre>
   */
  T adapt(Call<R> call);

  /**
   * Creates {@link CallAdapter} instances based on the return type of {@linkplain
   * Retrofit#create(Class) the service interface} methods.
   */
  abstract class Factory {
    /**
     * Returns a call adapter for interface methods that return {@code returnType}, or null if it
     * cannot be handled by this factory.
     */
    public abstract CallAdapter<?, ?> get(Type returnType, Annotation[] annotations,
        Retrofit retrofit);

    /**
     * Extract the upper bound of the generic parameter at {@code index} from {@code type}. For
     * example, index 1 of {@code Map<String, ? extends Runnable>} returns {@code Runnable}.
     */
    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    /**
     * Extract the raw class type from {@code type}. For example, the type representing
     * {@code List<? extends Runnable>} returns {@code List.class}.
     */
    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
}

但是,别怕,泛型 R 代表是 ResponseBody,泛型 T 代表最终的转换类型,所以,我们的 CallAdapter 这么写:

private static class RxCallAdapterWrapper<R> implements CallAdapter<R, Observable<?>> {

    private final Retrofit mRetrofit;

    private final CallAdapter<R, ?> mWrappedCallAdapter;
  
  	// ...省略 N 行

CallAdapterFactory 需要做点儿什么

其实需要做的工作很少:

  1. 因为 ResponseBody -> Observable 的工作 RxJavaCallAdapterFactory 已经做了,我们也没必要重新造轮子,包装它!
  2. 我们需要外部传入一个错误分发器。
public final class RxErrorHandlerCallAdapterFactory extends CallAdapter.Factory {

  private final RxJavaCallAdapterFactory mRxJavaCallAdapterFactory;

  private final RxErrorDispatcher mRxErrorDispatcher;

  private RxErrorHandlerCallAdapterFactory(RxErrorDispatcher errorDispatcher) {

    mRxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create();
    mRxErrorDispatcher = errorDispatcher;
  }
  // 省略 N 行

OK,到这里,只需要把最后干活的 CallAdapter 写完,就已经完成大部分工作了,不废话,直接上代码:

private static class RxCallAdapterWrapper<R> implements CallAdapter<R, Observable<?>> {

    private final Retrofit mRetrofit;

    private final CallAdapter<R, ?> mWrappedCallAdapter;

    private final RxErrorDispatcher mRxErrorDispatcher;

    RxCallAdapterWrapper(Retrofit retrofit, CallAdapter<R, ?> wrappedCallAdapter,
        RxErrorDispatcher rxErrorDispatcher) {

      mRetrofit = retrofit;
      mWrappedCallAdapter = wrappedCallAdapter;
      mRxErrorDispatcher = rxErrorDispatcher;
    }

    @Override public Type responseType() {

      return mWrappedCallAdapter.responseType();
    }

  	// 真正的转换在这里
    @SuppressWarnings("unchecked") @Override public Observable<?> adapt(Call<R> call) {

      return ((Observable) mWrappedCallAdapter.adapt(call)).onErrorResumeNext(

          new Func1<Throwable, Observable>() {

            @Override public Observable call(Throwable throwable) {

              return Observable.error(mRxErrorDispatcher.dispatchError(throwable, mRetrofit));
            }
          });
    }
  }

我们关注最后一个方法,这里在 ((Observable) mWrappedCallAdapter.adapt(call)) 后,我们已经得到了 Observable,接上我们的 onErrorResumeNext 操作符,至于具体错误如何分发,就交给 mRxErrorDispatcher 吧!

RxErrorDispatcher

只是一个接口而已,具体的分发还是得根据需求来分发:

public interface RxErrorDispatcher {

  Throwable dispatchError(Throwable throwable, Retrofit retrofit);
}

参数里为毛还有一个 Retrofit 呢?额,这里,如果你们的后端和 youyou 一样的话,应该是不需要的,而我这里有些历史遗留问题,所以…额

如果你仔细看过开头的流程图的话,会发现,如果错误产生,response.body() 返回的是 null,错误信息需要从 response.errorBody() 里取得,为什么会这样子呢,熟练地丢锅给后端…

private <T> T getErrorBodyAs(Response response, Class<T> type, Retrofit retrofit)
      throws IOException {

    if (response == null || response.errorBody() == null) {

      return null;
    }

    Converter<ResponseBody, T> converter = retrofit.responseBodyConverter(type, new Annotation[0]);

    return converter.convert(response.errorBody());
  }

就这样,我多了一段这样的代码来解析服务器端返回的错误信息,我能怎么办,我也很绝望啊!

写在最后

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android开发指南

6.后台任务封装

29470
来自专栏一个会写诗的程序员的博客

第10章 使用 Kotlin 创建 DSL第10章 使用 Kotlin 创建 DSL

使用DSL的编程风格,可以让程序更加简单干净、直观简洁。当然,我们也可以创建自己的 DSL。相对于传统的API, DSL 更加富有表现力、更符合人类语言习惯。

8920
来自专栏Android先生

RxJava2 实战知识梳理(4) - 结合 Retrofit 请求新闻资讯

如何通过结合Retrofit框架来进行网络请求,也是RxJava的学习过程中必须要掌握的一环。网上已经有很多开源项目和文章介绍了,今天这篇文章,我...

11420
来自专栏向治洪

Android的DataBinding原理介绍

Activity在inflate layout时,通过DataBindingUtil来生成绑定,从代码看,是遍历contentView得到View数组对象,然后...

49180
来自专栏非著名程序员

Retrofit OKHttp 教你怎么持久化管理Cookie

? 投稿作者:黄海杰 原文链接: http://blog.csdn.net/lyhhj/article/details/51345386 绪论 最近小编有点...

537100
来自专栏battcn

Spring解密 - 默认标签的解析

紧跟上篇 Spring解密 - XML解析 与 Bean注册 ,我们接着往下分析源码

11410
来自专栏chenssy

【死磕 Spring】—– IOC 之 bean 的初始化

一个 bean 经历了 createBeanInstance() 被创建出来,然后又经过一番属性注入,依赖处理,历经千辛万苦,千锤百炼,终于有点儿 bean 实...

13320
来自专栏程序猿DD

Spring框架中的设计模式(五)

通过以前的4篇文章,我们看到Spring采用了大量的关于创建和结构方面的设计模式。本文将描述属于行为方面的两种设计模式:命令和访问者。 前传: Spring框架...

50070
来自专栏流媒体

MediaCodec进行AAC编解码(文件格式转换)

AAC,全称Advanced Audio Coding,是一种专为声音数据设计的文件压缩格式。与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的“性...

26750
来自专栏技术小黑屋

如何在Android中避免创建不必要的对象

在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用。这其中避免创建不必要的对象是一项重要的方面。

7720

扫码关注云+社区

领取腾讯云代金券