Оптимизация списков (RecyclerView/UITableView/ListView) мобильного приложения

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Оптимизация списков (RecyclerView/UITableView/ListView) мобильного приложения
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Оптимизация списков (RecyclerView/UITableView/ListView) мобильного приложения

UITableView дёргается при быстром скролле — и почти всегда причина не в «медленном железе», а в синхронной декодировке JPEG на main thread в cellForRowAt. Prefetch срабатывает слишком поздно, ячейка уже запрошена, и пока изображение декодируется — кадр пропущен. На iPhone SE 2nd gen с его скромной памятью это воспроизводится стабильно там, где на Pro не замечается вовсе.

Реальные причины тормозов

Самая частая история на Android: RecyclerView с LinearLayoutManager и сотнями элементов, где в onBindViewHolder выполняется Picasso.get().load(url).into(imageView) без явного placeholder и без отмены предыдущего запроса через tag. При быстром скролле запросы накапливаются, старые не отменяются, UI-поток периодически блокируется колбэками. Переход на Glide с RequestManager привязанным к lifecycle и preload() в onScrollStateChanged решает это без каких-либо изменений в логике.

На iOS аналогичная ситуация: SDWebImage без SDWebImageAvoidAutoSetImage применяет изображение на main thread сразу после загрузки, вне зависимости от того, видна ли ячейка. Добавляешь sd_setImageWithURL:placeholderImage:options:SDWebImageAvoidAutoSetImage и применяешь в completion только если indexPath == self.tableView.indexPathForCell(cell) — и дёрганье исчезает.

Вторая по частоте проблема — тяжёлые вычисления высоты ячейки. UITableView.automaticDimension удобен, но при сложном layout с несколькими UILabel запускает полный systemLayoutSizeFitting на каждую видимую ячейку. Кэш высот через [IndexPath: CGFloat] и пересчёт только при изменении данных решает проблему.

На Jetpack Compose LazyColumn без key {} не может корректно переиспользовать composable при изменении данных — при submitList с изменёнными элементами перерисовываются все видимые ячейки вместо изменённых.

Что делаем конкретно

Android RecyclerView:

  • setHasFixedSize(true) если размер RecyclerView не меняется при обновлении данных
  • setItemViewCacheSize(20) для увеличения offscreen кэша ячеек
  • RecycledViewPool.setMaxRecycledViews(type, count) при нескольких RecyclerView с одинаковым типом ячеек — шаринг пула
  • AsyncListDiffer или ListAdapter с DiffUtil.ItemCallback — diff на фоновом потоке обязателен для любого динамического списка
  • Prefetch через LinearLayoutManager.setInitialPrefetchItemCount() для nested horizontal списков

iOS UITableView / UICollectionView:

  • prefetchDataSource — декодируем и кэшируем данные до cellForRowAt
  • estimatedRowHeight с реальным значением (не 44 для ячеек высотой 120) — неправильный estimatedRowHeight вызывает прыжки при скролле
  • prepareForReuse() — обязательная отмена всех async-операций: imageLoadTask?.cancel()
  • Offscreen rendering ячеек через UIGraphicsImageRenderer для статичного контента (аватары, иконки с наложением)

Flutter LazyColumn (ListView.builder):

  • itemExtent — если все элементы одной высоты, указание фиксированного itemExtent убирает необходимость measure каждой ячейки
  • cacheExtent — увеличиваем до 500–1000 пикселей для preloading вне viewport
  • AutomaticKeepAliveClientMixin — сохраняем состояние ячеек при скролле назад

Кейс с вложенными списками

Горизонтальный RecyclerView внутри вертикального — распространённый паттерн для «Netflix-like» интерфейсов. Типичная ошибка: каждый горизонтальный RecyclerView создаёт свой RecycledViewPool. При скролле вертикального списка горизонтальные ресайклятся вместе с дочерними элементами, и при возврате ячейки их состояние (позиция скролла) теряется.

Решение: выносим RecycledViewPool на уровень активности и передаём в каждый горизонтальный RecyclerView через setRecycledViewPool(). Сохраняем LinearLayoutManager.onSaveInstanceState() в ViewModel по ключу позиции. Итог — плавный скролл и сохранение позиции при прокрутке вертикального списка.

Сроки

Аудит и оптимизация одного проблемного списка — 2–4 дня. Системная работа со всеми списками в приложении — 1–2 недели.