FPS (Frames Per Second) — är ett mätvärde som visar hur många enskilda bilder det grafiska systemet renderar på en sekund. Inom mobilutveckling är FPS en standardindikator för UI-prestanda: ju högre FPS, desto jämnare animationer och desto mer responsivt gränssnitt. Enligt data från Google Android Performance, 2025 är målvärdet för FPS i mobilapplikationer 60 bilder per sekund — detta är tröskeln där det mänskliga ögat uppfattar rörelse som kontinuerlig och jämn.
Huvudpunkter
FPS (Frames Per Second) — är en måttenhet för bildfrekvens som används inom datorgrafik, video och mobila gränssnitt. Varje bildruta är en statisk bild som visas på skärmen under en kort tidsperiod. Vid snabb växling av bildrutor uppfattar hjärnan dem som kontinuerlig rörelse — denna effekt kallas synpersistens. För mobilapplikationer är FPS ett kritiskt mätvärde, eftersom varje missad bildruta (drop) förvandlar en jämn animation till märkbara hack. Applikationen måste hinna rendera varje bildruta strikt inom tidsbudgeten: 16.6 ms för 60 FPS, 11.1 ms för 90 FPS, 8.3 ms för 120 FPS.
FPS mäts inte bara för UI, utan även för spel, video och kamera. I spel beror FPS på scenens komplexitet, texturkvalitet och GPU-kraft. I video är FPS fast (24, 30, 60 bilder/s) och bestäms av innehållet. I mobilapplikationer beror FPS på UI-kodens effektivitet: Layoutens komplexitet, antalet vyer, omritningsfrekvens och GC:s (Garbage Collection) arbete. Enligt Apple WWDC 2022 kan genomsnittlig FPS i en app falla med 10–15% på grund av ineffektiv uppdatering av samlingar (reloadData istället för insert/delete/dequeueReusableCell). Realtidsmätning av FPS är standardpraxis för QA-ingenjörer och utvecklare som arbetar med prestanda.
Beräkningen av FPS i en mobilapp baseras på mätning av tiden mellan på varandra följande bildrutor. Den enklaste formeln: FPS = 1000 / deltaTimeMs, där deltaTimeMs är intervallet mellan slutförandet av föregående bildruta och slutförandet av den aktuella. Om den aktuella bildrutan renderades på 20 ms, blir FPS = 1000 / 20 = 50. I praktiken är FPS dock sällan stabil ens inom en sekund: en typisk profil innehåller bildrutor på 12–16 ms varvade med missade (jank) eller långsamma bildrutor (40–60 ms). Därför mäts FPS som ett glidande medelvärde över 1–5 sekunder eller som percentiler av bildtidsfördelningen.
I Android beräknas FPS via Choreographer, som tar emot en callback från VSync (skärmens synkroniseringsimpuls). Varje callback motsvarar en bildruta. Om callbacken inte kommer — har bildrutan missats. Choreographer gör det möjligt att exakt mäta antalet bilder per sekund och antalet missade bildrutor (skipped frames). I iOS fungerar CADisplayLink på liknande sätt — det anropas varje gång skärmen är redo att rendera en ny bildruta. Egenskapen timestamp innehåller den exakta tiden för den senaste bildrutan, och targetTimestamp — den förväntade tiden för nästa. Skillnaden mellan dem är tidsbudgeten för den aktuella bildrutan.
Kod i Swift demonstrerar enkel FPS-övervakning via CADisplayLink. Räknaren frameCount ökar vid varje anrop och en gång per sekund beräknas den faktiska FPS.
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
}
}
}
Standarden 60 FPS (60 Hz) har rotat sig i branschen av flera skäl. Det första — fysiologiskt: det mänskliga ögat urskiljer inte enskilda bildrutor vid frekvenser över 50–60 Hz och uppfattar dem som jämn rörelse. Denna tröskel kallas Critical Flicker Fusion (CFF). Det andra — historiskt: de första katodstrålerören (CRT) arbetade på 60 Hz i USA (NTSC) och 50 Hz i Europa (PAL). Moderna LCD-skärmar har ärvt denna frekvens. Det tredje — tekniskt: för UI-animationer ger 60 FPS en submillisekunds responsfördröjning på beröring, vilket är kritiskt för textinmatning, scrollning och dragning.
För mobilutvecklare är 60 FPS inte bara en rekommendation, utan en strikt budget på 16.6 ms per bildruta. Denna budget delas mellan alla renderingsfaser: Input (1–2 ms), Animation (2–3 ms), Layout (3–5 ms), Draw (3–5 ms) och Swap (1–2 ms). Om någon fas överskrider sin delbudget kan bildrutan kanske inte rymmas inom 16.6 ms. Google Android Performance rekommenderar att man använder 12–14 ms för att förbereda bildrutan, med 2–4 ms reserv för systemavbrott (GC, bakgrundstrådar). Enligt Firebase Performance får appar med genomsnittlig FPS under 52 och P99 FPS under 30 35% fler prestandaklagomål i Google Play-recensioner.
FPS och Frame Time (bildtid) — är två sidor av samma mätvärde och det är viktigt att inte blanda ihop dem. FPS är hastighet, Frame Time är fördröjning. Vid 60 FPS tar varje bildruta 16.6 ms. Vid 30 FPS — 33.3 ms. Men FPS är ett icke-linjärt mätvärde: ett fall från 60 till 30 FPS innebär att bildtiden har fördubblats, och ett fall från 30 till 20 — 1,5 gånger. Därför visar profileringsverktyg inte FPS, utan Frame Time — detta gör det möjligt att se problematiska bildrutor, inte den genomsnittliga frekvensen. Till exempel kan ett genomsnitt på 55 FPS dölja att 5% av bildrutorna har en Frame Time på 50–100 ms — dessa bildrutor orsakar Jank men påverkar inte den genomsnittliga FPS särskilt mycket.
Vid prestandaanalys rekommenderas det att inte titta på genomsnittlig FPS, utan på histogrammet över Frame Time. I Android Studio Profiler och iOS Instruments visas Frame Time som en skala, där den gröna zonen — upp till 16.6 ms (60 FPS), gul — 16.6–33.3 ms (30–60 FPS), röd — över 33.3 ms (mindre än 30 FPS). Varje röd kolumn är en märkbar fördröjning för användaren. Praktisk regel: P95 Frame Time (95% av bildrutorna ryms inom X ms) — är ett mer tillförlitligt mätvärde än genomsnittlig FPS. Om P95 Frame Time överstiger 32 ms (30 FPS) uppfattas appen som långsam även med genomsnittlig FPS på 50.
Funktion i Kotlin för att konvertera en array av bildtider till FPS med percentiler. Returnerar inte bara genomsnittlig FPS, utan även P50, P90 och P99 för detaljerad analys.
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)]
)
}
Moderna mobila enheter med skärmar på 90, 120 och 144 Hz ställer nya krav på FPS. Om en app levererar 60 FPS på en 120 Hz-skärm ser användaren mikrohackningar, eftersom varannan skärms uppdateringscykel får samma bildruta. För att bibehålla 120 FPS minskas budgeten per bildruta från 16.6 till 8.3 ms — detta kräver dubbelt så effektiv renderingskod. Enligt Android-utvecklare (Google I/O 2023) krävs det för stabil 120 FPS: att undvika allokeringar i Draw-cykeln, minimera antalet vyer i hierarkin (färre än 80), avstå från tunga drawable till förmån för VectorDrawable och använda surfaceView för komplex grafik.
I iOS är situationen liknande: iPhone Pro med ProMotion (120 Hz) kräver dubbelt så många bildrutor, men tiden per bildruta är hälften så lång. Apple påpekar att inte alla animationer behöver köras på 120 FPS — Core Animation sänker automatiskt frekvensen för stillastående eller långsamt föränderliga element. Dock bör scrollning, gestanimationer och övergångar leverera 120 FPS för en "silkeslen" känsla. De främsta problemen vid övergången från 60 till 120 FPS: ökad energiförbrukning (25–40% för GPU), enhetens uppvärmning och throttling — när frekvensen sjunker på grund av överhettning. Det rekommenderas att implementera en fallback-mekanism: om Frame Time stabilt överstiger 8.3 ms, sänk mål-frekvensen programmatiskt till 60 FPS, istället för att vänta på systemets throttling.
Kod i Java för Android avgör om enheten kan stödja 120 FPS och växlar renderingsläge. Använder Display.getMode för att bestämma vilka frekvenser som stöds.
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;
}
}
Optimering av FPS kräver ett systematiskt tillvägagångssätt, från profilering till refaktorering av problemområden. Första steget — mät nuvarande FPS med en profileringsverktyg (. Andra steget — hitta bildrutor som överskrider budgeten. För Android kan detta göras via GPU Profiling eller Perfetto. För iOS — Instruments med mallen Core Animation. Tredje steget — eliminera orsakerna: minska overdraw, minska djupet på vy-nestling, ersätta Layout-fasen med ConstraintLayout, lägg till ViewHolder Recycling, flytta tunga beräkningar till en bakgrundstråd.
Specifika FPS-optimeringar inkluderar: Frame Pacing — en mekanism som jämnt fördelar tiden mellan bildrutorna för att undvika "kluster" av snabba och långsamma bildrutor. I Android möjliggör Choreographer.FrameCallback med fast intervall implementering av Frame Pacing. I iOS gör CADisplayLink.preferredFrameRateRange samma sak. Den andra mekanismen — Triple Buffering: systemet använder tre buffertar istället för två, vilket gör att GPU:n kan börja rita nästa bildruta utan att vänta på att den föregående bufferten blir ledig. Android aktiverar automatiskt Triple Buffering vid behov, men i iOS kan utvecklaren uttryckligen begära det via CAMetalLayer. Den tredje — Texture Caching: cachning av bitmappar i GPU-minnet för att inte behöva ladda om dem vid varje bildruta.
Exempel i Kotlin demonstrerar implementering av Frame Pacing med fast intervall på 16.6 ms. Alla callbacks kommer med jämnt intervall, även om systemet fördröjs.
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 av bildruta
}
}
Vanliga frågor
60 FPS — bekväm nivå för mobilappar. Skillnaden mellan 60 och 120 FPS är märkbar endast på skärmar med hög uppdateringsfrekvens vid snabba animationer (scrollning, dragning). Under 30 FPS — obekvämt.
FPS = 1000 / FrameTime (ms). Om Frame Time = 16.6 ms, FPS = 60. Om Frame Time = 33.3 ms, FPS = 30. Det rekommenderas att övervaka Frame Time istället för FPS, eftersom den visar problematiska bildrutor.
Vid scrollning anropar systemet Layout och Draw för varje nytt element i listan. Om vyerna är komplexa, Layout inte cachas eller tunga drawable används — ökar Frame Time och FPS sjunker. Lösning — ViewHolder recycling och platt hierarki.
Använd Instruments med mallen Core Animation (visar FPS i realtid). För programmatisk mätning — CADisplayLink med räkning av bildrutor per sekund. För produktion — MetricKit med måttet MXAnimatoryMetric.
Triple Buffering använder tre buffertar istället för två, vilket gör att GPU:n kan börja rendera nästa bildruta innan VSync för den aktuella är slutförd. Detta jämnar ut toppbelastningar och ökar FPS-stabiliteten, men lägger till 1 bildrutas fördröjning.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också