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 та автоматично керують життєвим циклом завантаження: скасовують запити при перевикористанні комірки, завантажують зображення лише коли комірка видима та звільняють пам'ять при повідомленні про нестачу пам'яті.

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 з додатковим вбудованим 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 краще підходить для однорідних даних із можливістю свайпу, перетягування та вбудованого вибору. 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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