我为什么要订阅主线程?

时间:2018-09-22 10:11:45

标签: android rx-java

试图从该资源中找出一些东西:https://www.raywenderlich.com/384-reactive-programming-with-rxandroid-in-kotlin-an-introduction

我遇到一个问题:为什么我应该在主线程而不是Schedulers.io()中调用subscribeOn()?

当我这样订阅时,我的应用程序冻结了好几秒钟,我丢了​​帧。

searchTextObservable
                .subscribeOn(Schedulers.io())
                .map { cheeseSearchEngine.search(it) }
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    showResult(it)
                }

Dropping frames screen

然后我订阅主线程,并在Schedulers.io()中观察它(我也不知道为什么要那样做)应用程序根本没有冻结。

searchTextObservable
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(Schedulers.io())
                .map { cheeseSearchEngine.search(it) }
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    showResult(it)
                }

任何人都可以解释为什么会这样吗?

编辑

// 1
private fun createTextChangeObservable(): Observable<String> {
  // 2
  val textChangeObservable = Observable.create<String> { emitter ->
    // 3
    val textWatcher = object : TextWatcher {

      override fun afterTextChanged(s: Editable?) = Unit

      override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit

      // 4
      override fun onTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        s?.toString()?.let { emitter.onNext(it) }
      }

    }

    // 5
    queryEditText.addTextChangedListener(textWatcher)

    // 6
    emitter.setCancellable {
      queryEditText.removeTextChangedListener(textWatcher)
    }
  }

  // 7
  return textChangeObservable
}

3 个答案:

答案 0 :(得分:2)

subscribeOn vs.observeOn

subscribeOn将在给定的Scheduler上调用Observable的create方法。多少次使用subscribeOn都没有关系。源可观察的第一个SubscribeOn(链中的第一个)总是获胜。

observeOn将在操作员之间切换线程。当上游在线程X上发出值时,它将从observeOn-Operator中的给定调度程序切换到线程Y。现在,observeOn下面的所有内容都将在线程Y中处理。

提供的示例1的最佳猜测: 使用subscriptionOn将在Schedulers#io上调用Observable#create。将在Scheduler#io的此线程上调用create-lambda中的所有内容。侦听器回调(onTextChanged)实际上可以在另一个线程上发生。在这种情况下,它是UI-Thread,因为它是某种UI元素。现在,将从UI线程(emitter.onNext(it))调用onNext。该值将被发送到UI-Thread(.map {cheeseSearchEngine.search(it)})上的#map运算符,而cheeseSearchEngine#search将阻止UI-Thread。

示例2: 用作第一个运算符“ .subscribeOn(AndroidSchedulers.mainThread())”。实际上这没有任何效果,因为您已经在UI-Thread中。在这种情况下,将从AndroidSchedulers#mainThread调用create-lambda。就在Example1中,onNext也会在UI-Thread上发出,因为UI触发了onTextChanged-Event。然后将值通过observeOn(Schedulers.io())放入。来自observeOn-point的所有内容都将在Schedulers#io-Thread上执行。当map执行某些HTTP请求(或某种长时间运行的IO)时,这将不会阻止ui。映射完成并向下游发出下一个值后,下一个observeOn(AndroidSchedulers.mainThread())将切换回UI-Thread。因此,由于您位于UI线程上,因此您现在可以安全地更改subscription-lambda中的UI。 结论是,如果侦听器注册是从哪个线程发生的(侦听器注册可能必须是线程安全的)无关紧要,则可以省略Example2中的第一个subscriptionOn。

摘要: 使用subscribeOn将仅在给定的Scheduler-Thread上调用create lambda。 create中已注册侦听器的回调可能在另一个线程上发生。 这就是Example1将阻止UI线程而Example2不会阻止UI线程的原因。

答案 1 :(得分:1)

这是Rx的美。轻松进行线程切换。基本上,在Rx中,我们可以仅通过调用subscriptionOn()或ObserveOn()在不同线程之间切换。两者之间的区别在于,当调用subscriptionOn(Thread1)时,任务(在您的示例中-cheeseSearchEngine.search(it))在Thread1上运行。

但是,当您调用observeOn(Thread2)时,执行任务的结果将交给Thread2。这意味着结果将在Thread2上工作。 (在您的示例中,showResult将在Thread2上调用)

因此,当您调用subscribeOn(Schedulers.io())时,该任务是在IO线程上完成的。准备好结果后,将在调用observeOn(AndroidSchedulers.mainThread())时将其提供给主UI线程。

反之亦然,您基本上是在尝试在UI线程上执行任务,而不是使用IO后台线程。使用这种方法,如果您尝试更新任何UI元素,则会引发一个异常,指出“无法从后台线程访问UI元素(CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图)”。

希望我回答你的问题。 Rx中的编码很愉快。

答案 2 :(得分:1)

由于.map运算符,我认为代码有点误导,该运算符实际上用于执行昂贵的操作(搜索)。更好的方法是使用fromCallable包装代码,然后使用subscriptionOn将其转换为异步调用。像这样:

d3.interpolateZoom

解释见代码。我认为现在的意图更加清晰。有关详细信息,请参见HansWursrt的答案