Зависание списка на Android обычно означает, что главный поток занят сетью, базой, преобразованием данных, декодированием изображений или слишком тяжелой отрисовкой. Ускорение одного адаптера не поможет, если приложение получает и обрабатывает тысячи элементов до первого кадра.
Снимите trace проблемного запуска и разделите время на сеть, parsing, запрос БД, mapping и UI. Любая тяжелая операция должна уйти с main thread, а список — загружаться страницами и переиспользовать элементы.
Что проверить в первую очередь
Начните с воспроизводимого сценария: зафиксируйте время сбоя, идентификатор объекта, версию приложения или конфигурации и последнее известное рабочее состояние. Не меняйте несколько параметров одновременно. Один контролируемый шаг должен подтверждать или исключать одну гипотезу, иначе временное исчезновение симптома легко принять за исправление. Перед работой с данными и настройками подготовьте резервную копию и понятный способ отката.
- Проверьте Logcat на ANR, skipped frames и NetworkOnMainThreadException.
- Измерьте время до первого элемента и до полной загрузки.
- Уточните размер ответа, число объектов и объем изображений.
- Сравните поведение release-сборки на слабом физическом устройстве.
Почему возникает проблема
Внешний симптом часто появляется не в том компоненте, где возникла первичная ошибка. Интерфейс может показывать неверное состояние из-за backend, очереди, кеша, прав доступа или внешнего API. Полезно проследить данные от источника до результата и найти первую точку расхождения. Это надежнее, чем исправлять последнее сообщение об ошибке или бесконечно перезапускать сервис.
- JSON разбирается или сортируется в главном потоке.
- RecyclerView или Compose получает новый полный список при каждом изменении.
- Изображения декодируются в исходном разрешении без кеша.
- Запрос Room не индексирован и возвращает лишние столбцы.
- Пагинация запрашивает следующие страницы циклически из-за неверного ключа.
Пошаговая диагностика
Диагностику проводите на тестовой записи или отдельном окружении. В журналах скрывайте токены, пароли, персональные данные и содержимое документов. Для каждого шага сохраняйте измеримый результат: код ответа, версию записи, идентификатор события, состояние процесса, контрольную сумму или время выполнения. Сравнение одной и той же операции до и после изменения помогает отделить причину от совпадения.
- Запишите System Trace или Perfetto и найдите длинные участки main thread.
- Добавьте отдельные тайминги сети, parsing, БД и bind/composition.
- Проверьте количество recomposition или bind для одного экрана.
- Временно замените изображения заглушками, чтобы оценить их вклад.
- Проверьте SQL query plan и размер локальной таблицы.
Как разделить загрузку данных и отрисовку
Пользователю важен быстрый первый полезный кадр, а не мгновенная обработка всего набора. Поток данных должен отдавать небольшие страницы, а интерфейс — обновлять только изменившиеся элементы.
- Сеть возвращает страницу с устойчивым cursor или page key.
- Repository преобразует данные на background dispatcher.
- База хранит кеш и отдает наблюдаемый поток ограниченного размера.
- UI использует стабильные keys и diff вместо полной перерисовки.
- Изображения загружаются по размеру контейнера с memory/disk cache.
Как исправить проблему
Разбейте исправление на небольшие обратимые изменения. Сначала устраните подтвержденную причину, затем повторите исходный сценарий и проверьте соседние функции. Массовое обновление данных запускайте на ограниченной выборке с отчетом и только после сверки расширяйте на весь объем. Не отключайте авторизацию, валидацию, шифрование или проверку сертификатов ради быстрого исчезновения ошибки.
- Перенесите parsing, сортировку и запросы БД в Dispatchers.IO/Default по назначению.
- Внедрите Paging 3 или контролируемую cursor pagination.
- Для RecyclerView используйте ListAdapter/DiffUtil; для Compose — immutable state и стабильные key.
- Уменьшайте изображения на сервере или через image loader до размера экрана.
- Добавьте индексы БД и не выбирайте поля, которые не нужны карточке.
Безопасный порядок внедрения
- Сохраните затрагиваемые данные, конфигурацию и текущие журналы, заранее проверив реальный способ восстановления.
- Повторите проблему на тестовом объекте без реальных списаний, рассылок и необратимых изменений клиентских данных.
- Зафиксируйте изменение в системе контроля версий или журнале работ вместе с причиной и планом отката.
- Проведите тест на нормальном сценарии, ошибочном вводе, повторном запросе, параллельной операции и временной недоступности зависимости.
- После выпуска наблюдайте логи, метрики и полный пользовательский путь, а не только один успешный запрос.
Как проверить результат
Разовый успешный тест недостаточен. Повторите операцию, проверьте крайние значения, одновременные действия и восстановление после перезапуска или временного сбоя. Для важного сценария сохраните автоматический тест либо короткий регрессионный чек-лист. Итог должен подтверждаться не только интерфейсом, но и состоянием базы, очереди, внешнего сервиса и журналом действий.
- Главный поток не содержит длинных блокирующих операций при открытии экрана.
- Первый экран появляется за приемлемое время на слабом устройстве и медленной сети.
- Прокрутка не запускает дублирующиеся запросы и не пропускает элементы.
- Поворот экрана и возврат из фона не загружают весь список заново.
Типичные ошибки при исправлении
- Отключать StrictMode и считать предупреждения несущественными.
- Загружать все страницы заранее ради простой логики.
- Вызывать notifyDataSetChanged для каждого небольшого изменения.
- Оптимизировать только эмулятор или debug-сборку.
Как предотвратить повторение
Профилактика строится вокруг явных контрактов, повторяемых релизов и наблюдаемости. Система должна не только работать сейчас, но и позволять быстро увидеть нарушение правила при следующем обновлении, росте нагрузки или сбое внешнего сервиса. Проверки полезно автоматизировать там, где ошибка уже привела к потерям времени, данных или заявок.
- Добавьте performance-тест открытия списка на реалистичном объеме.
- Следите за ANR, slow frames и размером сетевых ответов в production.
- Ограничьте максимальный размер страницы и изображений контрактом API.
- Проверяйте Compose recomposition или RecyclerView bind после изменений карточки.
Что подготовить для технического разбора
- Описание ожидаемого и фактического поведения с точной последовательностью действий.
- Время проблемы, идентификатор тестового объекта и версии затронутых компонентов.
- Фрагменты журналов до и после ошибки без секретов и персональных данных.
- Перечень последних изменений и уже выполненных проверок.
- Безопасный доступ к тестовой среде либо способ воспроизвести сбой без влияния на клиентов.
Частые вопросы
Почему список плавный на новом телефоне, но зависает у клиентов?
Мощный процессор и быстрый диск скрывают блокировки. Проверяйте слабое устройство, release-сборку и большой набор данных.
Нужно ли сразу переходить с RecyclerView на Compose?
Нет. Оба подхода могут работать быстро; сначала найдите блокировку, лишние обновления и объем данных.
Когда нужна помощь специалиста
Если Android-приложение зависает на списках, я могу снять trace, найти нагрузку main thread, оптимизировать сеть, БД, пагинацию и отрисовку, а затем проверить результат на реальном устройстве.