Как передать результат из фонового потока на UI поток в Android
Что проверяют
Знаете ли вы, что обновлять интерфейс можно только с главного потока, и какие способы актуальны сегодня.
Как отвечать
Правило простое: элементы интерфейса трогает только главный поток. Попытка обновить их из фонового даёт CalledFromWrongThreadException.
Способы, начиная с современных:
1. Корутины. Основной вариант. Работа выполняется на Dispatchers.IO, а возврат на главный делается сменой контекста:
viewModelScope.launch {
val jobs = withContext(Dispatchers.IO) { repository.load() }
_state.value = UiState.Content(jobs) // уже на главном
}viewModelScope по умолчанию использует главный диспетчер, поэтому после withContext код продолжается там, где нужно.
2. StateFlow или SharedFlow. Модель представления публикует состояние, экран подписывается в lifecycleScope через repeatOnLifecycle, чтобы сбор шёл только пока экран виден и не тратил ресурсы в фоне.
3. LiveData. setValue только с главного потока, postValue с любого. Важная тонкость: postValue при частых вызовах подряд доставит только последнее значение, промежуточные теряются. Для потока событий это неверное поведение.
4. Низкоуровневые способы, которые встречаются в старом коде: runOnUiThread у активити, view.post, Handler(Looper.getMainLooper()).post. Работают, но требуют ручной заботы о жизненном цикле: результат может прийти на уничтоженный экран.
Общее правило поверх всех способов: результат должен приходить в область с правильным жизненным циклом, чтобы работа отменялась при уходе с экрана, а обновление не прилетало в уничтоженное представление.
Где спотыкаются
Обновляют интерфейс прямо из фонового потока.
Используют postValue для последовательности событий и теряют часть.
Запускают работу в глобальной области, и она переживает экран.
Собирают поток без учёта жизненного цикла и тратят ресурсы в фоне.