Lazy Loading — az adatok, képek és komponensek késleltetett betöltésének stratégiája, amely során az erőforrások nem az alkalmazás indításakor, hanem akkor kerülnek lekérésre, amikor a felhasználónak valóban szüksége van rájuk. A Android Paging 3 Guide szerint a listák lusta betöltése 60–80%-kal csökkenti a memóriafogyasztást nagy adathalmazokkal való munka során. Késleltetett inicializálás — az alapelv, amely az összes Lazy Loading implementáció mögött áll.
Főbb pontok
Lazy Loading (lusta betöltés, késleltetett betöltés) — tervezési és optimalizálási minta, amelyben az alkalmazás erőforrásai nem induláskor, hanem közvetlenül használat előtt töltődnek be. A mobilfejlesztésben a Lazy Loading három fő kategóriára alkalmazható: adatok (listák lapozása), képek (betöltés görgetés közben) és komponensek (lusta stackek és nézetek).
A Lazy Loading ellentéte az Eager Loading ( mohó betöltés), amikor az összes erőforrás a képernyő indításakor betöltődik. Az Eager Loading implementációja egyszerűbb, de több memóriát fogyaszt és növeli az első megjelenítés idejét. Több ezer elemet tartalmazó listák esetén az Eager Loading OOM-hoz (Out of Memory) vezet a korlátozott memóriájú eszközökön. A Lazy Loading megoldja ezt a problémát azáltal, hogy csak azt tölti be, ami látható a képernyőn, a többit pedig görgetés közben adja hozzá.
iOS és Android kontextusában a Lazy Loading különböző szinteken van implementálva. A SwiftUI LazyVStack-et és LazyHStack-et biztosít a lusta rendereléshez. Az UIKit UITableView-et használ a dequeueReusableCell-lel. Android — RecyclerView-t ViewHolder pool-lal. Adat szinten — Room Paging 3-mal és Core Data NSFetchedResultsController-rel. A konkrét technológia kiválasztása a stacktől és a teljesítménykövetelményektől függ.
A Lazy Loading központi elve — pontosan annyi adat betöltése, amennyi a képernyő aktuális állapotához szükséges, plusz egy előre betöltő puffer a sima görgetéshez. Ez a megközelítés két mechanizmuson alapul: láthatóság követése és elemek virtualizációja.
A követési mechanizmus meghatározza, hogy mely elemek találhatók a képernyő látható területén (viewport). Androidon ezt a LinearLayoutManager vagy GridLayoutManager végzi a findFirstVisibleItemPosition és findLastVisibleItemPosition metódusokon keresztül. iOS-ben az UIScrollView biztosítja a bounds.origin.y és contentOffset.height értékeket a látható terület kiszámításához. Amikor egy elem belép a viewportba (vagy a prefetch pufferbe), elindul a betöltése. Amikor az elem elhagyja a képernyőt, erőforrásai felszabadíthatók vagy áthelyezhetők a gyorsítótárba.
// Android — láthatóság követése a RecyclerView-ban
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
// Betöltjük a következő oldalt, ha kevesebb mint 5 elem maradt < 5 elem
if (lastVisible >= totalCount - 5) {
loadMoreItems()
}
}
})
Prefetch puffer — azon elemek előre betöltése, amelyek hamarosan megjelennek a képernyőn. A RecyclerView támogatja a GapWorker.Prefetch-et a layoutManager.setItemPrefetchEnabled(true) segítségével. Az iOS UITableView támogatja a prefetching-et az UITableViewDataSourcePrefetching segítségével. A prefetch puffer mérete általában 1–2 képernyő előre, ami kompromisszumot biztosít a görgetés simasága és a memóriafogyasztás között. Túl nagy prefetch puffer semmissé teszi a Lazy Loading előnyeit, túl kicsi — üres helyeket hoz létre gyors görgetéskor.
Képek — a legnehezebb erőforrástípus a mobil alkalmazásokban. Egy 12 MP-es fénykép 3–5 MB-ot foglalhat tömörítetlen formában. A képek Lazy Loading-je megakadályozza több száz láthatatlan kép memóriába töltését, ami végzetes lenne a felhasználói megjegyzések listái vagy termékkatalógusok esetén.
Glide — a legnépszerűbb képbetöltő könyvtár Androidhoz, gyorsítótár, transzformációk és animációk támogatásával. Coil — egy könnyebb alternatív a Kotlinban, korutinekkel írva. Mindkét könyvtár automatikusan leállítja a betöltést, amikor az ImageView elhagyja a képernyőt, és törli a kéréseket a ViewHolder újrahasználatakor. A Coil korutineket használ, és ~1.5 MB méretű a Glide ~4 MB-jával szemben, ami preferált választássá teszi az APK méretére fókuszáló projektek számára.
// Coil — kép lusta betöltése
imageView.load("https://example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
size(512, 512)
memoryCachePolicy(CachePolicy.ENABLED)
}
Kingfisher — iOS könyvtár Swift Concurrency, lemezre és memóriába történő gyorsítótárazás, valamint UICollectionView prefetching támogatással. SDWebImage — egy régebbi könyvtár Objective-C gyökerekkel, de Swift támogatással. Mindkét könyvtár integrálódik az UIImageView-val és automatikusan kezeli a betöltés életciklusát: törli a kéréseket a cella újrahasználatakor, csak akkor tölti be a képeket, amikor a cella látható, és felszabadítja a memóriát memóriahiányról szóló értesítés esetén.
Nagy adatlisták — a Lazy Loading fő alkalmazási területe a mobil alkalmazásokban. Hírfolyam, termékkatalógus, csevegések, művelettörténet — minden potenciálisan végtelen listával rendelkező képernyő lapozást és késleltetett betöltést igényel.
Paging 3 — az Android Jetpack könyvtára, amely a lusta betöltés teljes ciklusát implementálja: adatok lekérése a RemoteMediator-ból (API + adatbázis), gyorsítótárazás Room-ban, oldalankénti kiadás PagingData-n keresztül és megjelenítés AsyncPagingDataAdapter segítségével. A Paging 3 háromféle lapozást támogat: Page-based (oldalak), Item-based (offset/limit) és Key-based (lapozási kulcsok az API-ból). Separator — beépített támogatás az oldalak közötti elválasztókhoz a betöltési jelzőkhöz.
// Paging 3 — lusta betöltés API-ból
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 — beépített SwiftUI konténer, amely csak akkor hozza létre és rendereli az elemeket, amikor megjelennek a képernyőn. Ellentétben a VStack-kel, amely azonnal kiszámítja az összes gyermekelem elrendezését, a LazyVStack elhalasztja a nézet létrehozását addig, amíg az elem láthatóvá nem válik vagy belép a prefetch tartományba. LazyHStack — a vízszintes megfelelője karusszelekhez. Nagy volumenű listákhoz az Apple a List használatát javasolja, amely belsőleg hasonlóan működik, mint a LazyVStack, további beépített újrahasznosítással.
// SwiftUI — LazyVStack lusta betöltéssel
ScrollView {
LazyVStack(spacing: 8) {
ForEach(articles) { article in
ArticleRow(article: article)
.onAppear {
if article == articles.last {
viewModel.loadMore()
}
}
}
}
}
UI komponensek lusta betöltése — technika, amelyben a felület részei (fejlécek, láblécek, beállítási szakaszok, lapok) nem a képernyő indításakor jönnek létre, hanem az első hozzáféréskor. Ez felgyorsítja a kezdeti renderelést és csökkenti a main thread terhelését.
ViewStub — egy könnyű View helykitöltő Androidban, amely nem foglal helyet az elrendezésben és nem hoz létre gyermek View-kat az inflate() hívásáig. Ideális ritkán használt szakaszokhoz: keresőpanel, haladó beállítások, hirdetési blokkok. Fragment lusta betöltése — technika, amelyben a Fragment.onCreateView addig van elhalasztva, amíg a felhasználó át nem vált arra a lapra. A ViewPager-ben isVisible vagy UserVisibleHint segítségével implementálható.
AndroidX SplitInstallManager-t biztosít a modulok igény szerinti lusta betöltéséhez. A beállítási, diagnosztikai vagy kiegészítő funkciók moduljai Dynamic Feature modulokként csak a felhasználó első kérésére töltődnek be. Ez 30–50%-kal csökkenti az alkalmazás alapméretét, és egyidejűleg implementálja a Lazy Loading elvét nemcsak adat, hanem kód szinten is.
TabView a SwiftUI-ban lustán tölti be az egyes lapok tartalmát — csak a lap aktiválásakor. Az UIKit UITabBarController alapértelmezés szerint az összes gyermekvezérlőt létrehozza induláskor, de ez a viselkedés megváltoztatható azáltal, hogy nem adjuk hozzá őket azonnal a tabBarController.viewControllers-hoz, hanem váltáskor adjuk hozzá. A dinamikusan hozzáadott arrangedSubviews-al rendelkező UIStackView is követi a Lazy Loading elvét — csak akkor adjon hozzá Subview-t, amikor a felhasználó olyan műveletet hajt végre, amely megköveteli a felület azon részét.
A képernyő betöltésének optimalizálásához kombinálja a Lazy Loading-et minden szinten: ViewStub a ritkán használt szakaszokhoz, Paging 3 az adatokhoz, Glide/Coil a képekhez és a ViewModel lusta inicializálása Hilt/Dagger Scopes vagy Swinject segítségével. Egy ilyen megközelítés olyan képernyőt eredményez, amely 200–400 ms alatt betöltődik, még a 3 GB RAM-mal rendelkező olcsó eszközökön is.
Gyakran ismételt kérdések
Ha a képernyő garantáltan kevés elemet (legfeljebb 20) mutat, és mindegyikre azonnal szükség van — a Lazy Loading felesleges. Ritkán görgetett listák esetén az Eager Loading egyszerűbb és gyorsabb lehet az implementációban anélkül, hogy észrevehető teljesítményveszteséget okozna.
3–10-szeresére csökkenti a csúcsmemória-fogyasztást nagy listák esetén, mivel a memóriában csak a látható elemek és a prefetch puffer tárolódnak. A prefetch és a képek gyorsítótárazásának hozzáadása azonban mérsékelt memóriafogyasztást hoz létre, amelyet kezelni kell.
List előnyösebb homogén adatokhoz, csúsztatási, húzási és beépített kiválasztási lehetőséggel. LazyVStack — egyéni elrendezésekhez különböző cellatípusokkal, szakaszokkal és nem szabványos térközökkel. A List belsőleg hasonlóan működik, mint a LazyVStack, további funkciókkal.
Androidban használja a Layout Inspector-t a View hierarchia megtekintéséhez: Lazy Loading esetén az elemek többségének hiányoznia kell a fából. iOS-ben — Xcode Debug View Hierarchy. Ha görgetéskor az összes elem jelen van — a Lazy Loading nem működik.
Mindkét paraméter fontos, de a prioritás a platformtól függ. iOS-en ARC-kel és hatékony memóriakezeléssel a betöltési sebesség az elsődleges. Androidon JVM-mel és GC-vel a memóriatakarékosság az elsődleges, mivel minden allokáció a main thread-ben GC-fagyást okozhat.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is