有时,ConflatedBroadcastChannel会在没有任何操作的情况下发射最近的值。

问题描述 投票:0回答:1

在谷歌的官方代码实验室里,关于 高级coroutines-codelab 样品,他们已经用 ConflatedBroadcastChannel观察变量对象的变化.

我在我的一个副业项目中使用了同样的技术,当恢复监听活动时,有时会出现 ConflatedBroadcastChannel 触发它的最近值,导致执行了 flatMapLatest 体没有任何变化。

我认为这是在系统收集垃圾时发生的,因为我可以通过调用 System.gc() 来自另一个活动。

issue

下面是代码

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
        val tvCount = findViewById<TextView>(R.id.tv_count)

        viewModel.count.observe(this, Observer {
            tvCount.text = it
            Toast.makeText(this, "Incremented", Toast.LENGTH_LONG).show();
        })

        findViewById<Button>(R.id.b_inc).setOnClickListener {
            viewModel.increment()
        }

        findViewById<Button>(R.id.b_detail).setOnClickListener {
            startActivity(Intent(this, DetailActivity::class.java))
        }

    }
}

MainViewModel.kt

class MainViewModel : ViewModel() {

    companion object {
        val TAG = MainViewModel::class.java.simpleName
    }

    class IncrementRequest

    private var tempCount = 0
    private val requestChannel = ConflatedBroadcastChannel<IncrementRequest>()

    val count = requestChannel
        .asFlow()
        .flatMapLatest {
            tempCount++
            Log.d(TAG, "Incrementing number to $tempCount")
            flowOf("Number is $tempCount")
        }
        .asLiveData()

    fun increment() {
        requestChannel.offer(IncrementRequest())
    }
}

DetailActivity.kt

class DetailActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_detail)
        val button = findViewById<Button>(R.id.b_gc)


        val timer = object : CountDownTimer(5000, 1000) {
            override fun onFinish() {
                button.isEnabled = true
                button.text = "CALL SYSTEM.GC() AND CLOSE ACTIVITY"
            }

            override fun onTick(millisUntilFinished: Long) {
                button.text = "${TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished)} second(s)"
            }
        }

        button.setOnClickListener {
            System.gc()
            finish()
        }

        timer.start()

    }
}

这里是完整的源代码。CoroutinesFlowTest.zip

  • 为什么会这样?
  • 我遗漏了什么?
android kotlin android-architecture-components kotlin-coroutines kotlinx.coroutines.channels
1个回答
1
投票

除了 基斯凯的回答:

这可能不是你的情况,但你可以尝试使用 BroadcastChannel(1).asFlow().conflate 但在我的例子中,它导致了一个错误,即接收器侧的代码有时不会被触发(我认为是因为 混为一谈 工作在一个单独的coroutine或其他东西)。)

或者你可以使用一个自定义版本的无状态ConflatedBroadcastChannel(可以在这里找到 此处).

class StatelessBroadcastChannel<T> constructor(
    private val broadcast: BroadcastChannel<T> = ConflatedBroadcastChannel()
) : BroadcastChannel<T> by broadcast {

    override fun openSubscription(): ReceiveChannel<T> = broadcast
        .openSubscription()
        .apply { poll() }

}

0
投票

原因很简单。ViewModels 可以在生命周期之外持续存在。Activities. 通过移动到另一个活动并收集垃圾,你将处理原来的 MainActivity 但保持原样 MainViewModel.

然后当你回来的时候 DetailActivity 它重现了 MainActivity 但重用viewmodel,它仍然有一个最后已知值的broadcastchannel,触发回调,当 count.observe 被调用。

如果您添加了日志记录来观察 onCreateonDestroy 活动的方法,你应该看到生命周期越来越高级,而视图模型应该只创建一次。


0
投票

引自 官方回应(简单直接的解决方案)

这里的问题是,你试图使用 ConflatedBroadcastChannel 为事件,而它的设计是为了表示当前状态,如codelab所示。每当下游的 LiveData 重新激活时,它会接收最新的状态并执行增量动作。不要使用 ConflatedBroadcastChannel 的事件。

要解决这个问题,您可以将 ConflatedBroadcastChannelBroadcastChannel<IncrementRequest>(1) 非conflated通道,这对于事件来说是Ok的),它也会按照你的预期工作。

© www.soinside.com 2019 - 2024. All rights reserved.