使用RxJava中遇到的一些”坑“

前言

大家越用RxJava,越觉得它好用,所以不知不觉地发现代码里到处都是RxJava的身影。然而,RxJava也不是银弹,其中仍然有很多问题需要解决。这里,我简单地总结一下自己遇到的一些“坑”,内容上可能会比较松散。

一、考虑主线程的切换

RxJava中一个常用的使用方法是——在其他线程中做处理,然后切换到UI线程中去更新页面。其中,线程切换就是使用了observeOn()。后台下载文件,前台显示下载进度就可以使用这种方式完成。然而,实践发现这其中有坑。如果文件比较大,而下载包的粒度又比较小,这将导致很多通知积压下来,最终导致错误。

这种错误其实也是可以理解的,毕竟MainLooper是根据Message来工作的,Message过多必然会导致一些问题。当然,这还是比较想当然的想法,最终还是需要到源码中一探究竟。ObserveOn的原理在前面关于RxJava的文章已经有过分析,这里还是简单列一下代码。其中的重点还是OperatorObserveOn的内部类——ObserveOnSubscriber。其重要代码片段如下:

 /** Observe through individual queue per observer. */
 static final class ObserveOnSubscriber<T> extends Subscriber<T> implements Action0 {
  final Subscriber<? super T> child;
  final Scheduler.Worker recursiveScheduler;
  final NotificationLite<T> on;
  final boolean delayError;
  final Queue<Object> queue;
  /** The emission threshold that should trigger a replenishing request. */
  final int limit;

  // the status of the current stream
  volatile boolean finished;

  final AtomicLong requested = new AtomicLong();

  final AtomicLong counter = new AtomicLong();

  /**
   * The single exception if not null, should be written before setting finished (release) and read after
   * reading finished (acquire).
   */
  Throwable error;

  /** Remembers how many elements have been emitted before the requests run out. */
  long emitted;

  // do NOT pass the Subscriber through to couple the subscription chain ... unsubscribing on the parent should
  // not prevent anything downstream from consuming, which will happen if the Subscription is chained
  public ObserveOnSubscriber(Scheduler scheduler, Subscriber<? super T> child, boolean delayError, int bufferSize) {
   this.child = child;
   this.recursiveScheduler = scheduler.createWorker();
   this.delayError = delayError;
   this.on = NotificationLite.instance();
   int calculatedSize = (bufferSize > 0) ? bufferSize : RxRingBuffer.SIZE;
   // this formula calculates the 75% of the bufferSize, rounded up to the next integer
   this.limit = calculatedSize - (calculatedSize >> 2);
   if (UnsafeAccess.isUnsafeAvailable()) {
    queue = new SpscArrayQueue<Object>(calculatedSize);
   } else {
    queue = new SpscAtomicArrayQueue<Object>(calculatedSize);
   }
   // signal that this is an async operator capable of receiving this many
   request(calculatedSize);
  }

  void init() {
   // don't want this code in the constructor because `this` can escape through the
   // setProducer call
   Subscriber<? super T> localChild = child;

   localChild.setProducer(new Producer() {

    @Override
    public void request(long n) {
     if (n > 0L) {
      BackpressureUtils.getAndAddRequest(requested, n);
      schedule();
     }
    }

   });
   localChild.add(recursiveScheduler);
   localChild.add(this);
  }

  @Override
  public void onNext(final T t) {
   if (isUnsubscribed() || finished) {
    return;
   }
   if (!queue.offer(on.next(t))) {
    onError(new MissingBackpressureException());
    return;
   }
   schedule();
  }

  @Override
  public void onCompleted() {
   if (isUnsubscribed() || finished) {
    return;
   }
   finished = true;
   schedule();
  }

  @Override
  public void onError(final Throwable e) {
   if (isUnsubscribed() || finished) {
    RxJavaHooks.onError(e);
    return;
   }
   error = e;
   finished = true;
   schedule();
  }

  protected void schedule() {
   if (counter.getAndIncrement() == 0) {
    recursiveScheduler.schedule(this);
   }
  }
 }

关键点就在于这个queue成员,这个队列存放了需要进行发送给下行线程的消息。对于主线程来说,符合其实是比较重的,从消息的生产者和消费者的模式讲,过多过快的消息会导致消息阻塞。甚至,都到不了阻塞的情况,因为queue的大小会有上限,在onNext()方法中的queue.offer()可能会产生异常,这取决于queue的实现方式。但无论如何都不可能无限大,所以无法保证绝对不出异常。

解决这个问题的方法其实也很简单,可以在生产者降低消息的产生频率。也可以在消息处理的时候先不进行线程切换,而是通过判断,在必要的时候进行线程切换,比如使用runOnUIThread()

二、RxJava避免内存泄漏

RxJava的响应式机制本质上还是回调实现的,因此内存泄漏也是会出现的。倘若不对Subscription进行管理,内存泄漏会非常严重。对于Subscription,其实有几个比较广泛使用的方法,比如RxLifecycle,以及简单的CompositeSubscription。至于它们的使用方法,其实都非常简单,这里就不赘述了。

说到内存泄漏,就谈点题外话,动画也可能导致内存泄漏。其原因仍然是一些回调函数,这些回调函数实现的View变化的功能,但是在被撤销以后,回调函数没有取消掉,同时View可能持有Context信息,从而导致内存泄漏。最近才发现,LoadToastView这个开源库一直存在内存泄漏,其原因正如上文所说。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • android使用RxJava实现预加载

    在上一篇文章中介绍了使用非RxJava环境下,使用Handler机制SyncBarrier的特性实现预加载功能的方法. 在RxJava的环境下使用BehaviorSubject的特性来实现也是很方便的. BehaviorSubject内部会缓存消息流中最近的一个消息, 在后续有Subscriber订阅时,会直接将缓存的消息发送给Subscriber. RxPreLoader.java封装如下: import android.support.annotation.NonNull; import j

  • android非RxJava环境下使用Handler实现预加载

    在进行Android客户端界面开发时,我们常常会需要将从服务端获取的数据展示到页面布局上,由于数据显示到布局的前置条件是页面布局已初始化完成,否则会出现空指针异常,所以一般我们需要将网络请求放在布局初始化完成之后. 传统的页面加载流程是: 问题: 如果加载的UI布局比较复杂,或者初始化逻辑执行的时间比较多,那么网络请求开始执行的时间就比较晚,最终完成页面加载的时间就比较长. 如果页面初始化和网络加载能同时进行,等两者都执行结束后,再在布局上展示网络数据,这样我们就可以缩短整个页面的加载时间了.

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

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

  • 使用RxJava中遇到的一些”坑“

    前言 大家越用RxJava,越觉得它好用,所以不知不觉地发现代码里到处都是RxJava的身影.然而,RxJava也不是银弹,其中仍然有很多问题需要解决.这里,我简单地总结一下自己遇到的一些"坑",内容上可能会比较松散. 一.考虑主线程的切换 RxJava中一个常用的使用方法是--在其他线程中做处理,然后切换到UI线程中去更新页面.其中,线程切换就是使用了observeOn().后台下载文件,前台显示下载进度就可以使用这种方式完成.然而,实践发现这其中有坑.如果文件比较大,而下载包的粒度

  • 盘点javascript 正则表达式中 中括号的【坑】

    在javascript中使用正则时需要注意中括号里边的一个坑,那就是中括号内的元字符问题.自己踩到坑了,网上搜了一下还有不少人踩了这个坑,所以大概说一下. 中括号在正则中称为字符组(Character class),有的书翻译为字符类,还有的翻译成字符集.我觉得字符组更好点,毕竟class在计算机属于中代表面向对象里的"类".顾名思义,字符组为一组字符,它表示在一个位置里可能出现的多种字符.注意这里强调只匹配一个位置哦.(此段摘自-http://www.cnblogs.com/snan

  • 浅谈socket TCP编程中connect的一些坑

    1.服务端listen成功后,系统就自动接收客户端请求了 man listen: 其中有一段 The  behavior of the backlog argument on TCP sockets changed with Linux        2.2.  Now it specifies the  queue  length  for  completely  established        sockets  waiting  to  be  accepted, instead o

  • Python连接Hadoop数据中遇到的各种坑(汇总)

    最近准备使用Python+Hadoop+Pandas进行一些深度的分析与机器学习相关工作.(当然随着学习过程的进展,现在准备使用Python+Spark+Hadoop这样一套体系来搭建后续的工作环境),当然这是后话. 但是这项工作首要条件就是将Python与Hadoop进行打通,本来认为很容易的一项工作,没有想到竟然遇到各种坑,花费了整整半天时间.后来也在网上看到大家在咨询相同的问题,但是真正解决这个问题的帖子又几乎没有,所以现在将Python连接Hadoop数据库过程中遇到的各种坑进行一个汇总

  • 关于python scrapy中添加cookie踩坑记录

    问题发现: 前段时间项目中,为了防止被封号(提供的可用账号太少),对于能不登录就可以抓取的内容采用不带cookie的策略,只有必要的内容才带上cookie去访问. 本来想着很简单:在每个抛出来的Request的meta中带上一个标志位,通过在CookieMiddleware中查看这个标志位,决定是否是给这个Request是否装上Cookie. 实现的代码大致如下: class CookieMiddleware(object): """ 每次请求都随机从账号池中选择一个账号去访

  • 关于python中remove的一些坑小结

    前几天,使用python时遇到这么一个需求,删除一个列表中值为1的元素.我寻思着使用remove方法,但是remove方法只会删除第一个,于是我使用for循环去删除.代码和运行结果如下: 当时这个结果让我很懵逼,为什么1没有被删除完?查了资料发现,是for循环捣的鬼.因为for循环实际是循环的列表下标(索引),同时由于列表的可变性,每一次删除一个元素,列表的长度就会发生变化,元素的索引也会发生变化.这里来具体分析一下这段代码: 第一次循环,循环索引为0,此时索引为0的元素是1,满足条件,因此my

  • springboot中Excel文件下载踩坑大全

    目录 项目场景:Spring boot文件下载 问题一:下载的文件名称出现中文乱码的问题 问题二:在swagger中测试下载接口,点击下载的文件,发现文件名是乱码的问题 问题四:开发环境下载成功,打成jar包发布到服务器上部署就出现下载失败问题 完整代码 项目场景:Spring boot文件下载 调用接口下载spring boot工程的resources目录下的excel模板文件,非常常见的一个文件下载功能,但是却容易遇到很多坑,下面总结记录下. 问题一:下载的文件名称出现中文乱码的问题 解决方

  • 详解C++ 运算符重载中返回值的坑

    相信不少朋友在学习运算符重载的时候,都会被参数与返回值应该是左值引用,还是右值引用,还是const常量所困扰.当然我无法一一枚举,这次先讲一下返回值的坑 (没错就是我亲手写的bug) E0334 "Myclass" 没有适当的复制构造函数 其实这个问题的根源是,没有定义常量参数类型的拷贝构造函数所致 先来看看代码 //头文件head.h class Myclass { private: int a; public: Myclass(int b=0):a(b) {} //构造函数 Myc

  • java中jdbcTemplate的queryForList(坑)

    jdbcTemplate 中的queryForList,你真的懂吗? 你想象中的queryForList是不是应该长成下面这种模样? String sql = "select * from person"; List<Person> persons = jdbcTemplate.queryForList(sql, Person.class); 然后,你很激动的点了run 程序跑出来一个,你做梦都想不到的东西,不是list吗?为什么expected是1? 我总共7条数据啊,为

  • Java中Objects.equals踩坑记录

    目录 前言 1. 案发现场 2. 判断相等的方法 2.1 使用==号 2.2 使用equals方法 3. 空指针异常 4. Objects.equals的作用 5. Objects.equals的坑 总结 前言 最近review别人代码的时候,发现有个同事,在某个业务场景下,使用Objects.equals方法判断两个值相等时,返回了跟预期不一致的结果,引起了我的兴趣. 原本以为判断结果会返回true的,但实际上返回了false. 记得很早之前,我使用Objects.equals方法也踩过类似的

随机推荐