Можно ли обновлять UI не из UI-потока?

Нет, напрямую обновлять UI из не-UI потока в Android нельзя.
Все операции, связанные с пользовательским интерфейсом (например, изменение текста TextView, изменение ProgressBar, обновление RecyclerView) должны выполняться строго из UI-потока (также называемого главным потоком, main thread).

Если попытаться изменить UI из фонового потока (например, Thread, Coroutine с Dispatchers.IO, AsyncTask.doInBackground() и т.д.), возникнет CalledFromWrongThreadException или приложение начнёт вести себя нестабильно.

🔍 Почему нельзя обновлять UI из фонового потока

Android UI toolkit (View-система) не потокобезопасна. Она разрабатывалась с расчётом на то, что доступ к UI будет только из одного потока — главного. Если попытаться изменить свойства UI-элементов из другого потока, возможны:

  • сбои и исключения во время исполнения;

  • гонки данных и визуальные артефакты;

  • повреждение внутреннего состояния View.

✅ Как правильно обновить UI из не-UI потока

1. Через runOnUiThread (в Activity)

Thread {
val result = loadData()
runOnUiThread {
textView.text = result
}
}.start()

2. Через Handler (устаревший, но рабочий способ)

val handler = Handler(Looper.getMainLooper())
Thread {
val result = loadData()
handler.post {
textView.text = result
}
}.start()

3. С помощью корутин (Dispatchers.Main)

lifecycleScope.launch {
val data = withContext(Dispatchers.IO) {
fetchData()
}
textView.text = data // выполняется на главном потоке
}

→ withContext(Dispatchers.IO) переключает на фоновый поток, launch в Dispatchers.Main — на UI-поток.

4. Через LiveData и ViewModel

// ViewModel
val textData = MutableLiveData<String>()
fun loadText() {
viewModelScope.launch {
val result = fetchTextFromDB()
textData.value = result
}
}
// Activity или Fragment
viewModel.textData.observe(viewLifecycleOwner) {
textView.text = it
}

📌 Что происходит, если всё же попытаться изменить UI из фонового потока

Thread {
textView.text = "Ошибка" // вызовет исключение
}.start()

Результат:

android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.

🧠 Итого

Обновление UI должно происходить только в UI-потоке.
Для передачи данных с фонового потока на UI-поток используют:

  • runOnUiThread { ... }

  • Handler(Looper.getMainLooper())

  • Dispatchers.Main в корутинах

  • LiveData и ViewModel

Это обеспечивает стабильную и безопасную работу интерфейса без ошибок и гонок потоков.