在使用rxbinding时,我是否应该取消订阅?

13

这是我如何在Kotlin中使用RxBinding:

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    reset_password_text_view.clicks().subscribe { presenter.showConfirmSecretQuestionBeforeResetPassword() }
    password_edit_text.textChanges().skip(1).subscribe { presenter.onPasswordChanged(it.toString()) }
    password_edit_text.editorActionEvents().subscribe { presenter.done(password_edit_text.text.toString()) }
}
< p >Observable.subscribe(action) 返回 Subscription。我应该将其保留为引用并在 onPause()onDestroy() 中取消订阅吗?< /p >

像这样:


就像这样:

private lateinit var resetPasswordClicksSubs: Subscription

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    resetPasswordClicksSubs = reset_password_text_view.clicks().subscribe { presenter.showConfirmSecretQuestionBeforeResetPassword() }
}

override fun onDestroy() {
    super.onDestroy()
    resetPasswordClicksSubs.unsubscribe()
}
4个回答

16

我认为库的创建者Jake Wharton提供了最佳答案:

将订阅的RxView.clicks()(或来自此库的任何Observable)视为View引用本身。如果您在View的生命周期之外传递它(或订阅它),那么您就会泄漏整个Activity。

因此,如果您只是在ViewHolder内部进行订阅,就不需要取消订阅,就像手动注册点击监听器一样不需要注销一样。


13

我已经建立了一个小型测试环境来找出答案。虽然它不是一个Android应用程序,但它模拟了类之间的关系。以下是它的样子:

class Context
class View(val context: Context) {
    lateinit var listener: () -> Unit
    fun onClick() = listener.invoke()
}

fun View.clicks() = Observable.fromEmitter<String>({ emitter ->
    listener = { emitter.onNext("Click") }
}, Emitter.BackpressureMode.DROP)


var ref: PhantomReference<Context>? = null

fun main(args: Array<String>) {
    var c: Context? = Context()
    var view: View? = View(c!!)

    view!!.clicks().subscribe(::println)
    view.onClick()
    view = null

    val queue = ReferenceQueue<Context>()
    ref = PhantomReference(c, queue)
    c = null

    val t = thread {
        while (queue.remove(1000) == null) System.gc()
    }
    t.join()

    println("Collected")
}

在这个代码片段中,我实例化了一个包含对Context引用的View。该视图具有处理点击事件的回调函数,我将其包装在一个Observable中。我触发回调一次,然后将所有对ViewContext的引用设置为null,并仅保留一个PhantomReference。然后,在单独的线程上,我等待Context实例被释放。如您所见,我从未取消订阅Observable
如果运行此代码,它将打印
Click Collected
然后终止,证明确实释放了对Context的引用。

这对您意味着什么

如您所见,如果Observable对对象的唯一引用是循环引用,则不会阻止被引用的对象被收集。您可以在这个问题中了解更多关于循环引用的信息。
然而,这并非总是如此。根据您在可观察链中使用的操作符,引用可能会泄漏,例如通过调度程序或者将其与无限可观察对象(如interval())合并。明确取消订阅Observable总是一个好主意,您可以使用类似于RxLifecycle的东西来减少必要的样板代码。

感谢您提供如此详细的解释!在我的情况下,即使已经处理了“View”,.editorActionEvents()仍然会发出事件。我将尝试使用RxLifecycle在onDestroy中取消订阅。 - Alexandr

5

是的,当使用RxBinding时,您应该取消订阅

以下是一种方法...(在Java中,可以进行调整以适用于Kotlin?)

收集

在您的Activity或Fragment中,将disposable添加到CompositeDisposable中,您将在onDestroy()处处理它们。

CompositeDisposable mCompD; // collector

Disposable d = RxView.clicks(mButton).subscribe(new Consumer...);

addToDisposables(mCompD, d); // add to collector

public static void addToDisposables(CompositeDisposable compDisp, Disposable d) {
    if (compDisp == null) {
        compDisp = new CompositeDisposable();
    }

    compDisp.add(d);
}

处理

@Override
protected void onDestroy() {
    mCompD.dispose();
    super.onDestroy();
}

如果CompositeDisposable存在但已被处理,请格外小心,当添加另一个Disposable时,它会立即调用disposable.dispose()。这就是为什么我们应该重新创建CompositeDisposable,即使comDisp.isDisposed()。 - pumnao

4

是的,如果你查看文档,它明确指出:

  • 警告:创建的可观察对象会对view保持强引用。取消订阅以释放该引用。

1
这并没有回答问题。如果活动被销毁并且没有任何引用保留它,那么可观察对象也将被收集。问题是,使用RxBinding是否会创建对活动的引用,如果您只使用侦听器,则不会创建这些引用?我的猜测是除非您使用在特殊调度程序上运行的某些操作符,否则不会创建这些引用。 - Kirill Rakhman
如果您查看任何视图构造函数,它都需要一个上下文的引用。因此,任何未取消订阅的绑定都将防止您的活动被垃圾回收。 - Alexander Perfilyev
如果唯一的引用是一个循环,那么就不需要传递一个匿名内部类。这与传递一个匿名内部类完全相同。 - Kirill Rakhman
这个可观察对象永远不会完成,并且会一直保留你的引用,直到你取消订阅。 - Alexander Perfilyev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接