Иногда ConflatedBroadcastChannel запускает недавнее значение без каких-либо действий - PullRequest
1 голос
/ 30 апреля 2020

В официальной кодовой метке Google около образца advanced-coroutines-codelab они использовали ConflatedBroadcastChannel до наблюдения за изменением переменной / объекта .

I ' Я использовал ту же технику в одном из моих побочных проектов, и при возобновлении прослушивания иногда 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

  • Почему это происходит?
  • Чего мне не хватает?

Ответы [ 3 ]

1 голос
/ 07 мая 2020

В дополнение к ответу 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() }

}
0 голосов
/ 09 мая 2020

Цитата из официального ответа , (Простое и понятное решение)

Проблема в том, что вы пытаетесь использовать ConflatedBroadcastChannel для событий, в то время как это предназначен для представления текущего состояния, как показано в кодовой метке. Каждый раз, когда нисходящий поток LiveData активируется, он получает самое последнее состояние и выполняет действие увеличения. Не используйте ConflatedBroadcastChannel для событий.

Чтобы исправить это, вы можете заменить ConflatedBroadcastChannel на BroadcastChannel<IncrementRequest>(1) (несокращенный канал, который подходит для использования событий), и он будет работать как ты тоже этого ожидаешь.

0 голосов
/ 30 апреля 2020

Причина очень проста, ViewModels может сохраняться вне жизненного цикла Activities. Переходя к другому виду деятельности и собирая мусор, вы избавляетесь от оригинала MainActivity, но сохраняете оригинал MainViewModel.

Затем, когда вы возвращаетесь из DetailActivity, он воссоздает MainActivity, но повторно использует модель представления, которая все еще имеет широковещательный канал с последним известным значением, вызывая обратный вызов при вызове count.observe.

Если вы добавите запись в журнал для наблюдения за методами действия onCreate и onDestroy, вы должны увидеть, как жизненный цикл становится продвинутый, тогда как viewmodel должен быть создан только один раз.

...