FPS (Frames Per Second) è una metrica che mostra quanti fotogrammi individuali un sistema grafico renderizza in un secondo. Nello sviluppo mobile, FPS è un indicatore standard delle prestazioni dell'interfaccia utente: più alto è l'FPS, più fluide sono le animazioni e più reattiva è l'interfaccia. Secondo Google Android Performance, 2025, il valore target di FPS per le applicazioni mobili è di 60 fotogrammi al secondo — la soglia in cui l'occhio umano percepisce il movimento come continuo e fluido.
Punti chiave
FPS (Frames Per Second) è un'unità di misura del frame rate utilizzata in computer grafica, video e interfacce mobili. Ogni fotogramma è un'immagine statica visualizzata sullo schermo per un breve periodo di tempo. Con cambi rapidi di fotogrammi, il cervello li percepisce come movimento continuo — questo effetto è chiamato persistenza della visione. Per le applicazioni mobili, l'FPS è una metrica critica perché qualsiasi fotogramma perso trasforma un'animazione fluida in uno scatto percepibile. L'applicazione deve renderizzare ogni fotogramma rigorosamente entro il budget di tempo: 16,6 ms per 60 FPS, 11,1 ms per 90 FPS, 8,3 ms per 120 FPS.
L'FPS viene misurato non solo per l'interfaccia utente, ma anche per giochi, video e fotocamera. Nei giochi, l'FPS dipende dalla complessità della scena, dalla qualità delle texture e dalla potenza della GPU. Nel video, l'FPS è fisso (24, 30, 60 fps) e determinato dal contenuto. Nelle applicazioni mobili, l'FPS dipende dall'efficienza del codice dell'interfaccia utente: complessità del layout, numero di viste, frequenza di ridisegno e lavoro del GC (Garbage Collection). Secondo Apple WWDC 2022, l'FPS medio in un'applicazione può calare del 10–15% a causa di aggiornamenti inefficienti delle collezioni (reloadData invece di insert/delete/dequeueReusableCell). Misurare l'FPS in tempo reale è una pratica standard per gli ingegneri QA e gli sviluppatori che lavorano sulle prestazioni.
Il calcolo dell'FPS in un'applicazione mobile si basa sulla misurazione del tempo tra fotogrammi consecutivi. La formula più semplice: FPS = 1000 / deltaTimeMs, dove deltaTimeMs è l'intervallo tra il completamento del fotogramma precedente e il completamento di quello corrente. Se il fotogramma corrente è stato renderizzato in 20 ms, FPS = 1000 / 20 = 50. Tuttavia, in pratica, l'FPS è raramente stabile anche all'interno di un singolo secondo: un profilo tipico include fotogrammi di 12–16 ms intervallati da fotogrammi saltati (jank) o lenti (40–60 ms). Pertanto, l'FPS viene misurato come media mobile su 1–5 secondi o come percentili della distribuzione del tempo di fotogramma.
Su Android, l'FPS viene calcolato tramite Choreographer, che riceve un callback da VSync (impulso di sincronizzazione dello schermo). Ogni callback corrisponde a un fotogramma. Se il callback non arriva, il fotogramma viene saltato. Choreographer consente di misurare il numero esatto di fotogrammi al secondo e il numero di fotogrammi saltati. Su iOS, CADisplayLink funziona in modo simile — viene chiamato ogni volta che lo schermo è pronto per renderizzare un nuovo fotogramma. La proprietà timestamp contiene l'ora esatta dell'ultimo fotogramma e targetTimestamp l'ora prevista del successivo. La differenza tra loro è il budget di tempo per il fotogramma corrente.
Il codice Swift dimostra un semplice monitoraggio dell'FPS tramite CADisplayLink. Il contatore frameCount viene incrementato a ogni chiamata, e una volta al secondo viene calcolato l'FPS effettivo.
class FpsCounter {
private var displayLink: CADisplayLink?
private var frameCount = 0
private var lastTime = TimeInterval(0)
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(countFrame)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func countFrame() {
frameCount += 1
let now = Date().timeIntervalSince1970
if now - lastTime >= 1.0 {
print("FPS: \(frameCount)")
frameCount = 0
lastTime = now
}
}
}
Lo standard 60 FPS (o 60 Hz) si è affermato nel settore per diverse ragioni. La prima è fisiologica: l'occhio umano non distingue i singoli fotogrammi a frequenze superiori a 50–60 Hz, percepèndoli come movimento fluido. Questa soglia è chiamata Critical Flicker Fusion (CFF). La seconda è storica: i primi tubi a raggi catodici (CRT) funzionavano a 60 Hz negli Stati Uniti (NTSC) e a 50 Hz in Europa (PAL). Gli schermi LCD moderni hanno ereditato questa frequenza. La terza è tecnica: per le animazioni dell'interfaccia utente, 60 FPS fornisce una latenza di risposta tattile sub-millisecondo, che è fondamentale per l'immissione di testo, lo scorrimento e il trascinamento.
Per gli sviluppatori mobili, 60 FPS non è solo una raccomandazione, ma un budget rigoroso di 16,6 ms per fotogramma. Questo budget è suddiviso tra tutte le fasi di rendering: Input (1–2 ms), Animazione (2–3 ms), Layout (3–5 ms), Disegno (3–5 ms) e Scambio (1–2 ms). Se una fase supera il suo sotto-budget, il fotogramma potrebbe non rientrare in 16,6 ms. Google Android Performance consiglia di mantenersi entro 12–14 ms per la preparazione del fotogramma, lasciando 2–4 ms di margine per le interruzioni di sistema (GC, thread in background). Secondo Firebase Performance, le applicazioni con un FPS medio inferiore a 52 e P99 FPS inferiore a 30 ricevono il 35% in più di reclami sulle prestazioni nelle recensioni di Google Play.
FPS e tempo di fotogramma sono due facce della stessa metrica, ed è importante non confonderli. L'FPS è velocità, il tempo di fotogramma è latenza. A 60 FPS, ogni fotogramma impiega 16,6 ms. A 30 FPS — 33,3 ms. Ma l'FPS è una metrica non lineare: un calo da 60 a 30 FPS significa che il tempo di fotogramma è raddoppiato, mentre un calo da 30 a 20 significa un aumento di 1,5 volte. Pertanto, i profiler mostrano il tempo di fotogramma anziché l'FPS — ciò consente di vedere i fotogrammi problematici invece di una frequenza media. Ad esempio, una media di 55 FPS può nascondere il fatto che il 5% dei fotogrammi ha un tempo di fotogramma di 50–100 ms — questi fotogrammi causano Jank ma non influenzano significativamente l'FPS medio.
Quando si analizzano le prestazioni, si consiglia di guardare non l'FPS medio, ma l'istogramma del tempo di fotogramma. In Android Studio Profiler e iOS Instruments, il tempo di fotogramma viene visualizzato come una scala in cui la zona verde va fino a 16,6 ms (60 FPS), la zona gialla va da 16,6 a 33,3 ms (30–60 FPS) e la zona rossa oltre 33,3 ms (meno di 30 FPS). Ogni colonna rossa è un ritardo percepibile dall'utente. Una regola pratica: P95 Frame Time (il 95% dei fotogrammi rientra in X ms) è una metrica più affidabile dell'FPS medio. Se il P95 Frame Time supera 32 ms (30 FPS), l'applicazione viene percepita come lenta anche con un FPS medio di 50.
Una funzione Kotlin per convertire un array di tempi di fotogramma in FPS con percentili. Restituisce non solo l'FPS medio, ma anche P50, P90 e P99 per un'analisi dettagliata.
data class FpsReport(
val average: Float,
val p50: Float,
val p90: Float,
val p99: Float
)
fun List<Long>.toFpsReport(): FpsReport {
val fpsValues = this.map { ms ->
if (ms > 0) 1000f / ms else 0f
}.sorted()
return FpsReport(
average = fpsValues.average().toFloat(),
p50 = fpsValues[fpsValues.size / 2],
p90 = fpsValues[(fpsValues.size * 90 / 100)],
p99 = fpsValues[(fpsValues.size * 99 / 100)]
)
}
I dispositivi mobili moderni con schermi a 90, 120 e 144 Hz impongono nuovi requisiti all'FPS. Se un'applicazione fornisce 60 FPS su uno schermo a 120 Hz, l'utente vede micro-scatti perché ogni secondo ciclo di aggiornamento dello schermo riceve lo stesso fotogramma. Per mantenere 120 FPS, il budget per fotogramma si riduce da 16,6 a 8,3 ms — richiedendo un codice di rendering due volte più efficiente. Secondo gli sviluppatori Android (Google I/O 2023), per raggiungere 120 FPS stabili è necessario: evitare allocazioni nel ciclo di disegno, ridurre al minimo il numero di viste nella gerarchia (meno di 80), abbandonare i drawable pesanti a favore di VectorDrawable e utilizzare surfaceView per la grafica complessa.
La situazione è simile su iOS: l'iPhone Pro con ProMotion (120 Hz) richiede il doppio dei fotogrammi, ma il tempo per fotogramma è dimezzato. Apple nota che non tutte le animazioni devono funzionare a 120 FPS — Core Animation riduce automaticamente la frequenza dei fotogrammi per elementi statici o a cambiamento lento. Tuttavia, lo scorrimento, le animazioni gestuali e le transizioni devono fornire 120 FPS per una sensazione “setosa”. I principali problemi nel passaggio da 60 a 120 FPS: aumento del consumo energetico (25–40% per la GPU), riscaldamento del dispositivo e throttling — quando la frequenza dei fotogrammi diminuisce a causa del surriscaldamento. Si consiglia di implementare un meccanismo di fallback: se il tempo di fotogramma supera costantemente 8,3 ms, ridurre programmaticamente la frequenza dei fotogrammi target a 60 FPS invece di attendere il throttling del sistema.
Il codice Java per Android determina se il dispositivo può supportare 120 FPS e commuta la modalità di rendering. Display.getMode viene utilizzato per determinare le frequenze di aggiornamento supportate.
class FpsModeSwitcher {
static boolean canDo120Fps(Activity activity) {
Display display = activity.getWindowManager()
.getDefaultDisplay();
for (Display.Mode mode : display.getSupportedModes()) {
if (mode.getRefreshRate() >= 120f) {
return true;
}
}
return false;
}
}
L'ottimizzazione dell'FPS richiede un approccio sistematico, che inizia con la profilazione e termina con il refactoring delle aree problematiche. Il primo passo è misurare l'FPS corrente con un profiler. Il secondo passo è trovare i fotogrammi che superano il budget. Su Android, ciò può essere fatto tramite GPU Profiling o Perfetto. Su iOS — Instruments con il modello Core Animation. Il terzo passo è eliminare le cause: ridurre l'overdraw, diminuire la profondità della gerarchia delle viste, sostituire la fase di layout con ConstraintLayout, aggiungere ViewHolder Recycling e spostare i calcoli pesanti in un thread in background.
Le ottimizzazioni specifiche per l'FPS includono: Frame Pacing — un meccanismo che distribuisce uniformemente il tempo tra i fotogrammi per evitare “raffiche” di fotogrammi veloci e lenti. Su Android, Choreographer.FrameCallback con un intervallo fisso consente di implementare Frame Pacing. Su iOS, CADisplayLink.preferredFrameRateRange fa lo stesso. Il secondo metodo — Triple Buffering: il sistema utilizza tre buffer invece di due, consentendo alla GPU di iniziare a renderizzare il fotogramma successivo senza attendere il rilascio del precedente. Android abilita automaticamente Triple Buffering quando necessario, ma su iOS lo sviluppatore può richiederlo esplicitamente tramite CAMetalLayer. Il terzo — Texture Caching: memorizzazione nella cache delle bitmap nella memoria della GPU per evitare di ricaricarle a ogni fotogramma.
Un esempio Kotlin dimostra l'implementazione di Frame Pacing con un intervallo fisso di 16,6 ms. Tutti i callback arrivano a intervallo uniforme, anche se il sistema è in ritardo.
class PacedFrameRenderer {
private val targetDelta = 16_666_666L // 16.6 ms (60 FPS)
private var lastFrameTime = 0L
private val frameCallback =
Choreographer.FrameCallback { frameTimeNanos ->
val delta = frameTimeNanos - lastFrameTime
if (delta >= targetDelta) {
onFrame(delta)
lastFrameTime = frameTimeNanos
}
Choreographer.getInstance()
.postFrameCallback(this)
}
private fun onFrame(delta: Long) {
// rendering del fotogramma
}
}
Domande frequenti
60 FPS è un livello confortevole per le applicazioni mobili. La differenza tra 60 e 120 FPS è percepibile solo sugli schermi ad alta frequenza di aggiornamento durante animazioni rapide (scorrimento, trascinamento). Sotto i 30 FPS — disagio.
FPS = 1000 / FrameTime (ms). Se il tempo di fotogramma = 16,6 ms, FPS = 60. Se il tempo di fotogramma = 33,3 ms, FPS = 30. Si consiglia di monitorare il tempo di fotogramma anziché l'FPS, poiché mostra i fotogrammi problematici.
Durante lo scorrimento, il sistema chiama Layout e Draw per ogni nuovo elemento dell'elenco. Se le viste sono complesse, il layout non viene memorizzato nella cache o vengono utilizzati drawable pesanti — il tempo di fotogramma aumenta e l'FPS cala. La soluzione è il riciclo dei ViewHolder e una gerarchia piatta.
Utilizzare Instruments con il modello Core Animation (mostra l'FPS in tempo reale). Per la misurazione programmatica — CADisplayLink con conteggio dei fotogrammi al secondo. Per la produzione — MetricKit con la metrica MXAnimatoryMetric.
Triple Buffering utilizza tre buffer invece di due, consentendo alla GPU di iniziare a renderizzare il fotogramma successivo prima del completamento del VSync corrente. Ciò attenua i picchi di carico e migliora la stabilità dell'FPS, ma aggiunge un fotogramma di latenza.
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