Jank è un termine che indica scatti evidenti o “balbettii” nell'animazione dell'interfaccia causati dalla perdita di singoli frame. Nelle app mobili, Jank si verifica quando il tempo di rendering del frame supera il budget allocato dalla frequenza di aggiornamento del display. Secondo Android Developers, 2025, Jank è la causa principale della sensazione soggettiva di “lentezza” — un'app può essere funzionalmente perfetta, ma l'utente la percepisce come lenta a causa di FPS instabile.
Punti chiave
Jank è un termine della computer grafica che indica un difetto visivo in cui l'animazione si muove a scatti invece di scorrere fluidamente. Nello sviluppo mobile, Jank viene misurato come numero di frame persi (skipped frames) per unità di tempo. Se il sistema non riesce a preparare un frame in tempo per il VSync, il display ripete il frame precedente — si verifica una pausa di 16,6 ms a 60 Hz. Un singolo frame perso può passare inosservato, ma una serie di 3–5 frame persi consecutivi crea una sensazione di “scatto” della durata di 50–80 ms, che l'utente percepisce chiaramente.
Jank è particolarmente critico per le animazioni che devono funzionare a velocità costante: scorrimento dei feed, animazione di apertura dei menu, effetti parallasse, transizioni tra schermate. Secondo uno studio UX di Google (2024), un'app con un tasso di Jank superiore al 3% delle sessioni di scorrimento riceve il 22% di recensioni con una stella in più rispetto a un'app con un tasso inferiore allo 0,5%. Lo strumento Android Vitals traccia automaticamente Jank e lo classifica per gravità: moderato, grave e critico.
Le cause di Jank si dividono in diverse categorie. La prima è Layout Jank: causata da frequenti chiamate requestLayout() a causa di modifiche alle dimensioni delle View, animazioni LayoutTransition o caricamento dinamico dei contenuti. Ogni chiamata requestLayout attiva Measure + Layout per l'intero sottoalbero delle View, che può richiedere 5–30 ms. La seconda è Draw Jank: correlata all'overdraw e all'uso di drawable pesanti. La terza è Thread Jank: blocco del thread principale a causa di operazioni sincrone — caricamento di file, operazioni sul database nel thread principale, decodifica di Bitmap.
La quarta categoria è GC Jank: garbage collection (GC) in ART/Dalvik o Swift ARC. Quando molti oggetti si accumulano nell'heap, il GC avvia una pausa Stop-The-World di 5–15 ms. Su Android, le pause GC si verificano più spesso durante allocazioni frequenti nei cicli: creazione di oggetti in onDraw(), allocazione negli adapter, espressioni lambda inutilizzate. La quinta è IPC Jank: comunicazione interprocesso (ContentProvider, Binder) sul thread principale. La sesta è Rendering Jank: rendering GPU lento a causa di shader non ottimali o texture di grandi dimensioni.
| Tipo di Jank | Causa | Durata tipica | Strumento di rilevamento |
|---|---|---|---|
| Layout | requestLayout, relayout | 5–30 ms | Perfetto, Systrace |
| Draw | Overdraw, drawable pesanti | 3–20 ms | GPU Profiling |
| Thread | Blocco del thread principale | 10–200 ms | Android Studio Profiler |
| GC | Garbage Collection | 5–15 ms | Memory Profiler |
| Rendering | Carico GPU | 10–50 ms | GPU Tracer, Xcode GPU |
In Android, la diagnosi di Jank inizia con la traccia di sistema Perfetto. Perfetto registra l'attività di tutti i thread, CPU, GPU e scheduler. Un indicatore chiaro di Jank sono le righe Choreographer.doFrame e Choreographer.doCallbacks: se l'intervallo tra due chiamate consecutive doFrame supera 16,6 ms, un frame è stato perso. Perfetto mostra la causa esatta — quale chiamata di sistema, lock o GC ha causato il ritardo. In Android Studio Profiler, funzionalità simile è disponibile tramite CPU Profiler.
Per il rilevamento automatico di Jank in produzione, si utilizza FrameMetricsAggregator — un'API che raccoglie statistiche per ogni frame e le aggrega per sessione. In Android 12+ è apparso PerformanceHintManager — un'API per suggerire al sistema la frequenza fotogrammi target. Se l'app indica che funziona in uno scenario a 120 FPS, il sistema può aumentare la frequenza CPU/GPU per prevenire Jank. Per una semplice registrazione di tutti i frame persi, è sufficiente iscriversi a Choreographer.FrameCallback.
Il codice Kotlin si iscrive a Choreographer.FrameCallback e registra ogni frame perso con la durata del ritardo. Il callback viene invocato a ogni VSync.
class JankDetector {
private val frameBudget = 16_666_666L
private var previousFrameTime = 0L
private val callback =
Choreographer.FrameCallback { currentTime ->
if (previousFrameTime != 0L) {
val frameDuration =
currentTime - previousFrameTime
val skippedFrames =
(frameDuration / frameBudget) - 1
if (skippedFrames > 0) {
Log.w("Jank",
"Skipped $skippedFrames frames")
}
}
previousFrameTime = currentTime
Choreographer.getInstance()
.postFrameCallback(this)
}
fun start() {
Choreographer.getInstance()
.postFrameCallback(callback)
}
}
In iOS, la diagnosi di Jank viene eseguita tramite Instruments con il template Core Animation. Instruments mostra FPS in tempo reale, il numero di rendering fuori schermo e i test di hit. I principali indicatori di Jank in iOS: barre rosse nella timeline Core Animation (superamento del budget del frame), valore alto di Renderer (indica rendering fuori schermo) e FPS basso. Per il monitoraggio in produzione, MetricKit raccoglie report con la metrica MXAnimatoryMetric, che include FPS medio, tempo frame P50 e P95.
La diagnosi nativa di Jank in iOS include CADisplayLink con controllo di timestamp e targetTimestamp. Se il timestamp corrente è significativamente in ritardo rispetto a targetTimestamp, uno o più frame sono stati persi. Apple raccomanda anche di utilizzare os_signpost per la profilazione personalizzata: posizionare un signpost-interval all'inizio e alla fine del rendering del frame e controllare in Instruments quali intervalli superano 16,6 ms. In SwiftUI, per la diagnosi di Jank si usa UIView.invalidateIntrinsicContentSize — le chiamate frequenti a questo metodo indicano un Layout instabile.
Il codice Swift rileva i frame persi tramite CADisplayLink. Se la differenza tra timestamp e targetTimestamp supera 16,6 ms, Jank viene registrato.
class JankMonitor {
private var displayLink: CADisplayLink?
private var totalJank = 0
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(detectJank)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func detectJank() {
guard let link = displayLink else { return }
let delay = link.targetTimestamp
- link.timestamp
if delay > 0.0167 {
totalJank += 1
}
}
}
Per la profilazione di Jank, vengono utilizzati sia strumenti integrati nel sistema operativo che SDK di terze parti. Su Android, lo strumento chiave è Perfetto (che ha sostituito Systrace). Perfetto può registrare tracce fino a 30 secondi e analizzarle tramite l'interfaccia web ui.perfetto.dev. Mostra una timeline precisa con l'attività di Choreographer, i thread di rendering (RenderThread) e la GPU. Per l'analisi dettagliata dei problemi GPU, si utilizza AGI (Android GPU Inspector), che mostra non solo il tempo del frame ma anche il carico di specifici blocchi GPU — shader, rasterizzatore, unità texture.
Su iOS, l'equivalente è Instruments con i template Core Animation, Metal System Trace e GPU Driver. Core Animation mostra FPS e tempo frame, Metal System Trace mostra il lavoro GPU fino a ogni draw call. Per la profilazione su dispositivi reali sotto carico, vengono utilizzati Firebase Performance (raccoglie la metrica Screen Rendering) e Sentry(cattura lo stack trace in caso di Jank). La nuova API Android 15 Performance Hint consente allo sviluppatore di indicare al sistema quali frame sono importanti e ricevere avvisi dal sistema quando Jank è imminente.
Il codice Kotlin utilizza FrameMetricsAggregator per raccogliere statistiche per frame durante una sessione. Dopo aver fermato l'aggregatore, viene visualizzato il numero di frame persi.
class JankAggregator(private val activity: Activity) {
private val aggregator = FrameMetricsAggregator()
fun startCollection() {
aggregator.add(activity.window)
}
fun stopAndReport() {
aggregator.remove()
val result = aggregator.getMetrics()
val totalFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.size ?: 0
val jankFrames = result
?.get(FrameMetrics.TOTAL_DURATION)
?.count { it > 16_666_666L} ?: 0
Log.d("JankReport",
"Jank ratio: \${jankFrames * 100 / totalFrames}%")
}
}
Eliminare Jank richiede una combinazione di tecniche a seconda del tipo. Per Layout Jank: sostituire le gerarchie profonde con ConstraintLayout/Compose/SwiftUI, utilizzare tag merge, evitare requestLayout nelle animazioni. Per Draw Jank: utilizzare Debug GPU Overdraw per trovare overdraw 4x+, sostituire i drawable pesanti con vettoriali (VectorDrawable/PDF), utilizzare hardware layers con cautela — accelerano il rendering ma consumano più memoria GPU. Per Thread Jank: spostare tutte le operazioni I/O, il lavoro con il database e la decodifica Bitmap su thread in background, utilizzare Kotlin Coroutines con il Dispatcher corretto o RxJava con Schedulers.io().
Per GC Jank: minimizzare le allocazioni in onDraw() e getView(), utilizzare pool di oggetti (ObjectPool), sostituire for-each con for indicizzato, utilizzare data class immutabili in Kotlin con copy() con cautela — copy crea un nuovo oggetto. Per IPC Jank: inizializzare ContentProvider in modo lazy tramite App Startup, spostare le chiamate Binder su un thread in background. Per Rendering Jank: ridurre la dimensione delle texture alla risoluzione massima dello schermo, utilizzare compressione ASTC o ETC2, evitare compilazioni eccessive di shader (compilare gli shader in anticipo). Una soluzione completa è l'esecuzione regolare di profilazione Perfetto/Instruments in CI e il monitoraggio delle regressioni di Jank.
Il codice Kotlin dimostra il caricamento asincrono dei dati sullo schermo dopo reportFullyDrawn, in modo che il lavoro pesante non blocchi il primo frame. Il callback viene invocato dopo che l'utente vede l'interfaccia.
class JankSafeLoader {
suspend fun loadAfterFirstFrame(
activity: Activity
) {
// garantire che il primo frame sia già stato renderizzato
if (Build.VERSION.SDK_INT >= 29) {
activity.reportFullyDrawn()
}
// caricamento pesante — dopo il primo frame
withContext(Dispatchers.IO) {
val data = fetchHeavyData()
withContext(Dispatchers.Main) {
updateUI(data)
}
}
}
}
Domande frequenti
Jank sono frame di rendering persi che appaiono come scatti o scossoni evidenti nell'animazione. Si verifica quando il tempo di preparazione del frame supera il budget di tempo (16,6 ms per 60 FPS).
Layout Jank (requestLayout frequente), Draw Jank (overdraw), Thread Jank (blocco del thread principale), GC Jank (garbage collection), IPC Jank (chiamate Binder) e Rendering Jank (shader pesanti).
Utilizzare Perfetto per il tracing di sistema, GPU Profiling per l'analisi delle fasi del frame e FrameMetricsAggregator per il monitoraggio in produzione. In Android Studio — CPU Profiler con Deep Java Trace.
Tramite Instruments con template Core Animation o Metal System Trace. Per la produzione — MetricKit con MXAnimatoryMetric. Programmaticamente — CADisplayLink controllando la differenza tra timestamp e targetTimestamp.
Secondo Google, un tasso di Jank superiore al 3% delle sessioni di scorrimento (3 scorrimenti su 100 contengono uno scatto) porta a un aumento del 22% delle recensioni negative. Il tasso obiettivo è inferiore allo 0,5% delle sessioni di scorrimento.
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