使用 rxbinding 时我应该取消订阅吗?

Should I unsubscribe when using rxbinding?

这是我在 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()) }
}

Observable.subscribe(action)returnsSubscription。我应该保留它作为参考并退订 onPause()onDestroy() 吗?

像这样:

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

是的,如果您查看 doc,它明确表示:

  • Warning: The created observable keeps a strong reference to view. Unsubscribe to free this reference.

我做了一个小测试设置来找出它。它不是 Android 应用程序,但它模拟了 class 关系。这是它的样子:

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")
}

在此代码段中,我实例化了一个 View,它包含对 Context 的引用。该视图有一个点击事件的回调,我将其包装在 Observable 中。我触发回调一次,然后清空对 ViewContext 的所有引用,只保留 PhantomReference。然后,在一个单独的线程上等待 Context 实例被释放。如您所见,我从不取消订阅 Observable.

如果你运行代码,它会打印

Click

Collected

然后终止证明对 Context 的引用确实被释放了。


这对您意味着什么

如您所见,如果对它的唯一引用是循环引用,Observable 将不会阻止收集引用的对象。您可以在 this question.

中阅读有关循环引用的更多信息

然而,情况并非总是如此。根据您在可观察链中使用的运算符,引用 can 会被泄露,例如通过调度程序或者如果您将它与无限可观察对象合并,例如 interval(). Explictly unsubscribing from an observable is always a good idea and you can reduce the necessary boilerplate by using something like RxLifecycle.

是的,你应该unsubscribe when using RxBinding

这是一种方法...(在 java 中,可以针对 kotlin 进行调整吗?)

收集

在您的 Activity 或 Fragment 中,将一次性用品添加到您将在 onDestroy() 处理的 CompositeDisposable。

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();
}

我认为 Jake Wharton(库的创建者)给出了 best answer

Treat a subscribed RxView.clicks() (or any Observable from this library for that matter) like you would the View reference itself. If you pass it (or subscribe to it) somewhere outside the lifetime of the View, you've just leaked your entire activity.

So if you're just subscribing inside your ViewHolder there's no need to unsubscribe just like there'd be no need to unregister a click listener were you doing it manually.