Lazy Loading nello sviluppo mobile — come funziona, principi e implementazione

Autore: IT Sectr Pubblicato: 2026-04-01 Tempo di lettura: 9 min

Il Lazy Loading è una strategia di caricamento differito di dati, immagini e componenti, in cui le risorse non vengono richieste all'avvio dell'app, ma nel momento in cui l'utente ne ha effettivamente bisogno. Secondo la Guida Android Paging 3, il caricamento differito delle liste riduce il consumo di memoria del 60–80% quando si lavora con grandi insiemi di dati. L'inizializzazione differita è il principio chiave alla base di tutte le implementazioni di Lazy Loading.

Punti chiave

  • Lazy Loading è un pattern di caricamento differito che risparmia memoria e accelera il primo avvio
  • LazyVStack e LazyHStack sono componenti integrati di SwiftUI per liste differite
  • Paging 3 è una libreria Android per il caricamento paginato di dati da API e database
  • Le librerie di immagini (Glide, Coil, Kingfisher) caricano le immagini solo quando appaiono sullo schermo
  • Il precaricamento è il caricamento anticipato, il lato opposto del Lazy Loading per un'UX fluida

Cos'è il Lazy Loading

Il Lazy Loading (caricamento differito o pigro) è un pattern di progettazione e ottimizzazione in cui le risorse dell'applicazione non vengono caricate all'avvio, ma immediatamente prima dell'uso. Nello sviluppo mobile, il Lazy Loading si applica a tre categorie principali: dati (paginazione delle liste), immagini (caricamento durante lo scroll) e componenti (stack e viste differiti).

L'opposto del Lazy Loading è l'Eager Loading (caricamento immediato), dove tutte le risorse vengono caricate all'avvio dello schermo. L'Eager Loading è più semplice da implementare ma consuma più memoria e aumenta il tempo di prima visualizzazione. Per liste con migliaia di elementi, l'Eager Loading porta a OOM (Out of Memory) su dispositivi con memoria limitata. Il Lazy Loading risolve questo problema caricando solo ciò che è visibile sullo schermo e caricando il resto man mano che l'utente scorre.

Nel contesto di iOS e Android, il Lazy Loading è implementato a diversi livelli. SwiftUI fornisce LazyVStack e LazyHStack per il rendering differito. UIKit utilizza UITableView con dequeueReusableCell. Android utilizza RecyclerView con un pool di ViewHolder. A livello di dati, Room con Paging 3 e Core Data con NSFetchedResultsController. La scelta della tecnologia specifica dipende dallo stack e dai requisiti di prestazioni.

Principi di funzionamento del caricamento differito

Il principio centrale del Lazy Loading è caricare esattamente la quantità di dati necessaria per lo stato corrente dello schermo, più un buffer anticipato per uno scorrimento fluido. Questo approccio si basa su due meccanismi: tracciamento della visibilità e virtualizzazione degli elementi.

Tracciamento della visibilità

Il meccanismo di tracciamento determina quali elementi si trovano nell'area visibile dello schermo (viewport). Su Android, lo fanno LinearLayoutManager o GridLayoutManager attraverso i metodi findFirstVisibleItemPosition e findLastVisibleItemPosition. In iOS, UIScrollView fornisce bounds.origin.y e contentOffset.height per calcolare l'area visibile. Quando un elemento entra nel viewport (o nel buffer di precaricamento), viene avviato il suo caricamento. Quando un elemento esce dallo schermo, le sue risorse possono essere liberate o spostate nella cache.

kotlin
// Android — tracciamento della visibilità in 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
        // Caricamento della pagina successiva se ne rimangono < 5 elementi
        if (lastVisible >= totalCount - 5) {
            loadMoreItems()
        }
    }
})

Buffer e precaricamento

Il buffer di precaricamento è il caricamento anticipato degli elementi che appariranno presto sullo schermo. RecyclerView supporta GapWorker.Prefetch tramite layoutManager.setItemPrefetchEnabled(true). iOS UITableView supporta il precaricamento tramite UITableViewDataSourcePrefetching. La dimensione del buffer di precaricamento è tipicamente di 1–2 schermi in anticipo, offrendo un compromesso tra fluidità dello scroll e consumo di memoria. Un buffer di precaricamento troppo grande annulla i vantaggi del Lazy Loading; uno troppo piccolo crea spazi vuoti durante lo scorrimento veloce.

Lazy Loading delle immagini

Le immagini sono il tipo di risorsa più pesante nelle applicazioni mobili. Una singola foto da 12 MP può occupare da 3 a 5 MB in forma non compressa. Il Lazy Loading delle immagini impedisce il caricamento di centinaia di immagini invisibili in memoria, cosa che sarebbe fatale per liste con avatar utente o cataloghi di prodotti.

Librerie per Android: Glide e Coil

Glide è la libreria di caricamento immagini più popolare per Android con supporto per caching, trasformazioni e animazioni. Coil è un'alternativa più leggera, scritta in Kotlin usando le coroutine. Entrambe le librerie mettono in pausa automaticamente il caricamento quando un ImageView esce dallo schermo e annullano le richieste quando un ViewHolder viene riutilizzato. Coil utilizza le coroutine e ha una dimensione di ~1,5 MB contro i ~4 MB di Glide, rendendolo preferibile per progetti focalizzati sulla dimensione dell'APK.

kotlin
// Coil — caricamento differito delle immagini
imageView.load("https://example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.placeholder)
    size(512, 512)
    memoryCachePolicy(CachePolicy.ENABLED)
}

Librerie per iOS: Kingfisher e SDWebImage

Kingfisher è una libreria per iOS con supporto per Swift Concurrency, caching su disco e in memoria, e precaricamento per UICollectionView. SDWebImage è una libreria più vecchia con radici in Objective-C ma con supporto per Swift. Entrambe le librerie si integrano con UIImageView e gestiscono automaticamente il ciclo di vita del caricamento: annullano le richieste quando una cella viene riutilizzata, caricano le immagini solo quando la cella è visibile e liberano memoria in caso di avviso di memoria insufficiente.

Lazy Loading di dati e liste

Grandi liste di dati sono il principale campo di applicazione del Lazy Loading nelle app mobili. Feed di notizie, cataloghi di prodotti, chat, cronologia transazioni — qualsiasi schermata con una lista potenzialmente infinita richiede paginazione e caricamento differito.

Paging 3 per Android

Paging 3 è una libreria di Android Jetpack che implementa il ciclo completo di caricamento differito: richiesta dati da RemoteMediator (API + database), caching in Room, output pagina per pagina tramite PagingData e visualizzazione tramite AsyncPagingDataAdapter. Paging 3 supporta tre tipi di paginazione: basata su pagine, basata su elementi (offset/limit) e basata su chiavi (chiavi di paginazione dall'API). Separator è il supporto integrato per separatori tra le pagine per indicatori di caricamento.

kotlin
// Paging 3 — caricamento differito dall'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 e LazyHStack

LazyVStack è un contenitore integrato di SwiftUI che crea e renderizza gli elementi solo quando appaiono sullo schermo. A differenza di VStack, che calcola immediatamente il layout di tutti gli elementi figli, LazyVStack rimanda la creazione della vista fino a quando l'elemento diventa visibile o entra nell'intervallo di precaricamento. LazyHStack è l'equivalente orizzontale per caroselli. Per liste di grandi dimensioni, Apple consiglia di utilizzare List, che internamente funziona come LazyVStack con riciclo integrato aggiuntivo.

swift
// SwiftUI — LazyVStack con caricamento differito
ScrollView {
    LazyVStack(spacing: 8) {
        ForEach(articles) { article in
            ArticleRow(article: article)
                .onAppear {
                    if article == articles.last {
                        viewModel.loadMore()
                    }
                }
        }
    }
}

Lazy Loading dei componenti UI

Il caricamento differito dei componenti UI è una tecnica in cui parti dell'interfaccia (intestazioni, piè di pagina, sezioni delle impostazioni, schede) non vengono create all'avvio dello schermo, ma al primo accesso. Questo accelera il rendering iniziale e riduce il carico sul thread principale.

Android: ViewStub e caricamento differito dei Fragment

ViewStub è un segnaposto View leggero in Android che non occupa spazio nel layout e non crea Views figlie fino alla chiamata di inflate(). Ideale per sezioni usate raramente: pannello di ricerca, impostazioni avanzate, blocchi pubblicitari. Il caricamento differito dei Fragment è una tecnica in cui Fragment.onCreateView viene rimandato fino a quando l'utente passa a quella scheda. Viene implementato tramite isVisible o UserVisibleHint in ViewPager.

AndroidX fornisce SplitInstallManager per il caricamento differito dei moduli su richiesta. I moduli di configurazione, diagnostica o funzionalità aggiuntive vengono caricati come moduli Dynamic Feature solo alla prima richiesta dell'utente. Questo riduce la dimensione base dell'app del 30–50% e implementa contemporaneamente il principio del Lazy Loading non solo a livello di dati, ma anche a livello di codice.

iOS: TabView e la natura pigra di ViewBuilder

TabView in SwiftUI carica il contenuto di ciascuna scheda in modo differito — solo quando la scheda viene attivata. UIKit UITabBarController crea tutti i controller figli all'avvio per impostazione predefinita, ma questo comportamento può essere modificato non aggiungendoli immediatamente a tabBarController.viewControllers e sostituendoli man mano che l'utente cambia scheda. UIStackView con arrangedSubviews aggiunti dinamicamente segue anch'esso il principio del Lazy Loading — aggiungi una Subview solo quando l'utente esegue un'azione che richiede quella parte dell'interfaccia.

Per ottimizzare il caricamento complessivo dello schermo, combina il Lazy Loading a tutti i livelli: ViewStub per le sezioni usate raramente, Paging 3 per i dati, Glide/Coil per le immagini e inizializzazione differita di ViewModel tramite Hilt/Dagger Scopes o Swinject. Questo approccio produce uno schermo che si carica in 200–400 ms anche su dispositivi economici con 3 GB di RAM.

Domande frequenti

Quando non usare il Lazy Loading?

Se lo schermo mostra garantitamente pochi elementi (fino a 20) e tutti sono necessari immediatamente, il Lazy Loading è eccessivo. Per le liste che vengono raramente scrollate, l'Eager Loading può essere più semplice e veloce da implementare senza perdita di prestazioni evidente.

Come influisce il Lazy Loading sulla memoria?

Riduce il consumo di memoria di picco da 3 a 10 volte per le liste grandi, poiché in memoria vengono conservati solo gli elementi visibili più il buffer di precaricamento. Tuttavia, l'aggiunta del precaricamento e della cache delle immagini crea un consumo di memoria moderato che deve essere gestito.

Cosa scegliere — LazyVStack o List in SwiftUI?

List è preferibile per dati omogenei con supporto per swipe, drag-and-drop e selezione integrata. LazyVStack è per layout personalizzati con diversi tipi di cella, sezioni e spaziature non standard. List internamente funziona come LazyVStack con funzionalità aggiuntive.

Come risolvere i problemi di caricamento differito?

Su Android, usa Layout Inspector per visualizzare la gerarchia delle View: con Lazy Loading, la maggior parte degli elementi dovrebbe essere assente dall'albero. Su iOS, usa Xcode Debug View Hierarchy. Se tutti gli elementi sono presenti durante lo scroll, il Lazy Loading non funziona.

Cosa è più importante — velocità di caricamento o risparmio di memoria?

Entrambi i parametri sono importanti, ma la priorità dipende dalla piattaforma. Su iOS con ARC e gestione efficiente della memoria, la priorità è la velocità di caricamento. Su Android con JVM e GC, la priorità è il risparmio di memoria, poiché ogni allocazione nel thread principale può causare un blocco del GC.

Riepilogo

  • Lazy Loading è un pattern di caricamento differito che accelera il primo avvio e risparmia memoria
  • Le immagini vengono caricate tramite Glide, Coil, Kingfisher solo quando entrano nel viewport
  • Paging 3 per Android fornisce caricamento paginato dei dati con caching in Room
  • LazyVStack in SwiftUI rimanda il rendering degli elementi fino alla loro comparsa sullo schermo
  • ViewStub in Android — caricamento differito dei componenti UI al primo accesso
  • Buffer di precaricamento — caricamento anticipato per uno scorrimento fluido
  • Combina Lazy Loading a tutti i livelli: dati + immagini + UI

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche