浅谈RxJava处理业务异常的几种方式

本文介绍了RxJava处理业务异常的几种方式,分享给大家。具体如下:

关于异常

Java的异常可以分为两种:运行时异常和检查性异常。

运行时异常:

RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过。

检查性异常:

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查性异常。检查性异常必须被显式地捕获或者传递。当程序中可能出现检查性异常时,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。

处理业务异常

业务异常:

指的是正常的业务处理时,由于某些业务的特殊要求而导致处理不能继续所抛出的异常。在业务层或者业务的处理方法中抛出异常,在表现层中拦截异常,以友好的方式反馈给使用者,以便其可以依据提示信息正确的完成任务功能的处理。

1. 重试

不是所有的错误都需要立马反馈给用户,比如说在弱网络环境下调用某个接口出现了超时的现象,也许再请求一次接口就能获得数据。那么重试就相当于多给对方一次机会。

在这里,我们使用retryWhen操作符,它将错误传递给另一个被观察者来决定是否要重新给订阅这个被观察者。

听上去有点拗口,直接上代码吧。

 /**
  * 获取内容
  * @param fragment
  * @param param
  * @param cacheKey
  * @return
  */
 public Maybe<ContentModel> getContent(Fragment fragment, ContentParam param, String cacheKey) {

  if (apiService == null) {
   apiService = RetrofitManager.get().apiService();
  }

  return apiService.loadContent(param)
    .retryWhen(new RetryWithDelay(3,1000))
    .compose(RxLifecycle.bind(fragment).<ContentModel>toLifecycleTransformer())
    .compose(RxUtils.<ContentModel>toCacheTransformer(cacheKey));
 }

这个例子是一个网络请求,compose的内容可以忽略。如果网络请求失败的话,会调用retryWhen操作符。RetryWithDelay实现了Function接口,RetryWithDelay是一个重试的机制,包含了重试的次数和重试时间隔的时间。

import com.safframework.log.L;

import org.reactivestreams.Publisher;

import java.util.concurrent.TimeUnit;

import io.reactivex.Flowable;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Function;

/**
 * 重试机制
 * Created by tony on 2017/11/6.
 */

public class RetryWithDelay implements Function<Flowable<? extends Throwable>, Publisher<?>> {

 private final int maxRetries;
 private final int retryDelayMillis;
 private int retryCount;

 public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
  this.maxRetries = maxRetries;
  this.retryDelayMillis = retryDelayMillis;
  this.retryCount = 0;
 }

 @Override
 public Publisher<?> apply(@NonNull Flowable<? extends Throwable> attempts) throws Exception {

  return attempts.flatMap(new Function<Throwable, Publisher<?>>() {
   @Override
   public Publisher<?> apply(Throwable throwable) throws Exception {
    if (++retryCount <= maxRetries) {

     L.i("RetryWithDelay", "get error, it will try after " + retryDelayMillis
       + " millisecond, retry count " + retryCount);
     // When this Observable calls onNext, the original
     // Observable will be retried (i.e. re-subscribed).
     return Flowable.timer(retryDelayMillis, TimeUnit.MILLISECONDS);

    } else {

     // Max retries hit. Just pass the error along.
     return Flowable.error(throwable);
    }
   }
  });
 }
}

如果运气好重试成功了,那用户在无感知的情况下可以继续使用产品。如果多次重试都失败了,那么必须在onError时做一些异常的处理,提示用户可能是网络的原因了。

2. 返回一个默认值

有时出错只需返回一个默认值,有点类似Java 8 Optional的orElse()

RetrofitManager.get()
    .adService()
    .vmw(param)
    .compose(RxLifecycle.bind(fragment).<VMWModel>toLifecycleTransformer())
    .subscribeOn(Schedulers.io())
    .onErrorReturn(new Function<Throwable, VMWModel>() {
     @Override
     public VMWModel apply(Throwable throwable) throws Exception {
      return new VMWModel();
     }
    });

上面的例子使用了onErrorReturn操作符,表示当发生错误的时候,发射一个默认值然后结束数据流。所以 Subscriber 看不到异常信息,看到的是正常的数据流结束状态。

跟它类似的还有onErrorResumeNext操作符,表示当错误发生的时候,使用另外一个数据流继续发射数据。在返回的被观察者中是看不到错误信息的。

使用了onErrorReturn之后,onError是不是就不做处理了?onErrorReturn的确是返回了一个默认值,如果onErrorReturn之后还有类似doOnNext的操作,并且doOnNext中出错的话,onError还是会起作用的。

曾经遇到过一个复杂的业务场景,需要多个网络请求合并结果。这时,我使用zip操作符,让请求并行处理,等所有的请求完了之后再进行合并操作。某些请求失败的话,我使用了重试机制,某些请求失败的话我给了默认值。

3. 使用onError处理异常

现在的Android开发中,网络框架是Retrofit的天下。在接口定义的返回类型中,我一般喜欢用Maybe、Completable来代替Observable。

我们知道RxJava在使用时,观察者会调用onNext、onError、onComplete方法,其中onError方法是事件在传递或者处理的过程中发生错误后会调用到。

下面的代码,分别封装两个基类的Observer,都重写了onError方法用于处理各种网络异常。这两个基类的Observer是在使用Retrofit时使用的。

封装一个BaseMaybeObserver

import android.accounts.NetworkErrorException
import android.content.Context

import com.safframework.log.L
import io.reactivex.observers.DisposableMaybeObserver
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

/**
 * Created by Tony Shen on 2017/8/8.
 */
abstract class BaseMaybeObserver<T> : DisposableMaybeObserver<T>() {

 internal var mAppContext: Context

 init {
  mAppContext = AppUtils.getApplicationContext()
 }

 override fun onSuccess(data: T) {
  onMaybeSuccess(data)
 }

 abstract fun onMaybeSuccess(data: T)

 override fun onError(e: Throwable) {
  var message = e.message
  L.e(message)

  when(e) {

   is ConnectException -> message = mAppContext.getString(R.string.connect_exception_error)
   is SocketTimeoutException -> message = mAppContext.getString(R.string.timeout_error)
   is UnknownHostException -> message = mAppContext.getString(R.string.network_error)
   is NetworkErrorException -> message = mAppContext.getString(R.string.network_error)
   else -> message = mAppContext.getString(R.string.something_went_wrong)
  }

  RxBus.get().post(FailedEvent(message))
 }

 override fun onComplete() {}
}

封装一个BaseCompletableObserver

import android.accounts.NetworkErrorException
import android.content.Context

import com.safframework.log.L
import io.reactivex.observers.ResourceCompletableObserver
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

/**
 * Created by Tony Shen on 2017/8/8.
 */
abstract class BaseCompletableObserver : ResourceCompletableObserver() {

 internal var mAppContext: Context

 init {
  mAppContext = AppUtils.getApplicationContext()
 }

 override fun onComplete() {
  onSuccess()
 }

 abstract fun onSuccess()

 override fun onError(e: Throwable) {
  var message = e.message
  L.e(message)

  when(e) {

   is ConnectException -> message = mAppContext.getString(R.string.connect_exception_error)
   is SocketTimeoutException -> message = mAppContext.getString(R.string.timeout_error)
   is UnknownHostException -> message = mAppContext.getString(R.string.network_error)
   is NetworkErrorException -> message = mAppContext.getString(R.string.network_error)
   else -> message = mAppContext.getString(R.string.something_went_wrong)
  }

  RxBus.get().post(FailedEvent(message))
 }
}

在这里用到了Kotlin来写这两个基类,使用Kotlin的目的是因为代码更加简洁,避免使用switch或者各种if(XX instancof xxException)来判断异常类型,可以跟Java代码无缝结合。

下面的代码展示了如何使用BaseMaybeObserver,即使遇到异常BaseMaybeObserver的onError也会做相应地处理。如果有特殊的需求,也可以重写onError方法。

    model.getContent(VideoFragment.this,param, cacheKey)
      .compose(RxJavaUtils.<ContentModel>maybeToMain())
      .doFinally(new Action() {
       @Override
       public void run() throws Exception {
        refreshlayout.finishRefresh();
       }
      })
      .subscribe(new BaseMaybeObserver<ContentModel>(){

     @Override
     public void onMaybeSuccess(ContentModel data) {
      adpter.addDataToFront(data);
     }
    });

4. 内部异常使用责任链模式来分发

这是微信中一位网友提供的方法,他做了一个很有意思的用于异常分发的一个库,github地址:https://github.com/vihuela/Retrofitplus

内部异常使用责任链分发,分发逻辑为:

  1. 自定义异常->网络异常->服务器异常->内部程序异常->未知异常
  2. 除了以上自定义异常之外,此库包含其它异常分发,默认适应场景为:Rx+Json
  3. 自定义异常使用请调用,ExceptionParseMgr类的addCustomerParser方法添加业务异常

这个库对原先的代码无侵入性。此外,他还提供了另一种思路,结合compose来处理一些特定的业务异常。

总结

本文仅仅是总结了个人使用RxJava遇到业务异常的情况,并对此做了一些相应地处理,肯定是不能覆盖开发的方方面面,仅作为抛砖引玉,如果有更好、更优雅的处理方式,一定请告知。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java的RxJava库操作符的用法及实例讲解

    操作符就是为了解决对Observable对象的变换的问题,操作符用于在Observable和最终的Subscriber之间修改Observable发出的事件.RxJava提供了很多很有用的操作符. 比如map操作符,就是用来把把一个事件转换为另一个事件的. Observable.just("Hello, world!") .map(new Func1<String, String>() { @Override public String call(String s) { r

  • RxJava入门指南及其在Android开发中的使用示例

    RxJava的GitHub主页,部署部分就没什么好说的了~ https://github.com/ReactiveX/RxJava 基础 RxJava最核心的两个东西是Observables(被观察者,事件源)和Subscribers(观察者).Observables发出一系列事件,Subscribers处理这些事件.这里的事件可以是任何你感兴趣的东西(触摸事件,web接口调用返回的数据...) 一个Observable可以发出零个或者多个事件,知道结束或者出错.每发出一个事件,就会调用它的Su

  • 基于RxJava实现酷炫启动页

    前言 RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的.基于事件的程序的库).这就是 RxJava ,概括得非常精准. 之前注意到coding APP启动页很是酷炫,今天我们使用RxJava和属性动画模仿实现其效果.

  • RxJava 1升级到RxJava 2过程中踩过的一些“坑”

    RxJava2介绍 RxJava2 发布已经有一段时间了,是对 RxJava 的一次重大的升级,由于我的一个库cv4j使用了 RxJava2 来尝鲜,但是 RxJava2 跟 RxJava1 是不能同时存在于一个项目中的,逼不得已我得把自己所有框架中使用 RxJava 的地方以及 App 中使用 RxJava 的地方都升级到最新版本.所以我整理并记录了一些已经填好的坑.分享出来供大家参考学习,下面来看看详细的介绍: 填坑记录 1. RxJava1 跟 RxJava2 不能共存 如果,在同一个mo

  • Retrofit Rxjava实现图片下载、保存并展示实例

    首先我们看一下Retrofit常规的用法,在不使用Rxjava的情况下,我们默认返回的是Call. public interface ServiceApi { //下载文件 @GET Call<ResponseBody> downloadPicFromNet(@Url String fileUrl); } 但是如果我们要配合Rxjava使用,那么就要按照如下方式来重新定义我们的方法: @GET Observable<ResponseBody> downloadPicFromNet(

  • 简单谈谈RxJava和多线程并发

    前言 相信对于RxJava,大家应该都很熟悉,他最核心的两个字就是异步,诚然,它对异步的处理非常的出色,但是异步绝对不等于并发,更不等于线程安全,如果把这几个概念搞混了,错误的使用RxJava,是会来带非常多的问题的. RxJava与并发 首先让我们来看一段RxJava协议的原文: Observables must issue notifications to observers serially (not in parallel). They may issue these notificat

  • RxJava入门之介绍与基本运用

    前言 因为这个RxJava内容不算少,而且应用场景非常广,所以这个关于RxJava的文章我们会陆续更新,今天就来先来个入门RxJava吧 初识RxJava 什么是Rx 很多教程在讲解RxJava的时候,上来就介绍了什么是RxJava.这里我先说一下什么是Rx,Rx就是ReactiveX,官方定义是: Rx是一个函数库,让开发者可以利用可观察序列和LINQ风格查询操作符来编写异步和基于事件的程序 看到这个定义我只能呵呵,稍微通俗点说是这样的: Rx是微软.NET的一个响应式扩展.Rx借助可观测的序

  • 浅析RxJava处理复杂表单验证问题的方法

    无论是简单的登录页面,还是复杂的订单提交页面,表单的前端验证(比如登录名和密码都符合基本要求才能点亮登录按钮)都是必不可少的步骤.本文展示了如何用RxJava来方便的处理表单提交前的验证问题,例子采用了Android上的一个简单的登录页面 内容提要 传统的验证方式 combineLatest操作符 用combineLatest处理表单验证 combineLatest和zip的区别 本文中所演示的例子sample代码位于RxAndroidDemo,参见loginActivity这个文件 传统的验证

  • RxJava2.x实现定时器的实例代码

    前言 由于现在网络层已经升级到RxJava2.x相关的了,所以需要做些调整.虽然RxJava1.x和RxJava2.x同属RxJava系列,但由于RxJava2.x部分代码的重写,导致RxJava2.x与RxJava1.x已是两个不同的版本,RxJava2.x在性能上更优,尤其在背压支持上.当然,此篇重点不在Rx版本上的区别,有兴趣的同学可以自行研究.当然,2.x之于1.x的区别之一是2.x中已经没有 Subscription mSubscription, Observable.create()

  • 浅谈RxJava处理业务异常的几种方式

    本文介绍了RxJava处理业务异常的几种方式,分享给大家.具体如下: 关于异常 Java的异常可以分为两种:运行时异常和检查性异常. 运行时异常: RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获它,也没有用throws字句声明抛出它,还是会编译通过. 检查性异常: 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查性异

  • 浅谈MySQL8.0 异步复制的三种方式

    本实验中分别针对空库.脱机.联机三种方式,配置一主两从的mysql标准异步复制.只做整服务器级别的复制,不考虑对个别库表或使用过滤复制的情况. 实验环境 [root@slave2 ~]# cat /etc/hosts 192.168.2.138 master 192.168.2.192 slave1 192.168.2.130 slave2 mysql> select version(); +-----------+ | version() | +-----------+ | 8.0.16 |

  • 浅谈Springboot实现拦截器的两种方式

    目录 一.拦截器方式 1.配置HandlerInterceptor 2.注册拦截器 3.使用拦截器的坑 二.过滤器方式 1.实现Filter接口 2.使用过滤器需要注意的 实现过滤请求有两种方式: 一种就是用拦截器,一种就是过滤器 拦截器相对来说比较专业,而过滤器虽然不专业但是也能完成基本的拦截请求要求. 一.拦截器方式 1.配置HandlerInterceptor 下面这个也是我们公司项目拦截器的写法,总体来说感觉还不错,我就记录了下来. 利用了一个静态Pattern变量存储不走拦截器的路径,

  • 浅谈react路由传参的几种方式

    第一种传参方式,动态路由传参 通过设置link的path属性,进行路由的传参,当点击link标签的时候,会在上方的url地址中显示传递的整个url <Link to='/home?name=dx'>首页</Link> 如果想真正获取到传递过来的参数,需要在对应的子组件中 this.props.location.search 获取字符串,再手动解析 因为传参能够被用户看见,传递获取比较麻烦,所以不推荐 第二种传参方式,隐式路由传参 <Link to={{ pathname: '

  • 浅谈前端JS沙箱实现的几种方式

    目录 前言 iframe实现沙箱 diff方式实现沙箱 基于代理(Proxy)实现单实例沙箱 基于代理(Proxy)实现多实例沙箱 结束语 参考 前言 在微前端领域当中,沙箱是很重要的一件事情.像微前端框架single-spa没有实现js沙箱,我们在构建大型微前端应用的时候,很容易造成一些变量的冲突,对应用的可靠性面临巨大的风险.在微前端当中,有一些全局对象在所有的应用中需要共享,如document,location,等对象.子应用开发的过程中可能是多个团队在做,很难约束他们使用全局变量.有些页

  • 浅谈Spring解决循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种:构造器参数循环依赖 表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyIn CreationException异常表示循环依赖. 如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建Test

  • 浅谈React Native 传参的几种方式(小结)

    在React Native 中由于业务的需要, 我们往往要在诸多的页面间,组件之间做一些参数的传递与管理, 在这里我总结了几大经过验证,稳定好用的方式给大家 React Navigation 导航传值 推荐指数: ♥ ♥ ♥ ♥ ♥ 适用范围: 相互跳转的页面间传值 兼容性: IOS/Android 原理: React Navigation 为页面的 props 上挂载了 navigation 对象, 可用来做路由跳转,在做页面跳转时可以携带参数/回调方法前往目标页面, 从而达到传参的目的 说明

  • 浅谈PHP发送HTTP请求的几种方式

    PHP 开发中我们常用 cURL 方式封装 HTTP 请求,什么是 cURL? cURL 是一个用来传输数据的工具,支持多种协议,如在 Linux 下用 curl 命令行可以发送各种 HTTP 请求.PHP 的 cURL 是一个底层的库,它能根据不同协议跟各种服务器通讯,HTTP 协议是其中一种. 现代化的 PHP 开发框架中经常会用到一个包,叫做 GuzzleHttp,它是一个 HTTP 客户端,也可以用来发送各种 HTTP 请求,那么它的实现原理是什么,与 cURL 有何不同呢? Does

  • 浅谈Java中实现深拷贝的两种方式—clone() & Serialized

    clone() 方法麻烦一些,需要将所有涉及到的类实现声明式接口 Cloneable,并覆盖Object类中的clone()方法,并设置作用域为public(这是为了其他类可以使用到该clone方法). 序列化的方法简单,需要将所有涉及到的类实现接口Serializable package b1ch06.clone; import java.io.Serializable; class Car implements Cloneable, Serializable { private String

  • 浅谈java异常处理(父子异常的处理)

    我当初学java异常处理的时候,对于父子异常的处理,我记得几句话"子类方法只能抛出父类方法所抛出的异常或者是其子异常,子类构造器必须要抛出父类构造器的异常或者其父异常".那个时候还不知道子类方法为什么要这样子抛出异常,后来通过学习<Thinking in Java>,我才明白其中的道理,现在我再来温习一下. 一.子类方法只能抛出父类方法的异常或者是其子异常 对于这种限制,主要是因为子类在做向上转型的时候,不能正确地捕获异常 package thinkinginjava; p

随机推荐