Lazy Loading — στρατηγική καθυστερημένης φόρτωσης δεδομένων, εικόνων και στοιχείων, κατά την οποία οι πόροι ζητούνται όχι κατά την εκκίνηση της εφαρμογής, αλλά τη στιγμή που ο χρήστης πραγματικά τους χρειάζεται. Σύμφωνα με τον Android Paging 3 Guide, η τεμπέλικη φόρτωση λιστών μειώνει την κατανάλωση μνήμης κατά 60–80% κατά την εργασία με μεγάλα σύνολα δεδομένων. Καθυστερημένη αρχικοποίηση — η βασική αρχή που βρίσκεται πίσω από όλες τις υλοποιήσεις 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. Η επιλογή συγκεκριμένης τεχνολογίας εξαρτάται από το stack και τις απαιτήσεις απόδοσης.
Η κεντρική αρχή του Lazy Loading — φόρτωση ακριβώς του όγκου δεδομένων που απαιτείται για την τρέχουσα κατάσταση της οθόνης, συν ένα προληπτικό buffer για ομαλή κύλιση. Αυτή η προσέγγιση βασίζεται σε δύο μηχανισμούς: παρακολούθηση ορατότητας και εικονικοποίηση στοιχείων.
Ο μηχανισμός παρακολούθησης καθορίζει ποια στοιχεία βρίσκονται στην ορατή περιοχή της οθόνης (viewport). Στο Android, αυτό γίνεται από το LinearLayoutManager ή GridLayoutManager μέσω των μεθόδων findFirstVisibleItemPosition και findLastVisibleItemPosition. Στο iOS, το UIScrollView παρέχει bounds.origin.y και contentOffset.height για τον υπολογισμό της ορατής περιοχής. Όταν ένα στοιχείο εισέρχεται στο viewport (ή στο prefetch buffer), ξεκινά η φόρτωσή του. Όταν το στοιχείο εγκαταλείπει την οθόνη, οι πόροι του μπορούν να απελευθερωθούν ή να μεταφερθούν στην προσωρινή μνήμη.
// 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 στοιχεία < 5 στοιχεία
if (lastVisible >= totalCount - 5) {
loadMoreItems()
}
}
})
Prefetch buffer — προληπτική φόρτωση στοιχείων που θα εμφανιστούν σύντομα στην οθόνη. Το RecyclerView υποστηρίζει GapWorker.Prefetch μέσω layoutManager.setItemPrefetchEnabled(true). Το iOS UITableView υποστηρίζει prefetching μέσω UITableViewDataSourcePrefetching. Το μέγεθος του prefetch buffer είναι συνήθως 1–2 οθόνες μπροστά, παρέχοντας συμβιβασμό μεταξύ ομαλότητας κύλισης και κατανάλωσης μνήμης. Πολύ μεγάλο prefetch buffer ακυρώνει τα πλεονεκτήματα του Lazy Loading, πολύ μικρό — δημιουργεί κενά σημεία σε γρήγορη κύλιση.
Εικόνες — ο βαρύτερος τύπος πόρων σε εφαρμογές κινητών. Μία φωτογραφία 12 MP μπορεί να καταλαμβάνει 3–5 MB σε μη συμπιεσμένη μορφή. Το Lazy Loading εικόνων αποτρέπει τη φόρτωση εκατοντάδων αόρατων εικόνων στη μνήμη, που θα ήταν καταστροφικό για λίστες σχολίων χρηστών ή καταλόγους προϊόντων.
Glide — η πιο δημοφιλής βιβλιοθήκη φόρτωσης εικόνων για Android με υποστήριξη προσωρινής αποθήκευσης, μετασχηματισμών και κινούμενων εικόνων. Coil — μια ελαφρύτερη εναλλακτική γραμμένη σε Kotlin χρησιμοποιώντας coroutines. Και οι δύο βιβλιοθήκες σταματούν αυτόματα τη φόρτωση όταν το ImageView εγκαταλείπει την οθόνη και ακυρώνουν αιτήματα κατά την επαναχρησιμοποίηση του ViewHolder. Το Coil χρησιμοποιεί coroutines και έχει μέγεθος ~1.5 MB έναντι ~4 MB του Glide, καθιστώντας το προτιμότερο για έργα που εστιάζουν στο μέγεθος APK.
// Coil — τεμπέλικη φόρτωση εικόνας
imageView.load("https://example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
size(512, 512)
memoryCachePolicy(CachePolicy.ENABLED)
}
Kingfisher — βιβλιοθήκη για iOS με υποστήριξη Swift Concurrency, προσωρινής αποθήκευσης σε δίσκο και μνήμη, καθώς και prefetching για UICollectionView. SDWebImage — παλαιότερη βιβλιοθήκη με ρίζες Objective-C αλλά με υποστήριξη Swift. Και οι δύο βιβλιοθήκες ενσωματώνονται με UIImageView και διαχειρίζονται αυτόματα τον κύκλο ζωής φόρτωσης: ακυρώνουν αιτήματα κατά την επαναχρησιμοποίηση κελιού, φορτώνουν εικόνες μόνο όταν το κελί είναι ορατό και απελευθερώνουν μνήμη κατά ειδοποίηση έλλειψης μνήμης.
Μεγάλες λίστες δεδομένων — ο κύριος τομέας εφαρμογής του Lazy Loading σε εφαρμογές κινητών. Ροή ειδήσεων, κατάλογος προϊόντων, συνομιλίες, ιστορικό συναλλαγών — κάθε οθόνη με δυνητικά ατελείωτη λίστα απαιτεί σελιδοποίηση και καθυστερημένη φόρτωση.
Paging 3 — βιβλιοθήκη από το Android Jetpack που υλοποιεί τον πλήρη κύκλο τεμπέλικης φόρτωσης: αίτημα δεδομένων από RemoteMediator (API + βάση δεδομένων), προσωρινή αποθήκευση στο Room, παράδοση ανά σελίδα μέσω PagingData και εμφάνιση μέσω AsyncPagingDataAdapter. Το Paging 3 υποστηρίζει τρεις τύπους σελιδοποίησης: Page-based (σελίδες), Item-based (offset/limit) και Key-based (κλειδιά σελιδοποίησης από API). Separator — ενσωματωμένη υποστήριξη διαχωριστικών μεταξύ σελίδων για δείκτες φόρτωσης.
// 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)
}
}
}
LazyVStack — ενσωματωμένο κοντέινερ SwiftUI που δημιουργεί και αποδίδει στοιχεία μόνο όταν εμφανίζονται στην οθόνη. Σε αντίθεση με το VStack, το οποίο υπολογίζει αμέσως τη διάταξη όλων των θυγατρικών στοιχείων, το LazyVStack αναβάλλει τη δημιουργία προβολής έως ότου το στοιχείο γίνει ορατό ή εισέλθει στο εύρος prefetch. LazyHStack — το οριζόντιο αντίστοιχο για καρουζέλ. Για λίστες μεγάλου όγκου, η Apple συνιστά τη χρήση List, η οποία εσωτερικά λειτουργεί παρόμοια με το LazyVStack με πρόσθετη ενσωματωμένη ανακύκλωση.
// SwiftUI — LazyVStack με τεμπέλικη φόρτωση
ScrollView {
LazyVStack(spacing: 8) {
ForEach(articles) { article in
ArticleRow(article: article)
.onAppear {
if article == articles.last {
viewModel.loadMore()
}
}
}
}
}
Τεμπέλικη φόρτωση στοιχείων UI — τεχνική κατά την οποία τμήματα της διεπαφής (κεφαλίδες, υποσέλιδα, ενότητες ρυθμίσεων, καρτέλες) δημιουργούνται όχι κατά την εκκίνηση της οθόνης, αλλά κατά την πρώτη πρόσβαση σε αυτά. Αυτό επιταχύνει την αρχική απόδοση και μειώνει το φορτίο στο main thread.
ViewStub — ένα ελαφρύ στοιχείο πλαισίου View στο Android που δεν καταλαμβάνει χώρο στη διάταξη και δεν δημιουργεί θυγατρικά View μέχρι την κλήση inflate(). Ιδανικό για σπάνια χρησιμοποιούμενες ενότητες: πίνακας αναζήτησης, προηγμένες ρυθμίσεις, διαφημιστικά μπλοκ. Τεμπέλικη φόρτωση Fragment — τεχνική κατά την οποία το Fragment.onCreateView αναβάλλεται έως ότου ο χρήστης μεταβεί σε εκείνη την καρτέλα. Υλοποιείται μέσω isVisible ή UserVisibleHint στο ViewPager.
Το AndroidX παρέχει SplitInstallManager για τεμπέλικη φόρτωση λειτουργικών μονάδων κατόπιν ζήτησης. Οι λειτουργικές μονάδες ρυθμίσεων, διαγνωστικών ή πρόσθετων λειτουργιών φορτώνονται ως Dynamic Feature λειτουργικές μονάδες μόνο στο πρώτο αίτημα του χρήστη. Αυτό μειώνει το βασικό μέγεθος της εφαρμογής κατά 30–50% και ταυτόχρονα υλοποιεί την αρχή του Lazy Loading όχι μόνο σε επίπεδο δεδομένων, αλλά και κώδικα.
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 ms ακόμη και σε οικονομικές συσκευές με 3 GB RAM.
Συχνές ερωτήσεις
Εάν η οθόνη εγγυημένα εμφανίζει λίγα στοιχεία (έως 20) και όλα χρειάζονται άμεσα — το Lazy Loading είναι περιττό. Για λίστες που σπάνια κυλίονται, το Eager Loading μπορεί να είναι απλούστερο και ταχύτερο στην υλοποίηση χωρίς αισθητή απώλεια απόδοσης.
Μειώνει τη μέγιστη κατανάλωση μνήμης 3–10 φορές για μεγάλες λίστες, καθώς στη μνήμη αποθηκεύονται μόνο τα ορατά στοιχεία συν το prefetch buffer. Ωστόσο, η προσθήκη prefetch και η προσωρινή αποθήκευση εικόνων δημιουργεί μέτρια κατανάλωση μνήμης που πρέπει να διαχειρίζεται.
List προτιμάται για ομοιογενή δεδομένα με δυνατότητα σάρωσης, μεταφοράς και ενσωματωμένης επιλογής. LazyVStack — για προσαρμοσμένες διατάξεις με διαφορετικούς τύπους κελιών, ενότητες και μη τυπικά διαστήματα. Το List εσωτερικά λειτουργεί όπως το LazyVStack με πρόσθετη λειτουργικότητα.
Στο Android χρησιμοποιήστε το Layout Inspector για προβολή της ιεραρχίας View: στο Lazy Loading τα περισσότερα στοιχεία θα πρέπει να απουσιάζουν από το δέντρο. Στο iOS — Xcode Debug View Hierarchy. Εάν κατά την κύλιση όλα τα στοιχεία είναι παρόντα — το Lazy Loading δεν λειτουργεί.
Και οι δύο παράμετροι είναι σημαντικές, αλλά η προτεραιότητα εξαρτάται από την πλατφόρμα. Στο iOS με ARC και αποτελεσματική διαχείριση μνήμης, προτεραιότητα είναι η ταχύτητα φόρτωσης. Στο Android με JVM και GC, προτεραιότητα είναι η εξοικονόμηση μνήμης, καθώς κάθε εκχώρηση στο main thread μπορεί να προκαλέσει πάγωμα GC.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης