为什么不使用 RxLifecycle?

本文为翻译文章,原文连接:为什么不使用RxLifecycle

Why Not RxLifecycle?


Hello. This is Dan Lew. You may or may not know me as the author of RxLifecycle.

hello,我是Dan Lew,你可能还不知道我就是RxLifecycle.的作者。

Origins

When Trello first started using RxJava, we were dismayed with how easy it was to leak memory when using it. Seemingly any Subscription you setup would leak unless you explicitly cleared it. As such, we were constantly juggling Subscriptions and unsubscribing when we were done using them.

当Trello开始使用RxJava的时候,我们被很容易造成内存泄漏的现象吓到了。好像你使用的每一个Subscription都会造成内存泄漏,除非你在代码中明确的去清除它。因此,我们就在想有么有一种简单方法来解决这种问题。

Manually handling subscriptions turned out to be rather tedious, so we wanted something that took the thought out of it. For the most part, we simply wanted all our Subscriptionsto end when the Fragment or Activitylifecycle ended. Thus was born RxLifecycle.

手动的处理subscriptions是相当无聊的,所以我们想要一种东西来帮我们处理这些问题。当时我们只是想在Fragment或者Activity结束的时候自动结束Subscriptions。所以RxLifecycle诞生了。

With RxLifecycle, you just slap a compose() call onto any stream and it automatically completes the stream when certain lifecycle events happen. It was back to the old days of not having to worry about memory leaks!

使用RxLifecycle, 你只需要在流的某处使用一个compose()操作符。当某个lifecycle事件发生时,那么就会自动的结束该流。那么我们就又可以回到以前不用担心内存泄漏的美好时光了。

Problems

There have been some lingering problems with RxLifecycle that over time have gnawed at my mind more and more. Roughly in order of importance, here they are:

RxLifecycle存在的一些遗留问题,越来越来使我感到烦恼,根据重要性排序如下:

Automatic lifecycle detection leads to confusing and sometimes non-deterministic code.

The code is trying to detect where in the lifecycle you are and when to unsubscribe. If you’re subscribing in, say,onStart() then it’s not really a big deal. But if you’re inside some non-Activity component, then you have to give it access to the Activity lifecycle and then hope it is subscribing at the right time in the lifecycle, which is not guaranteed to be the case. Worse still, it is often obscure when subscription go awry.

代码尝试去检查你当前处于什么生命周期,以及什么时候取消订阅。也许你会说在OnStart()生命周期里面去处理Observable发送的items。但是如果是在一个没有生命周期的组件里面,那么就需要该该组件自己去获取Activity的生命周期,并且希望在正确的时机去subscribing,然而这种行为是没有保障的。更糟糕的是,当订阅失败时,通常是模糊的。

For example, suppose you’ve got an Adapter that you give an Observableas its data source. It needs to subscribe to the Observable and (at some point later) unsubscribe. The key problem with RxLifecycle here is: how do you know that your automatic unsubscription will happen at the right moment? Inside of the Adapter, there's no way to verify when in the lifecycle you're starting the subscription, and you have even less of a clue of when it's ending. Even if the code works now, if someone moves the Adapter listener code around it could change when it automatically unsubscribes. That’s messy.

例如,假设你有一个适配器,传递一个Observableas作为该Adapter的数据源。Adapter需要在某个时候订阅这个Observable和(在某个时间点)取消订阅。 RxLifecycle的关键问题是:你如何知道自动取消订阅会在正确的时刻发生?在适配器的内部,没有办法去获取你需要开始订阅的生命周期,也不知道何时去结束。即使代码现在可以工作,如果有人修改了Adapter的监听器代码(这里可以理解给Adapter更换了一个新的Observable作为数据源),可能会导致自动取消订阅的时机也跟着发生变动。这样子就会很混乱。

Over time I’ve grown weary of automatic code that sometimes breaks.I much prefer code that is rock-solid and never breaks, even if it means writing more boilerplate.

随着时间的推移,我已经厌倦了自动代码,有时会破裂。我更喜欢代码是坚如磐石的,永远不会中断,即使这意味着写更多的样板。

(Using the more explicit bindUntilEvent() instead of automatic detection somewhat avoids this problem, but lessens the utility of RxLifecycle.)

使用更明确的bindUntilEvent()而不是自动检测有些避免了这个问题,但是减少了RxLifecycle的实用程序。

Often times you end up manually handling the Subscription anyways.

Let's extend the Adapter example above. You're listening to one data source, but then whoever is controlling theAdapterwants to send it a new one, so it passes it a newObservable. You want to unsubscribe from the lastObservablebefore subscribing to the new one. None of this has anything to do with the lifecycle, and thus must be handled manually.

一般来说,你最终会通过手动的方式来处理订阅。继续上面的Adapter的示例。你正在监听一个数据源,但这个时候突然有人想要给他一个新的数据源,也就是传递一个新的Observable给Adapter。您要在订阅新的Observable之前取消以前订阅。这与生命周期无关,因此必须手动处理。

Having to manually handle Subscriptions anyways means that RxLifecycle is just an extra headache. It’s confusing to developers - why are we usingunsubscribe()in one place and RxLifecycle in another?

必须手动的去处理订阅意味着RxLifecycle只是一个额外的麻烦,它会使开发者感到迷惑,为什么我在一个地方需要手动处理,而在另外的地方可以使用RxLifecycle?

RxLifecycle can only simulate Subscription.unsubscribe().

Because of RxJava 1 limitations, it can (at most) simulate the stream ending due to onComplete(). 99% of the time this is fine, but it leaves open the door for developer mistakes due to subtle differences between onComplete() vs unsubscription.

RxLifecycle只是去模拟取消订阅。由于RxJava1的限制(RxJava1里面没有Single/Completable),在99%的情况下可以通过OnComplete()来结束Stream。但是由于onComplete()和untubscription之间的微妙差异,它为开发人员打开了一扇错误的门。

RxLifecycle throws exceptions for Single/Completable.

Again, because we can only simulate the stream ending.Single/Completable either emit or error, so there’s no other choice. For a while we weren’t using anything except Observable, but now that we’re using other types this can cause problems.

对于Single/Completable,RxLifecycle会抛出一个意外。同样的,因为我们只能模拟的去结束流,但是Single/Completable要么发送数据要么发送错误,没有其他选择。在前面的一段时间内我们只能使用Observable(RxJava1的时候),但是现在(RxJava2)我们可以使用其他类型的被观察者,这就会引起一些问题。

Subtle timing bugs require calling RxLifecycle late in the stream.

It’s an avoidable issue, but again can lead to developer mistakes that are best avoided.

由于一些时间上差异,我们只能在流的末尾去使用RxLifecycle。当然这是可以去避免发生,不过需要开发者去注意。

RxLint cannot detect when you’re using RxLifecycle bindings.

RxLint is a handy tool and using RxLifecycle lessens its utility.

在使用了RxLifecycle bingings的时候 RxLint无法检查。会降低RxLint的功能。

It generally requires subclassing Activity/Fragment.

While not a requirement (since it’s implemented using interfaces), not subclassing leads to a lot of busywork reproducing what the library does. That’s fine most of the time, but every once in a while we need to use a specializedActivityorFragmentand that causes pain.

大概意思你的Activity/Fragment需要去继承它要求的基类。

(Note that this minor problem can soon be fixed via Google'slifecycle-aware components.)

What it all boils down to is that the automatic nature of RxLifecycle can have complex, unintended consequences. While the goal of RxLifecycle was to make life easier, it often ended having the opposite effect.

所有这些都归结为RxLifecycle的自动性质可能会产生复杂的,意想不到的后果。虽然RxLifecycle的目标是使生活更轻松,但它往往会产生相反的效果

Some of these problems are solved by Uber’s AutoDispose library, which was born out of years of discussion between Zac Sweers and I on how to better write RxLifecycle. In particular, it uses true disposal instead of a simulacrum, does not throw exceptions for Single and Completable, and has fewer restrictions on when it can be used in a stream. I have not, however, just switched to AutoDispose because it doesn't solve all the above problems.

这些问题中的一些由Uber的AutoDispose库解决,这是由ZacSweers和我之间多年来就如何更好地编写RxLifecycle而进行的讨论而诞生的。特别地,它使用真正的处置而不是模拟,不会为Single和Completable抛出异常,并且对于在流中可以使用的限制更少。然而,我没有转而使用AutoDispose,因为它并不能解决上述所有问题。

Better Patterns

Here’s what I’ve started doing instead of using RxLifecycle.

Manually manageSubscriptions.

That means hanging onto Subscriptions(or stuffing them into a CompositeSubscription) then manually calling unsubscribe()/clear() when appropriate.

意味着保存Subscriptions对象(或着添加到CompositeSubscription对象中),然后在适当的时候手动调用unsubscribe()/ clear()。

Now that I’m used to the idea it’s not so bad. Its explicit nature makes code easier to reason about. It doesn't require me to think through a complex flow of logic or anticipate unexpected consequences. The extra boilerplate is worth the simplicity.

现在我习惯了这个想法,其实它并没有那么糟糕。其明确的性质使代码更容易理解。它不需要我思考一个复杂的逻辑流程或预期意想不到的后果。多写简单额外的样板是值得。

Components pass their Subscriptions upwards until someone handles it.

In other words, if a component is given an Observable from its parent but does not know when to unsubscribe, it passes the resulting Subscription upwards to the parent, since the parent should have a better grasp of the lifecycle.

也就是说,如果一个组件(比如Adapter)从它的父亲(比如Activity)获取到一个Observable,但是不知道在什么时候去取消订阅,那么该组件可以把订阅这个Observable后返回对象(Subscription往上传递给组件的父亲(比如Activity),因为父亲(Activity)可以更好的去获得生命周期。

Let’s look at that Adapter example from before. We now provide a function fun listen(data: Observable): Subscription. That way the Adapter can listen to the Observable, but is not responsible for knowing when it needs to stop listening; that responsibility is explicitly given to the owner of the Adapter.

比如上面的Adapter示例,我们现在提供一个函数(参数为Observable类型的data,返回值为Subscription类型)。该函数让Adapter可以去监听这个Observable,但是不需要它去处理什么时候停止监听。可以由Adapter的拥有者通过返回值Subscription对象控制什么时候取消订阅。

This pattern can be applied repeatedly to as many layers as you want. You could have an Activity that creates a View that contains a RecyclerView that creates an Adapter that listens to an Observable… but as long as you pass that Subscription upwards at each layer, it will eventually make its way back to a parent (possibly the Activity itself) who knows when to unsubscribe.

该模式可以应用到多层的结构(比如Activity-->Fragment--->RecyclerView)。您可以创建一个Activity,该视图包含一个RecyclerView,它创建一个Adapter,该Adapter监听了一个Observable。你只要把订阅Observable后返回的Subscription对面往上层传递,它将最终返回到顶层父亲(可能是一个Activity)。顶层父亲知道什么时候取消订阅。

Another subtle reason for the switch away from RxLifecycle is our adoption of Kotlin. Kotlin makes manually handling Subscriptions easier for two reasons:

宁外一个不使用RxLifecycle的理由,如果使用Kotlin,那么手动处理Subscriptions会变得很容易。

Unsubscribing from nullable Subscriptions is a simple one-liner. Before you had to check for nullability (or use a one-liner utility function). Annoying. Now you can just call mySubscription?.unsubscribe().

对一个可null的Subscription调用Unsubscrib()函数在Kotlin中只要简单一行,比如mySubscription?.unsubscribe()。而不需要像在java中进行非空判断。

A simple CompositeSubscription operator extension lets you use += to add Subscriptions. Otherwise you need to wrap your whole Observable chain in parentheses, which is a huge pain formatting-wise.

在Kotlin中我们只要使用+= 就可以把Subscriptions添加到CompositeSubscription。而不是调用add(subscription)函数。

Here’s the extension in all its glory:

下面是Kotlin的+=对CompositeSubscription类的扩展函数:

operator fun CompositeSubscription.plusAssign(subscription: Subscription) = add(subscription)

As a result, you can simply use a CompositeSubscription like so:

然后你只需要对CompositeSubscription对象按如下方式简单调用+=即可。

compositeSubscription += Observable.just().etc().subscribe()


Mea Culpa

It's not a great feeling when you have built and supported a framework which you no longer believe in. But admitting you were wrong is far more valuable than steadfastly sticking with a subpar solution.

当不再相信自己开发的框架时,这不是一个很好的感觉,但承认你错了比坚定不移地坚持一个不好的解决方案更有价值。

So... what now? Well, I'm going to keep maintaining RxLifecycle because people are still using it (including Trello), but in the long term I'm pulling away from it. For those still wanting this sort of library, I would suggest people look into AutoDispose, since I think it is better architecturally than RxLifecycle.

但是我还是会继续维护RxLifecycle,因为已经有那么多人在使用了(这其中包括Trello),但是在接下来的很长时间后我会渐渐地放弃。如果想使用类似的库,我建议使用AutoDispose,因为我觉得它的框架比RxLifecycle更优秀。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容