Lag i en mobilapp är en märkbar fördröjning mellan användarens handling och gränssnittets reaktion, som uppstår på grund av överbelastning av huvudtråden, minnesläckor eller icke-optimala in-/utdataoperationer. Till skillnad från buggar relaterade till logiska fel, är lag ett prestandaproblem: appen fungerar korrekt men långsamt. Enligt AppDynamics Mobile App Performance Report 2024 tar 62% av användarna bort en app om den går långsamt i mer än 3 sekunder. Diagnostik av lag kräver profilering av CPU, minne och nätverk med Android Studio Profiler och Xcode Instruments.
Huvudpunkter
Lag (från engelskans lag) i en mobilapp är en subjektivt märkbar fördröjning mellan användarens handling (tryckning, svepning, textinmatning) och gränssnittets reaktion. Tekniskt mäts lag som tiden mellan inmatningshändelsen och fullständig rendering av bildrutan: bekväm tröskel — upp till 100 ms, märkbar — från 200 ms, kritisk — mer än 500 ms.
I användarterminologi används „lagar” och „går långsamt” ofta som synonymer, men tekniskt är lag en fast fördröjning (t.ex. 300 ms vid varje klick), medan „går långsamt” är en ojämn inbromsning: appen fungerar ibland smidigt, ibland fryser den i en sekund. En bugg, till skillnad från lag, handlar inte om hastighet utan om korrekt visning.
Google Play och App Store tar hänsyn till prestandaindikatorer vid rankning av appar. ANR-frekvens, jank-frekvens och starttid påverkar synligheten i sökning och installationskonvertering. En app med konstanta lag förlorar upp till 40% av användarna efter första starten.
Lag uppstår när huvud-UI-tråden inte hinner bearbeta bildrutor med 60 FPS (16.6 ms per bildruta) eller 120 FPS (8.3 ms). Låt oss titta på de främsta källorna till fördröjning.
Varje synkron operation i UI-tråden — läsning från SharedPreferences, arbete med databas via Room utan suspend, avkodning av bild till Bitmap — blockerar rendering av bildrutan. På Android leder detta till överhoppade bildrutor (jank), på iOS — till fördröjning av Core Animation-rendering.
När Garbage Collector på Android eller ARC på iOS frigör minne, stoppas alla trådar. Frekventa GC-pauser uppstår när många tillfälliga objekt skapas — till exempel vid varje anrop till listadaptern skapas en ny ViewHolder-instans. Detta visar sig som ryckig scrollning.
Nästlade ConstraintLayout, flera LinearLayout, överlappande View — varje nästling ökar tiden för measure och layout pass. Xcode anger att djup lagerhierarki (mer än 10 nivåer) orsakar en FPS-minskning på 20-30%.
För att identifiera orsaker till lag används profilerare inbyggda i IDE och systemövervakningsverktyg. Varje verktyg löser sin egen uppgift.
CPU Profiler visar vilka metoder som upptar processortid och i vilka trådar de körs. Om en metod med tunga beräkningar körs i main thread — är det roten till problemet. Inspelning av spårning med aktiverad sample Java Method gör det möjligt att se anropsstacken varje ögonblick och hitta „heta punkter”.
Motsvarande verktyg för iOS — Time Profiler — samlar stackprover varje millisekund och visar hur stor procentandel av CPU-tiden varje metod upptar. Kombinationen med flaggan Main Thread Only filtrerar endast operationer på huvudtråden, vilket direkt indikerar källorna till lag.
Långsamma nätverksförfrågningar skapar intryck av lag, även om UI-tråden inte är blockerad. Network Profiler i Android Studio och Network Link Conditioner i Xcode gör det möjligt att simulera långsam anslutning och se hur appen beter sig under verkliga förhållanden. Chunkade svar utan framsteg och stora JSON-laster är typiska källor till skenbara lag.
Exempel på profilering av en nätverksförfrågan med OkHttp med tidsmätning:
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("Tidsinställning", "Förfrågan tog $duration ms")
return response
}
}
Att åtgärda lag kräver systematiskt arbete: från optimering av en enskild metod till arkitektoniska förändringar. Låt oss titta på de mest effektiva teknikerna.
Kotlin Coroutines med dispatcher Dispatchers.IO för nätverksförfrågningar och Dispatchers.Default för beräkningar garanterar att huvudtråden förblir fri för UI. På iOS är Grand Central Dispatch med queue .global(qos: .userInitiated) för bakgrundsuppgifter och .main för UI-uppdateringar standardmetoden. Undvik sync-operationer mellan köer.
RecyclerView på Android och UICollectionView på iOS kräver korrekt konfiguration: ViewHolder med minimal objektskapning i onBindViewHolder, DiffUtil för att beräkna förändringar, prefetching för att ladda data i förväg. På iOS, använd diffable data source för animerade uppdateringar utan manuell hantering.
Att ladda samma bild vid varje scrollning — garanterat lag. Coil (Android) och Kingfisher (iOS) cachar bilder i minne och på disk, vilket säkerställer omedelbar visning vid upprepad förfrågan. För data, använd Room med ett cachelager baserat på Flow eller Combine.
Exempel på konfiguration av bildcachelagring med Coil på Android:
val imageLoader = ImageLoader(context) {
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
crossfade(true)
size(512, 512)
}
// Laddar med automatisk cachelagring aktiverad
imageView.load("https://example.com/image.jpg") {
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
Att förebygga lag är billigare än att åtgärda dem i produktion. Förebyggande åtgärder byggs in i utvecklingsprocessen på verktygs- och arkitekturnivå.
StrictMode — ett inbyggt Android-verktyg som upptäcker slumpmässiga in-/utdataoperationer och nätverksanrop på huvudtråden under utvecklingsfasen. Aktivera det i Application.onCreate med policy penaltyDeath för kritiska överträdelser. Detta är det enda sättet att garantera att utvecklaren ser problemet före commit.
Motsvarigheten för iOS — Main Thread Checker i Xcode, en del av Runtime Sanitization. Den kontrollerar automatiskt att alla UIKit- och AppKit-anrop utförs från huvudtråden. Aktivera den i Debug-byggschemat och uppnå noll varningar i CI.
Lägg till i CI-pipelinen att köra Macrobenchmark (Android) och XCTMetrics (iOS) för mätning av starttid, scroll-FPS och minnesanvändning. Sätt trösklar: om en ny commit ökar starttiden med mer än 5% — misslyckas bygget.
Vanliga frågor
Lag är en subjektiv känsla av fördröjning som kan uppstå även vid hög FPS om fördröjningen orsakas av bearbetningstiden för inmatning, inte rendering. Låg FPS (mindre än 30 bildrutor/s) — en av orsakerna till lag, men inte den enda.
Använd Frame Timing API på Android (Choreographer) och CADisplayLink på iOS för att mäta tiden mellan bildrutor. Google Play Vitals visar jank-frekvens under verkliga förhållanden. För noggranna mätningar, använd Macrobenchmark med scroll-scenarier.
Äldre enheter har färre CPU-kärnor, mindre RAM och långsammare minne. En operation som tar 5 ms på en flaggskeppstelefon kan ta 50 ms på en budgetenhet. Testa prestanda på lågprisenheter och ställ in Baseline Profiles för AOT-kompilering.
Ja, detta är en av de mest effektiva metoderna. Högupplösta bilder tar mycket minne och CPU-tid för avkodning. Använd downscale till View-storlek, formaten WebP (Android) och HEIC (iOS), samt cachelagring via Coil eller Kingfisher.
SwiftUI optimerar automatiskt uppdateringar genom diffing, vilket minskar risken för lag vid dataförändringar. Men komplexa hierarkier och frekventa ombyggnationer av body kan orsaka FPS-fall. UIKit ger mer kontroll över prestandan, men kräver manuell optimering.
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å