再见!RxJava!
看到这个标题你可能会惊讶,作为 RxJava 坚定的拥护者,为什么突然不再支持 RxJava 了呢?本文是来自 W_BinaryTree 博客[1],对于 RxJava 我一直感冒不起来,W_BinaryTree 的观点与我不谋而合,同时也欢迎你说说对 RxJava 看法。
先讲讲历史
在我的文章中已经讲过很多次 RxJava 诞生之初就是因为异步。再后来借鉴 LINQ 的思想借用 Monad 的力量使得 Rx 可以使用操作符进行组合将各种复杂的请求简单化。可以说,RxJava 的设计初衷就是围绕着 Asyhconization 和 Composition。当年的 Netflix 也是为了增加服务器的性能和吞吐量来编写 RxJava 并开源。才使得 RxJava 问世。
再聊聊异步
在那个 RxJava 刚刚火爆的年代,那是一个荒蛮的年代。我们在异步方面资源匮乏,手头仅有 ThreadPool,AsyncTask 和 Handler 这些基础封装的异步库。所以当我们看见 RxJava 这个新奇的小玩意,当我们看到异步还可以这么简单,轻而易举的解决 Concurrency 问题。我们当然如获至宝。而我们现在选择就更多了,无论是 Java 8 本身提供的CompletableFuture
。还是后起之秀 Kotlin 上的Coroutine
,还有 Android 上官方提供的LiveData
(这里说下:虽然本质上线程管理仍需用户自己,但是常见的比如 Room 数据库,Retrofit 等等都有现成的 LiveDataAdapter,实际上并不需要我们过多操心线程问题)。相比之下,RxJava 优势并不那么明显,相反劣势却很突出。
RxJava 门槛太高
相信多数 Android 开发者并没有了解过或者说深入了解过(我自己也没深入了解过)函数式相关的知识。但是如果不了解这些,那么而几乎可以说不可能融会贯通 RxJava 的一些概念。举个例子,一个很著名的 Googler:Yigit Boyar。也就是每次 IO 的那个大胡子,他的代表作有很多。比如 RecyclerView,再比如 Architecture Component。这样一个 Android 界名人,水平怎么也有平均以上。但是他在实现 LiveData 和 RxJava 适配的时候,同样出现了由于理解上出的问题,造成错误的实现方式。RxJava 的门槛过于高,就连我自己推广这么久,自己也不敢说对 RxJava 了解有多深刻。经常在常见操作符的使用中出现了或多或少的 unexpected behavior。再者,无论国内国外的 RxJava 教程水平都参差不齐。新手很难鉴别哪些人说的是对的哪些人说的是错误的。在这样鱼龙混杂的条件下学好这个高门槛的异步库更是变得难上加难。很多教程在自己没有精通的情况下,很容易误导其他人(包括我自己的文章)。
投入高,收获少
虽然这点存疑,因为我自己钻研 RxJava 之后确实觉得收获很大,尤其是经由 RxJava 窥探了函数式的大门。但是功利的看,RxJava 在解决异步处理这个问题上,的确是投入高,收获少。异步问题是 Android 开发必不可少的一个环节,可以说掌握异步应该是成为入门 Android 开发的敲门砖。而 RxJava 归根到底是通过响应式的方式配合 Monad 来解决异步问题。但是仅仅为了解决异步问题,学习并精通 RxJava 并不是必不可少的。相反,精通 RxJava 需要大量时间和精力,在现在异步编程逐步完善的情况下,完全没有必要。
你永远无法预测你同事的 RxJava 水平
上面几点可能有点抽象,而这点和接下来的几点都是我在实际工作中遇到的实际情况。首先就是你并不能预测或者要求你的同事 RxJava 到达什么样的水平。我之前的公司使用了一个简单的类 redux 框架。其中 RxJava 是核心部分,他承载了中间 render 层和 view 层的连接。具体关于这个架构可以看我这里的项目实例:Twivy[2]。在 Review 同事的代码之后,我才发现 RxJava 还能这么玩?各种奇思妙想的作用让我不得不佩服法国同事的丰富想象力。而这些错误使用就像一颗颗定时炸弹一样埋在代码里。随时可能爆炸。但是反过来一想,并不是所有人都像我一样喜欢研究 RxJava。他们可能仅仅是因为使用了这个架构而接触 Rx。而 RxJava 的掌握并不是一个 Android 开发的必要条件。他完全可以一点 RxJava 也不会也成为一个优秀的 Android Developer。
RxJava 的行为并不可预期
RxJava 还有一大毛病就是光看方法名你很难知道他的真正意思。在初学 RxJava 时候,两个一直纠缠不清的问题就是map
和flatMap
的区别。还有flatMap
和concatMap
的区别。简单的讲map
是一对一,flatMap
是一对 N 的 map 然后在进行flatten
操作。还有些教程直接写出flatMap
无序,concatMap
有序。其实这些都只是简单总结,而实际的行为照着相差甚远。比如flatMap
在第一个 error 的时候会不会继续继续触发第二个?如果我想继续,将如何操作?再比如concatMap
在遇到第一个Observable
不会中断的时候,怎么继续下一个?这些都几乎是要看源码或者做多次实验对比才能得出结论的问题,而实际工作中并不想去因为这个工具而去浪费太多时间,得不偿失。但是如果不做,就像前文提到的定时炸弹一样。上线直接增加错误几率。
RxJava 太容易出错
Uncle Ben 说过:
with great power comes great responsibility. RxJava 就是这样。在简单易用的同时他太容易被滥用了。我在实际工作中碰到的例子:
val stationId = "5bCP6Iqx"
val statoin:Observable = staionRepo.getStationById(stationId)
val stationLine:Observable = station.flatMap{station ->stationRepo.getLine(station)}
return Observable.merge(station.map{it.toUiModel()},stationLine.map{it.toUiModel()})
复制代码
乍一看,这几行代码并没有错。这个 Bug 还是后台反馈给我的说为什么 android 每次都会发两个一模一样的请求?其实问题就出在 stationLine 和 station 并没有共享结果。造成了每次请求都要发两次。修改后的代码:
val stationId = "5bCP6bif"
val statoin:Observable = staionRepo.getStationById(stationId)
return station.publish{selector ->
Observable.merge(selector.map{it.toUiModel()},
selector.flatMap{station -> stationRepo.getLine(station)}
.map{it.toUiModel()})
}
RxJava 还是过于理想化了
RxJava 承诺出一个完美的异步世界,一切异步操作由上游控制,下游只需要思考如何处理,并不关心数据来源。而实际过程中,这个过程还是过于理想化了。最直接的例子就是 BackPressure 的出现。在数据量足够庞大时,缓存池并不能及时缓存所有生产的数据,造成越积越多最终 OOM。也即是所谓的 BackPressure。再者,函数式中的 Monad 来包裹异步这个操作还是过于复杂了,看过 RxJava 的朋友都应该清楚。某些很简单的操作符在实现起来其实非常复杂。追踪数据十分困难,很容易掉入很难 Debug 的情况。而且虽然 RxJava 的文档是我见过少有写的非常出色的库,但是很多操作符如果不读通源码,仅仅从 Java Doc 和 Method Signature 来观察,并不清楚期待的行为是什么。就算知道,在一些特殊情况如何处理,仍是一个未知结果。同时 RxJava 虽然解放了上游控制权力的,也引入了不安全性。如果上游出现了非预想的问题,下游将很难处理。其次,RxJava 为了这个理想化的世界,引入了太多的 overhead。无论是每个操作符都要生成一个新的 Observable 实例还是蹦床模式的异步解决方案。都生成了太多的 Object 在堆中存放。这种 overhead 在轻量级应用,或者一些小型异步处理比如数据埋点等等行为中,都显得过于庞大。
RxJava 起于异步,却也不单单是异步
Rx 在被 Erik Meijer 提出的时候,确实是由同步的 Iterable 推导,由主动拉取数据改为被动接受数据。但是在加入函数是 Monad 的概念之后,RxJava 作为响应式数据流,应用在了更多 Callback base 的场景中。在 Android 这种 GUI 平台下尤为出色。多数基于 Redux 结构的 Android 架构都或多或少基于 RxJava。或者借鉴 RxJava 的思想。比如 Airbnb 推出的 MvRx[3]。还有 Google 在 18 年 io 中当作 Sample App 做出的 Sunflower,大量使用LiveData
。而LiveData
无疑也是大量借鉴了 RxJava 的思想。
总结:RxJava 虽然优秀,但并不适合所有人
即使 RxJava 有且不仅限于我说的上述几个问题,但无疑 RxJava 仍是一个划时代的优秀的异步框架。但是优秀并不代表适合所有人,我在之前推广 RxJava,认为这样的异步基础应该是每一个 Android 开发者必不可少的知识点。但实际工作使用两年之后,我觉得这并不实际,也不必要。RxJava 的水平并不能映射一个 Android Dev 的开发水平,反之,一个高水平的 Android Dev 也并不一定对 RxJava 了解多少。在这样的前提下,再加上入门门槛高,易出错,行为不好预期等等缺点下。在团队没有 RxJava Expert 的情况下我更倾向于直接弃用 RxJava,转为更容易使用的异步框架和响应式数据流。我在入职新公司的的时候,邮件里写了这样一句:
engineering is about trade off
RxJava 便是这样一个库,甲之蜜糖,乙之砒霜。用的好 RxJava,他是一个利器,根本离不开。用不好,他就是你身边的定时炸弹,随时爆炸却又很难拆解。
参考资料
博客: https://juejin.im/post/5cd04b6e51882540e53fdfa2
[2]Twivy: https://github.com/wbinarytree/Twivy
[3]MvRx: https://github.com/airbnb/MvRx
- End -