Что можно и нельзя делать в главном (main) потоке в Android
Что проверяют
Понимаете ли вы, как устроен главный поток: один Looper, одна очередь сообщений, и всё, что её задерживает, пользователь видит как подтормаживание или ANR. Следом спрашивают, как вы уводите работу в фон на корутинах и возвращаете результат в UI.
Как отвечать
Главный поток обрабатывает ввод, вызывает колбэки жизненного цикла, измеряет и рисует интерфейс. На кадр при 60 Гц есть около 16 мс, при 120 Гц около 8 мс, и в этот бюджет входит всё, что вы туда положили.
Что делают в главном потоке:
- работа с View: трогать их можно только из потока, который их создал, иначе
CalledFromWrongThreadException; - колбэки и обработчики:
onCreate,onClick, навигация, короткая логика; - обновление состояния для UI:
LiveData.setValue, запись вStateFlow, который собирает Compose.
Чего там быть не должно:
- сеть: начиная с API 11 это сразу
NetworkOnMainThreadException; - база: Room бросает исключение на запросе из главного потока, пока не включён
allowMainThreadQueries(), которым в проде не пользуются; - диск и тяжёлые вычисления: чтение файлов, парсинг большого JSON, декодирование картинок,
SharedPreferences.commit(); - ожидание:
Thread.sleep,runBlocking, блокирующийget()уFuture.
Сегодня стандарт это корутины: viewModelScope.launch стартует на Dispatchers.Main.immediate, тяжёлое уходит в withContext(Dispatchers.IO) или Dispatchers.Default, результат возвращается в главный поток сам. Хорошая функция main-safe: её можно звать из главного потока, а переключение она делает внутри, как suspend-методы DAO в Room. Нарушения ловит StrictMode в debug-сборке.
ANR система показывает, если приложение не отвечает на ввод около 5 секунд или BroadcastReceiver не успел завершить onReceive.
Где спотыкаются
Называют AsyncTask рабочим инструментом. Он объявлен устаревшим в API 30, и в новом коде его нет.
Думают, что launch сам уводит работу в фон. Корутина в viewModelScope без withContext выполнится на главном потоке и точно так же его заблокирует.
Путают postValue и setValue: если вызвать postValue несколько раз до обработки, подписчик получит только последнее значение.