Lazy Loading — strategie odloženého načítání dat, obrázků a komponent, při které jsou zdroje vyžadovány nikoli při spuštění aplikace, ale ve chvíli, kdy je uživatel skutečně potřebuje. Podle Android Paging 3 Guide snižuje líné načítání seznamů spotřebu paměti o 60–80% při práci s velkými datovými sadami. Odložená inicializace — klíčový princip, který stojí za všemi implementacemi Lazy Loading.
Hlavní body
Lazy Loading (líné načítání, odložené načítání) — vzor návrhu a optimalizace, při kterém jsou zdroje aplikace načítány nikoli při spuštění, ale bezprostředně před použitím. V mobilním vývoji se Lazy Loading aplikuje na tři hlavní kategorie: data (stránkování seznamů), obrázky (načítání při rolování) a komponenty (líné zásobníky a pohledy).
Opakem Lazy Loading je Eager Loading (chtivé načítání), kdy jsou všechny zdroje načteny při spuštění obrazovky. Eager Loading je jednodušší na implementaci, ale spotřebovává více paměti a prodlužuje čas prvního zobrazení. U seznamů s tisíci prvků vede Eager Loading k OOM (Out of Memory) na zařízeních s omezenou pamětí. Lazy Loading řeší tento problém načítáním pouze toho, co je vidět na obrazovce, a přidáváním zbytku během rolování.
V kontextu iOS a Android je Lazy Loading implementován na různých úrovních. SwiftUI poskytuje LazyVStack a LazyHStack pro líné vykreslování. UIKit používá UITableView s dequeueReusableCell. Android — RecyclerView s fondem ViewHolder. Na úrovni dat — Room s Paging 3 a Core Data s NSFetchedResultsController. Výběr konkrétní technologie závisí na stacku a požadavcích na výkon.
Centrální princip Lazy Loading — načítání přesně takového objemu dat, který je nezbytný pro aktuální stav obrazovky, plus předběžný buffer pro plynulé rolování. Tento přístup je založen na dvou mechanismech: sledování viditelnosti a virtualizace prvků.
Mechanismus sledování určuje, které prvky se nacházejí ve viditelné oblasti obrazovky (viewport). Na Androidu to provádí LinearLayoutManager nebo GridLayoutManager pomocí metod findFirstVisibleItemPosition a findLastVisibleItemPosition. V iOS poskytuje UIScrollView bounds.origin.y a contentOffset.height pro výpočet viditelné oblasti. Když prvek vstoupí do viewportu (nebo do prefetch bufferu), spustí se jeho načítání. Když prvek opustí obrazovku, jeho zdroje mohou být uvolněny nebo přesunuty do mezipaměti.
// Android — sledování viditelnosti v 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
// Načítáme další stránku, pokud zbývá méně než 5 prvků < 5 prvků
if (lastVisible >= totalCount - 5) {
loadMoreItems()
}
}
})
Prefetch buffer — předběžné načítání prvků, které se brzy objeví na obrazovce. RecyclerView podporuje GapWorker.Prefetch prostřednictvím layoutManager.setItemPrefetchEnabled(true). iOS UITableView podporuje prefetching prostřednictvím UITableViewDataSourcePrefetching. Velikost prefetch bufferu je obvykle 1–2 obrazovky dopředu, což poskytuje kompromis mezi plynulostí rolování a spotřebou paměti. Příliš velký prefetch buffer ruší výhody Lazy Loading, příliš malý — vytváří prázdná místa při rychlém rolování.
Obrázky — nejtěžší typ zdrojů v mobilních aplikacích. Jedna fotografie o rozlišení 12 MP může zabírat 3–5 MB v nekomprimované podobě. Lazy Loading obrázků zabraňuje načítání stovek neviditelných obrázků do paměti, což by bylo fatální pro seznamy uživatelských komentářů nebo katalogy produktů.
Glide — nejoblíbenější knihovna pro načítání obrázků pro Android s podporou ukládání do mezipaměti, transformací a animací. Coil — lehčí alternativa napsaná v Kotlinu s využitím korutin. Obě knihovny automaticky zastavují načítání, když ImageView opustí obrazovku, a ruší požadavky při opětovném použití ViewHolder. Coil používá korutiny a má velikost ~1.5 MB oproti ~4 MB u Glide, což jej činí preferovaným pro projekty zaměřené na velikost APK.
// Coil — líné načítání obrázku
imageView.load("https://example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
size(512, 512)
memoryCachePolicy(CachePolicy.ENABLED)
}
Kingfisher — knihovna pro iOS s podporou Swift Concurrency, ukládání do mezipaměti na disk a do paměti a také prefetching pro UICollectionView. SDWebImage — starší knihovna s kořeny v Objective-C, ale s podporou Swift. Obě knihovny se integrují s UIImageView a automaticky spravují životní cyklus načítání: ruší požadavky při opětovném použití buňky, načítají obrázky pouze když je buňka viditelná a uvolňují paměť při oznámení o nedostatku paměti.
Velké seznamy dat — hlavní oblast použití Lazy Loading v mobilních aplikacích. Zpravodajský kanál, katalog produktů, chaty, historie transakcí — každá obrazovka s potenciálně nekonečným seznamem vyžaduje stránkování a odložené načítání.
Paging 3 — knihovna z Android Jetpack, která implementuje celý cyklus líného načítání: vyžádání dat z RemoteMediator (API + databáze), ukládání do mezipaměti v Room, stránkové doručování prostřednictvím PagingData a zobrazení prostřednictvím AsyncPagingDataAdapter. Paging 3 podporuje tři typy stránkování: Page-based (stránky), Item-based (offset/limit) a Key-based (klíče stránkování z API). Separator — vestavěná podpora oddělovačů mezi stránkami pro indikátory načítání.
// Paging 3 — líné načítání z 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)
}
}
}
LazyVStack — vestavěný kontejner SwiftUI, který vytváří a vykresluje prvky pouze při jejich zobrazení na obrazovce. Na rozdíl od VStack, který okamžitě vypočítá rozvržení všech podřízených prvků, LazyVStack odkládá vytvoření pohledu až do okamžiku, kdy se prvek stane viditelným nebo vstoupí do rozsahu prefetch. LazyHStack — horizontální obdoba pro karusely. U seznamů velkého objemu Apple doporučuje používat List, který interně funguje podobně jako LazyVStack s dodatečným vestavěným recyklováním.
// SwiftUI — LazyVStack s líným načítáním
ScrollView {
LazyVStack(spacing: 8) {
ForEach(articles) { article in
ArticleRow(article: article)
.onAppear {
if article == articles.last {
viewModel.loadMore()
}
}
}
}
}
Líné načítání komponent UI — technika, při které se části rozhraní (záhlaví, zápatí, sekce nastavení, karty) vytvářejí nikoli při spuštění obrazovky, ale při prvním přístupu k nim. To zrychluje počáteční vykreslení a snižuje zatížení hlavního vlákna.
ViewStub — lehký zástupný prvek View v Androidu, který nezabírá místo v rozvržení a nevytváří podřízené View až do volání inflate(). Ideální pro zřídka používané sekce: vyhledávací panel, rozšířená nastavení, reklamní bloky. Líné načítání Fragmentu — technika, při které je Fragment.onCreateView odložen až do okamžiku, kdy uživatel přepne na tuto kartu. Implementuje se pomocí isVisible nebo UserVisibleHint v ViewPager.
AndroidX poskytuje SplitInstallManager pro líné načítání modulů na vyžádání. Moduly nastavení, diagnostiky nebo doplňkových funkcí se načítají jako moduly Dynamic Feature až při prvním požadavku uživatele. To snižuje základní velikost aplikace o 30–50% a zároveň implementuje princip Lazy Loading nejen na úrovni dat, ale i kódu.
TabView ve SwiftUI načítá obsah každé karty líně — pouze při aktivaci karty. UIKit UITabBarController ve výchozím nastavení vytváří všechny podřízené kontrolery při spuštění, ale toto chování lze změnit tím, že je nepřidáte okamžitě do tabBarController.viewControllers, ale přidáte je při přepínání. UIStackView s dynamicky přidávanými arrangedSubviews také následuje princip Lazy Loading — přidávejte Subview pouze tehdy, když uživatel provádí akci vyžadující tuto část rozhraní.
Pro optimalizaci načítání obrazovky jako celku kombinujte Lazy Loading na všech úrovních: ViewStub pro zřídka používané sekce, Paging 3 pro data, Glide/Coil pro obrázky a líná inicializace ViewModel přes Hilt/Dagger Scopes nebo Swinject. Takový přístup poskytuje obrazovku, která se načítá za 200–400 ms i na levných zařízeních s 3 GB RAM.
Často kladené otázky
Pokud obrazovka garantovaně zobrazuje málo prvků (do 20) a všechny jsou potřeba okamžitě — Lazy Loading je zbytečný. U seznamů, které se zřídka rolují, může být Eager Loading jednodušší a rychlejší na implementaci bez znatelné ztráty výkonu.
Snižuje špičkovou spotřebu paměti 3–10krát u velkých seznamů, protože v paměti jsou uloženy pouze viditelné prvky plus prefetch buffer. Přidání prefetch a ukládání obrázků do mezipaměti však vytváří mírný nárůst spotřeby paměti, který je třeba řídit.
List je vhodnější pro homogenní data s možností posouvání, přetahování a vestavěného výběru. LazyVStack — pro vlastní rozvržení s různými typy buněk, sekcemi a nestandardními mezerami. List interně funguje jako LazyVStack s dodatečnou funkcionalitou.
V Androidu použijte Layout Inspector pro prohlížení hierarchie View: při Lazy Loading by většina prvků měla ve stromu chybět. V iOS — Xcode Debug View Hierarchy. Pokud jsou při rolování všechny prvky přítomny — Lazy Loading nefunguje.
Oba parametry jsou důležité, ale priorita závisí na platformě. Na iOS s ARC a efektivní správou paměti je prioritou rychlost načítání. Na Androidu s JVM a GC je prioritou úspora paměti, protože každá alokace v hlavním vlákně může způsobit zamrznutí GC.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také