为什么不使用 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.的作者。


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.


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.


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事件发生时,那么就会自动的结束该流。那么我们就又可以回到以前不用担心内存泄漏的美好时光了。


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:


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.


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.)


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.


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 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 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.


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.


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.


(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.


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.


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.


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.


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.


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


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().


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:


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

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


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.