4

在 Google 的关于advanced-coroutines-codelabConflatedBroadcastChannel示例的官方代码实验室中,他们曾经观察变量/对象的变化

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

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

问题

这是代码

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

    }
}

主视图模型.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())
    }
}

详细活动.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

  • 为什么会这样?
  • 我错过了什么?
4

4 回答 4

2

除了Kiskae 的回答

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

或者,您可以使用无状态 ConflatedBroadcastChannel 的自定义版本(在此处找到)。

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

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

}
于 2020-05-07T12:21:02.000 回答
2

引用官方回复,(简单直接的解决方案)

这里的问题是您试图将其 ConflatedBroadcastChannel用于事件,而它旨在表示当前状态,如代码实验室中所示。每次下游LiveData重新激活时,它都会接收最新状态并执行递增操作。不要 ConflatedBroadcastChannel用于活动。

要修复它,您可以替换ConflatedBroadcastChannelBroadcastChannel<IncrementRequest>(1)(non-conflated channel,事件可以使用),它也会按您的预期工作。

于 2020-05-09T14:59:50.567 回答
0

原因很简单,ViewModels可以在Activities. 通过转移到另一个活动和垃圾收集,您正在处理原始文件MainActivity但保留原始文件MainViewModel

然后当你从DetailActivity它返回时重新创建MainActivity但重用视图模型,它仍然具有最后一个已知值的广播通道,在count.observe被调用时触发回调。

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

于 2020-04-30T15:07:20.143 回答
0

在 Coroutine 1.4.2 和 Kotlin 1.4.31 上

不使用实时数据

private var tempCount = 0
private val requestChannel = BroadcastChannel<IncrementRequest>(Channel.CONFLATED)

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

使用流和协程

lifecycleScope.launchWhenStarted {
     viewModel.count.collect {
          tvCount.text = it
          Toast.makeText(this@MainActivity, "Incremented", Toast.LENGTH_SHORT).show()
    }
}

不使用 BroadcastChannel

private var tempCount = 0
    private val requestChannel = MutableStateFlow("")

    val count: StateFlow<String> = requestChannel
    
    fun increment() {
        tempCount += 1
        requestChannel.value = "Number is $tempCount"
    }
于 2021-03-25T04:56:45.900 回答