Зачем выполнять загрузку изображений не на main thread в Android?
Что проверяют
Понимаете ли вы, как устроен главный поток Android и что конкретно в загрузке картинки его блокирует: сеть, диск и декодирование. Следом обычно спрашивают, как это решают Glide или Coil и что происходит с картинкой в переиспользованной ячейке RecyclerView.
Как отвечать
Главный поток обрабатывает ввод, измеряет, раскладывает и отрисовывает интерфейс. На кадр при 60 Гц у него около 16 мс, при 120 Гц около 8 мс. Всё, что дольше, даёт пропущенные кадры и дёрганую прокрутку, а если поток занят несколько секунд, система показывает ANR: на обработку события ввода даётся 5 секунд.
В загрузке картинки тормозят три этапа:
- сеть: сетевой запрос на главном потоке запрещён, начиная с Android 3.0 приложение получает
NetworkOnMainThreadException; - диск: чтение из кэша тоже блокирующая операция,
StrictModeпокажет её как нарушение; - декодирование:
BitmapFactoryиImageDecoderнагружают процессор и память. Фото 4000×3000 вARGB_8888занимает около 48 МБ, поэтому его декодируют с уменьшением под размерImageViewчерезinSampleSizeили целевой размер.
При этом менять View можно только из главного потока, иначе CalledFromWrongThreadException. Отсюда схема на корутинах: сеть и диск на Dispatchers.IO, декодирование на Dispatchers.Default, запуск из lifecycleScope или viewModelScope, где результат возвращается на главный поток, а работа отменяется вместе с экраном.
На практике берут Coil (корутины, поддержка Compose) или Glide. Они дают пулы потоков, кэш в памяти по LRU и дисковый кэш, даунсэмплинг, переиспользование битмапов и отмену запроса, когда ImageView ушёл из прокрутки. AsyncTask как решение называть не стоит: он объявлен устаревшим в API 30.
Где спотыкаются
Называют только ANR. Интерфейс начинает дёргаться задолго до пяти секунд, достаточно пары пропущенных кадров при прокрутке.
Забывают про декодирование. Фоновая загрузка байтов не спасает, если decodeByteArray() полноразмерного фото всё равно выполняется на главном потоке.
Не думают об отмене. Ячейка RecyclerView переиспользуется, и старый запрос, завершившись позже нового, покажет чужую картинку, если его не отменить.