Lazy Loading в мобильной разработке — как устроено, принципы и реализация

Автор: IT Sectr Опубликовано: 2026-04-01 Время чтения: 9 мин

Lazy Loading — стратегия отложенной загрузки данных, изображений и компонентов, при которой ресурсы запрашиваются не при старте приложения, а в момент, когда они реально понадобились пользователю. По данным Android Paging 3 Guide, ленивая загрузка списков снижает потребление памяти на 60–80% при работе с большими наборами данных. Отложенная инициализация — ключевой принцип, лежащий в основе всех реализаций Lazy Loading.

Главное

  • Lazy Loading — паттерн отложенной загрузки, экономящий память и ускоряющий первый запуск
  • LazyVStack и LazyHStack — встроенные компоненты SwiftUI для ленивых списков
  • Paging 3 — библиотека Android для постраничной загрузки данных из API и БД
  • Библиотеки изображений (Glide, Coil, Kingfisher) загружают изображения только при появлении на экране
  • Preloading — опережающая загрузка, обратная сторона Lazy Loading для плавного UX

Что такое Lazy Loading

Lazy Loading (ленивая загрузка, отложенная загрузка) — паттерн проектирования и оптимизации, при котором ресурсы приложения загружаются не при старте, а непосредственно перед использованием. В мобильной разработке Lazy Loading применяется к трём основным категориям: данные (пагинация списков), изображения (загрузка при скролле) и компоненты (ленивые стеки и вью).

Противоположность Lazy Loading — Eager Loading (жадная загрузка), когда все ресурсы загружаются при старте экрана. Eager Loading проще в реализации, но потребляет больше памяти и увеличивает время первого отображения. Для списков с тысячами элементов Eager Loading приводит к OOM (Out of Memory) на устройствах с ограниченной памятью. Lazy Loading решает эту проблему, загружая только то, что видно на экране, и подгружая остальное по мере скролла.

В контексте iOS и Android Lazy Loading реализован на разных уровнях. SwiftUI предоставляет LazyVStack и LazyHStack для ленивого рендеринга. UIKit использует UITableView с dequeueReusableCell. Android — RecyclerView с ViewHolder-пулом. На уровне данных — Room с Paging 3 и Core Data с NSFetchedResultsController. Выбор конкретной технологии зависит от стэка и требований к производительности.

Принципы работы ленивой загрузки

Центральный принцип Lazy Loading — загрузка ровно того объёма данных, который необходим для текущего состояния экрана, плюс опережающий буфер для плавного скролла. Этот подход базируется на двух механизмах: отслеживание видимости и виртуализация элементов.

Отслеживание видимости

Механизм отслеживания определяет, какие элементы находятся в видимой области экрана (viewport). На Android это делает LinearLayoutManager или GridLayoutManager через методы findFirstVisibleItemPosition и findLastVisibleItemPosition. В iOS UIScrollView предоставляет bounds.origin.y и contentOffset.height для расчёта видимой области. Когда элемент входит в viewport (или в prefetch buffer), запускается его загрузка. Когда элемент покидает экран, его ресурсы могут быть освобождены или перемещены в кэш.

kotlin
// Android — отслеживание видимости в RecyclerView
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        val layoutManager = recyclerView.layoutManager as LinearLayoutManager
        val lastVisible = layoutManager.findLastVisibleItemPosition()
        val totalCount = layoutManager.itemCount
        // Загружаем следующую страницу, если осталось < 5 элементов
        if (lastVisible >= totalCount - 5) {
            loadMoreItems()
        }
    }
})

Буферизация и prefetch

Prefetch buffer — опережающая загрузка элементов, которые скоро появятся на экране. RecyclerView поддерживает GapWorker.Prefetch через layoutManager.setItemPrefetchEnabled(true). iOS UITableView поддерживает prefetching через UITableViewDataSourcePrefetching. Размер prefetch buffer обычно составляет 1–2 экрана вперёд, что даёт компромисс между плавностью скролла и потреблением памяти. Слишком большой prefetch buffer сводит на нет преимущества Lazy Loading, слишком маленький — создаёт пустые места при быстром скролле.

Lazy Loading изображений

Изображения — самый тяжёлый тип ресурсов в мобильных приложениях. Одна фотография в 12 МП может занимать 3–5 МБ в несжатом виде. Lazy Loading изображений предотвращает загрузку сотен невидимых картинок в память, что было бы фатально для списков с подписями пользователей или каталогов товаров.

Библиотеки для Android: Glide и Coil

Glide — самая популярная библиотека загрузки изображений для Android с поддержкой кэширования, трансформаций и анимаций. Coil — более лёгкая альтернатива, написанная на Kotlin с использованием корутин. Обе библиотеки автоматически приостанавливают загрузку, когда ImageView покидает экран, и отменяют запросы при повторном использовании ViewHolder. Coil использует корутины и размером ~1.5 МБ против ~4 МБ у Glide, что делает его предпочтительным для проектов с фокусом на размер APK.

kotlin
// Coil — ленивая загрузка изображения
imageView.load("https://example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.placeholder)
    size(512, 512)
    memoryCachePolicy(CachePolicy.ENABLED)
}

Библиотеки для iOS: Kingfisher и SDWebImage

Kingfisher — библиотека для iOS с поддержкой Swift Concurrency, кэширования на диск и в память, а также prefetching для UICollectionView. SDWebImage — более старая библиотека с Objective-C корнями, но с поддержкой Swift. Обе библиотеки интегрируются с UIImageView и автоматически управляют жизненным циклом загрузки: отменяют запросы при переиспользовании ячейки, загружают изображения только когда ячейка видна и освобождают память при notification о нехватке памяти.

Lazy Loading данных и списков

Большие списки данных — основная сфера применения Lazy Loading в мобильных приложениях. Лента новостей, каталог товаров, чаты, история операций — любой экран с потенциально бесконечным списком требует пагинации и отложенной загрузки.

Paging 3 для Android

Paging 3 — библиотека из Android Jetpack, реализующая полный цикл ленивой загрузки: запрос данных из RemoteMediator (API + БД), кэширование в Room, постраничная выдача через PagingData и отображение через AsyncPagingDataAdapter. Paging 3 поддерживает три типа пагинации: Page-based (страницы), Item-based (offset/limit) и Key-based (ключи пагинации от API). Separator — встроенная поддержка разделителей между страницами для индикаторов загрузки.

kotlin
// Paging 3 — ленивая загрузка из API
class ArticlePagingSource(
    private val api: ArticleApi
) : PagingSource<Int, Article>() {

    override suspend fun load(
        params: LoadParams<Int>
    ): LoadResult<Int, Article> {
        return try {
            val page = params.key ?: 1
            val response = api.getArticles(page)
            LoadResult.Page(
                data = response.items,
                prevKey = page.takeIf { it > 1 }?.dec(),
                nextKey = page + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

SwiftUI LazyVStack и LazyHStack

LazyVStack — встроенный контейнер SwiftUI, который создаёт и рендерит элементы только при их появлении на экране. В отличие от VStack, который сразу вычисляет layout всех дочерних элементов, LazyVStack откладывает создание view до момента, когда элемент становится видимым или входит в prefetch range. LazyHStack — горизонтальный аналог для каруселей. Для списков большого объёма Apple рекомендует использовать List, который внутри работает аналогично LazyVStack с добавлением built-in recycling.

swift
// SwiftUI — LazyVStack с ленивой загрузкой
ScrollView {
    LazyVStack(spacing: 8) {
        ForEach(articles) { article in
            ArticleRow(article: article)
                .onAppear {
                    if article == articles.last {
                        viewModel.loadMore()
                    }
                }
        }
    }
}

Lazy Loading компонентов UI

Ленивая загрузка UI-компонентов — техника, при которой части интерфейса (хэдеры, футеры, секции настроек, вкладки) создаются не при старте экрана, а при первом обращении к ним. Это ускоряет initial render и снижает нагрузку на main thread.

Android: ViewStub и Fragment lazy loading

ViewStub — лёгкий View-заполнитель в Android, который не занимает места в layout и не создаёт дочерних View до вызова inflate(). Идеален для редко используемых секций: панель поиска, расширенные настройки, рекламные блоки. Fragment lazy loading — техника, при которой Fragment.onCreateView откладывается до момента, когда пользователь переключится на эту вкладку. Реализуется через isVisible или UserVisibleHint в ViewPager.

AndroidX предоставляет SplitInstallManager для ленивой загрузки модулей по требованию. Модули настройки, диагностики или дополнительных функций загружаются как Dynamic Feature модули только при первом запросе пользователя. Это уменьшает базовый размер приложения на 30–50% и одновременно реализует принцип Lazy Loading на уровне не только данных, но и кода.

iOS: TabView и ViewBuilder ленивость

TabView в SwiftUI загружает содержимое каждой вкладки лениво — только при активации таба. UIKit UITabBarController по умолчанию создаёт все дочерние контроллеры при старте, но это поведение можно изменить, не добавляя их в tabBarController.viewControllers сразу, а подставляя по мере переключения. UIStackView с arrangedSubviews, добавляемыми динамически, также следует принципу Lazy Loading — добавляйте Subview только когда пользователь выполняет действие, требующее этой части интерфейса.

Для оптимизации загрузки экрана в целом комбинируйте Lazy Loading на всех уровнях: ViewStub для редко используемых секций, Paging 3 для данных, Glide/Coil для изображений и ленивая инициализация ViewModel через Hilt/Dagger Scopes или Swinject. Такой подход даёт экран, который загружается за 200–400 мс даже на бюджетных устройствах с 3 ГБ оперативной памяти.

Часто задаваемые вопросы

В каких случаях не стоит использовать Lazy Loading?

Если экран гарантированно показывает мало элементов (до 20) и все они нужны сразу — Lazy Loading избыточен. Для списков, которые редко скроллятся, Eager Loading может быть проще и быстрее в реализации без заметных потерь производительности.

Как Lazy Loading влияет на память?

Снижает пиковое потребление памяти в 3–10 раз для больших списков, так как в памяти хранятся только видимые элементы плюс prefetch buffer. Однако добавление prefetch и кэширование изображений создаёт умеренный расход памяти, которым нужно управлять.

Что выбрать — LazyVStack или List в SwiftUI?

List предпочтительнее для однородных данных с возможностью свайпа, перетаскивания и built-in selection. LazyVStack — для кастомных layout с разными типами ячеек, секциями и нестандартными отступами. List внутри работает как LazyVStack с дополнительной функциональностью.

Как отладить проблемы с ленивой загрузкой?

В Android используйте Layout Inspector для просмотра иерархии View: при Lazy Loading большая часть элементов должна отсутствовать в дереве. В iOS — Xcode Debug View Hierarchy. Если при скролле все элементы присутствуют — Lazy Loading не работает.

Что важнее — скорость загрузки или экономия памяти?

Оба параметра важны, но приоритет зависит от платформы. На iOS с ARC и эффективным управлением памятью приоритет — скорость загрузки. На Android с JVM и GC — приоритет — экономия памяти, так как каждая аллокация в main thread может вызвать GC-фриз.

Итоги

  • Lazy Loading — паттерн отложенной загрузки, ускоряющий первый запуск и экономящий память
  • Изображения загружаются через Glide, Coil, Kingfisher только при попадании в viewport
  • Paging 3 для Android обеспечивает постраничную загрузку данных с кэшированием в Room
  • LazyVStack в SwiftUI откладывает рендеринг элементов до их появления на экране
  • ViewStub в Android — ленивая загрузка UI-компонентов при первом обращении
  • Prefetch buffer — опережающая загрузка для плавного скролла
  • Комбинируйте Lazy Loading на всех уровнях: данные + изображения + UI

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также