為什么不使用 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更優秀。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容