在谷歌的官方代码实验室里,关于 高级coroutines-codelab 样品,他们已经用 ConflatedBroadcastChannel
到 观察变量对象的变化.
我在我的一个副业项目中使用了同样的技术,当恢复监听活动时,有时会出现 ConflatedBroadcastChannel
触发它的最近值,导致执行了 flatMapLatest
体没有任何变化。
我认为这是在系统收集垃圾时发生的,因为我可以通过调用 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))
}
}
}
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
除了 基斯凯的回答:
这可能不是你的情况,但你可以尝试使用 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() }
}
原因很简单。ViewModels
可以在生命周期之外持续存在。Activities
. 通过移动到另一个活动并收集垃圾,你将处理原来的 MainActivity
但保持原样 MainViewModel
.
然后当你回来的时候 DetailActivity
它重现了 MainActivity
但重用viewmodel,它仍然有一个最后已知值的broadcastchannel,触发回调,当 count.observe
被调用。
如果您添加了日志记录来观察 onCreate
和 onDestroy
活动的方法,你应该看到生命周期越来越高级,而视图模型应该只创建一次。
引自 官方回应(简单直接的解决方案)
这里的问题是,你试图使用
ConflatedBroadcastChannel
为事件,而它的设计是为了表示当前状态,如codelab所示。每当下游的LiveData
重新激活时,它会接收最新的状态并执行增量动作。不要使用ConflatedBroadcastChannel
的事件。要解决这个问题,您可以将
ConflatedBroadcastChannel
与BroadcastChannel<IncrementRequest>(1)
非conflated通道,这对于事件来说是Ok的),它也会按照你的预期工作。