Il lag in un'applicazione mobile è un ritardo percepibile tra l'azione dell'utente e la reazione dell'interfaccia, causato dal sovraccarico del thread principale, da perdite di memoria o da operazioni di I/O non ottimali. A differenza dei bug legati a errori logici, il lag è un problema di prestazioni: l'app funziona correttamente ma lentamente. Secondo il AppDynamics Mobile App Performance Report 2024, il 62% degli utenti elimina un'app se questa rallenta per più di 3 secondi. Diagnosticare il lag richiede la profilazione di CPU, memoria e rete con Android Studio Profiler e Xcode Instruments.
Punti chiave
Il lag in un'applicazione mobile è un ritardo soggettivamente percepibile tra l'azione dell'utente (tocco, swipe, inserimento testo) e la risposta dell'interfaccia. Tecnicamente, il lag si misura come il tempo tra l'evento di input e il rendering completo del frame: la soglia comoda è fino a 100 ms, percepibile da 200 ms, critica oltre 500 ms.
Nella terminologia utente, “lag” e “lentezza” sono spesso usati come sinonimi, ma tecnicamente il lag è un ritardo fisso (es. 300 ms a ogni tocco), mentre “lentezza” è un rallentamento intermittente: l'app funziona fluidamente e poi si blocca per un secondo. Un bug, a differenza del lag, non riguarda la velocità ma la correttezza della visualizzazione.
Google Play e App Store considerano le metriche di prestazione nella classificazione delle app. Il tasso di ANR, la frequenza di jank e il tempo di avvio influenzano la visibilità nella ricerca e la conversione di installazione. Un'app con lag persistente perde fino al 40% degli utenti dopo il primo avvio.
Il lag si verifica quando il thread principale dell'UI non riesce a elaborare i frame a 60 FPS (16,6 ms per frame) o 120 FPS (8,3 ms). Esaminiamo le principali fonti di ritardo.
Qualsiasi operazione sincrona nel thread dell'UI — lettura da SharedPreferences, lavoro con database tramite Room senza suspend, decodifica di un'immagine in Bitmap — blocca il rendering del frame. Su Android questo causa jank, su iOS causa un ritardo nel rendering di Core Animation.
Quando il Garbage Collector su Android o ARC su iOS libera memoria, tutti i thread vengono sospesi. Pause frequenti del GC si verificano quando vengono creati molti oggetti temporanei — ad esempio, creando una nuova istanza di ViewHolder a ogni chiamata dell'adapter. Ciò si manifesta come scorrimento a scatti.
ConstraintLayout annidati, multipli LinearLayout, View sovrapposte — ogni livello di annidamento aumenta il tempo di misurazione e layout pass. Xcode indica che una gerarchia di layer profonda (più di 10 livelli) causa un calo del FPS del 20-30%.
Per identificare le cause del lag si utilizzano i profiler integrati negli IDE e gli strumenti di monitoraggio di sistema. Ogni strumento risolve il proprio compito.
CPU Profiler mostra quali metodi consumano tempo CPU e in quali thread vengono eseguiti. Se un metodo di calcolo pesante viene eseguito nel thread principale — questa è la causa principale. Registrare una traccia con sample Java Method abilitato permette di vedere lo stack delle chiamate in qualsiasi momento e trovare i punti caldi.
Lo strumento equivalente per iOS — Time Profiler — raccoglie campioni dello stack ogni millisecondo e mostra la percentuale di tempo CPU consumata da ogni metodo. Combinato con il flag Main Thread Only, filtra solo le operazioni del thread principale, indicando direttamente le fonti di lag.
Le richieste di rete lente creano l'impressione di lag anche se il thread UI non è bloccato. Network Profiler in Android Studio e Network Link Conditioner in Xcode permettono di simulare connessioni lente e identificare come l'app si comporta in condizioni reali. Le risposte chunked senza progresso e i grandi payload JSON sono fonti tipiche di lag apparente.
Esempio di profilazione di una richiesta di rete con OkHttp con misurazione del tempo:
class TimingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val start = System.nanoTime()
val response = chain.proceed(chain.request())
val duration = (System.nanoTime() - start) / 1_000_000
Log.d("Timing", "Request took $duration ms")
return response
}
}
Risolvere il lag richiede un lavoro sistematico: dall'ottimizzazione di un singolo metodo ai cambiamenti architetturali. Esaminiamo le tecniche più efficaci.
Kotlin Coroutines con Dispatchers.IO per le richieste di rete e Dispatchers.Default per i calcoli garantiscono che il thread principale rimanga libero per l'UI. Su iOS Grand Central Dispatch con queue .global(qos: .userInitiated) per i compiti in background e .main per gli aggiornamenti dell'UI è l'approccio standard. Evitate operazioni sync tra le code.
RecyclerView su Android e UICollectionView su iOS richiedono una configurazione corretta: ViewHolder con creazione minima di oggetti in onBindViewHolder, DiffUtil per il calcolo delle modifiche, prefetching per il caricamento anticipato dei dati. Su iOS utilizzate diffable data source per aggiornamenti animati senza gestione manuale.
Caricare la stessa immagine a ogni scorrimento è lag garantito. Coil (Android) e Kingfisher (iOS) memorizzano le immagini nella cache in memoria e su disco, garantendo la visualizzazione immediata a richieste ripetute. Per i dati, utilizzate Room con un livello di cache basato su Flow o Combine.
Esempio di configurazione della cache delle immagini con Coil su Android:
val imageLoader = ImageLoader(context) {
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
crossfade(true)
size(512, 512)
}
// Loading with auto-caching enabled
imageView.load("https://example.com/image.jpg") {
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
Prevenire il lag è più economico che risolverlo in produzione. Le misure preventive sono integrate nel processo di sviluppo a livello di strumenti e architettura.
StrictMode è uno strumento integrato di Android che rileva operazioni di I/O accidentali e chiamate di rete sul thread principale durante lo sviluppo. Attivatelo in Application.onCreate con politica penaltyDeath per violazioni critiche. Questo è l'unico modo per garantire che lo sviluppatore veda il problema prima del commit.
L'equivalente per iOS — Main Thread Checker in Xcode, parte di Runtime Sanitization — controlla automaticamente che tutte le chiamate UIKit e AppKit vengano eseguite sul thread principale. Attivatelo nello schema di build Debug e puntate a zero avvisi in CI.
Aggiungete esecuzioni di Macrobenchmark (Android) e XCTMetrics (iOS) alla vostra pipeline CI per misurare il tempo di avvio, il FPS di scorrimento e l'utilizzo della memoria. Impostate soglie: se un nuovo commit aumenta il tempo di avvio di oltre il 5% — il build fallisce.
Domande frequenti
Il lag è una sensazione soggettiva di ritardo che può verificarsi anche con FPS elevato se il ritardo è causato dal tempo di elaborazione dell'input, non dal rendering. Il basso FPS (meno di 30 fps) è una causa di lag, ma non l'unica.
Utilizzate Frame Timing API su Android (Choreographer) e CADisplayLink su iOS per misurare il tempo tra i frame. Google Play Vitals mostra il tasso di jank in condizioni reali. Per misurazioni precise utilizzate Macrobenchmark con scenari di scorrimento.
I dispositivi vecchi hanno meno core CPU, meno RAM e memoria più lenta. Un'operazione che richiede 5 ms su un flagship può richiedere 50 ms su un dispositivo economico. Testate le prestazioni su dispositivi di fascia bassa e configurate Baseline Profiles per la compilazione AOT.
Sì, è uno dei metodi più efficaci. Le immagini ad alta risoluzione consumano molta memoria e tempo CPU per la decodifica. Utilizzate il downscale alla dimensione della View, i formati WebP (Android) e HEIC (iOS) e la cache tramite Coil o Kingfisher.
SwiftUI ottimizza automaticamente gli aggiornamenti tramite diffing, riducendo il rischio di lag quando i dati cambiano. Tuttavia, gerarchie complesse e ricostruzioni frequenti del body possono causare cali di FPS. UIKit offre più controllo sulle prestazioni ma richiede ottimizzazione manuale.
Riepilogo
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.
Leggi anche