Как реализована синхронизация при обработке событий в UI слое Android
Что проверяют
Понимает ли кандидат, что в UI слое Android синхронизация достигается не блокировками, а привязкой к одному потоку и очереди сообщений. Ждут связного рассказа про main thread, Looper/Handler и современные способы вернуть результат фоновой работы в UI: корутины, StateFlow, LiveData.
Как отвечать
Базовая модель: у главного потока есть Looper, он крутит MessageQueue, а Handler кладёт в эту очередь сообщения и Runnable. Все обработчики кликов, колбэки жизненного цикла и отрисовка выполняются как задачи из одной очереди, последовательно. Именно поэтому в UI слое не нужны мьютексы: состояние View правится только из главного потока, это thread confinement, а не синхронизация примитивами.
Обращение к View из фонового потока ловит проверка в ViewRootImpl и падает с CalledFromWrongThreadException. Вернуться в главный поток можно так:
Handler(Looper.getMainLooper()).post { textView.text = result }
// или view.post { ... }, или requireActivity().runOnUiThread { ... }Дальше про инструменты:
AsyncTaskобъявлен устаревшим в API 30. Упомянуть можно как историю, предлагать как решение нельзя.- Корутины это основной способ сегодня. Работа запускается в
viewModelScopeилиlifecycleScope, тяжёлое уходит вwithContext(Dispatchers.IO), результат возвращается вDispatchers.Main.GlobalScopeне использовать: он не отменяется вместе с экраном и даёт утечки. LiveData:observe(viewLifecycleOwner, ...)во фрагменте, неthis.setValueвызывается только с главного потока,postValueс любого, но при частых вызовах подряд подписчик увидит только последнее значение.StateFlow/SharedFlow: собирать черезrepeatOnLifecycle(Lifecycle.State.STARTED), иначе сбор продолжится, когда экран не виден.
Отдельно стоит развести состояние и события. Состояние экрана держит StateFlow, оно переживает поворот. Одноразовые события (навигация, снекбар, тост) через StateFlow продублируются после пересоздания Activity, для них берут Channel плюс receiveAsFlow().
Про утечки: Handler в нестатическом внутреннем классе удерживает Activity через отложенные сообщения, поэтому в onDestroy вызывают removeCallbacksAndMessages(null).
Где спотыкаются
Начинают рассказывать про synchronized и volatile, хотя в UI слое конкуренцию снимает сама очередь сообщений. Второй классический промах: observe(this) внутри фрагмента вместо viewLifecycleOwner, из-за чего после пересоздания вью подписки накапливаются.