Lag v mobilní aplikaci je znatelné zpoždění mezi akcí uživatele a reakcí rozhraní, které vzniká kvůli přetížení hlavního vlákna, únikům paměti nebo neoptimálním operacím vstupu-výstupu. Na rozdíl od bugů souvisejících s logickými chybami, lag je problém výkonu: aplikace funguje správně, ale pomalu. Podle AppDynamics Mobile App Performance Report 2024 62 % uživatelů odstraňuje aplikaci, pokud zpomaluje déle než 3 sekundy. Diagnostika lagů vyžaduje profilování CPU, paměti a sítě pomocí Android Studio Profiler a Xcode Instruments.
Hlavní body
Lag (z anglického lag) v mobilní aplikaci je subjektivně postřehnutelné zpoždění mezi akcí uživatele (dotyk, přejetí, zadání textu) a reakcí rozhraní. Technicky se lag měří jako čas mezi vstupní událostí a úplným vykreslením snímku: komfortní práh — do 100 ms, znatelný — od 200 ms, kritický — více než 500 ms.
V uživatelské terminologii se „laguje” a „zpomaluje” často používají jako synonyma, ale technicky lag je pevné zpoždění (např. 300 ms při každém kliknutí), zatímco „zpomaluje” je nepravidelné zpomalení: aplikace někdy běží plynule, někdy zamrzne na vteřinu. Bug se na rozdíl od lagu netýká rychlosti, ale správnosti zobrazení.
Google Play a App Store berou v úvahu ukazatele výkonu při řazení aplikací. Míra ANR, frekvence jank a doba spouštění ovlivňují viditelnost ve vyhledávání a konverzi instalací. Aplikace s neustálými lagy ztrácí až 40 % uživatelů po prvním spuštění.
Lagy vznikají, když hlavní UI vlákno nestíhá zpracovávat snímky s frekvencí 60 FPS (16.6 ms na snímek) nebo 120 FPS (8.3 ms). Podívejme se na hlavní zdroje zpoždění.
Jakákoli synchronní operace v UI vlákně — čtení z SharedPreferences, práce s databází přes Room bez suspend, dekódování obrázku do Bitmap — blokuje vykreslení snímku. Na Androidu to vede k přeskakování snímků (jank), na iOS — ke zpoždění vykreslování Core Animation.
Když Garbage Collector na Androidu nebo ARC na iOS uvolňuje paměť, všechna vlákna se zastaví. Časté pauzy GC vznikají při vytváření velkého množství dočasných objektů — například při každém volání adaptéru seznamu se vytváří nová instance ViewHolder. To se projevuje jako trhané rolování.
Vnořené ConstraintLayout, vícenásobné LinearLayout, překrývající se View — každé vnoření zvyšuje čas measure a layout pass. Xcode uvádí, že hluboká hierarchie vrstev (více než 10 úrovní) způsobuje pokles FPS o 20-30 %.
Pro identifikaci příčin lagů se používají profilery vestavěné v IDE a nástroje systémového monitorování. Každý nástroj řeší svůj úkol.
CPU Profiler ukazuje, které metody zabírají čas procesoru a ve kterých vláknech jsou prováděny. Pokud je metoda s těžkými výpočty prováděna v main thread — to je kořen problému. Nahrávání trasování s zapnutým sample Java Method umožňuje vidět zásobník volání v každém okamžiku a najít „horká místa”.
Analogický nástroj pro iOS — Time Profiler — sbírá vzorky zásobníku každou milisekundu a ukazuje, jaké procento času CPU zabírá každá metoda. Kombinace s příznakem Main Thread Only filtruje pouze operace na hlavním vlákně, což přímo ukazuje na zdroje lagů.
Pomalé síťové požadavky vytvářejí dojem lagů, i když UI vlákno není blokováno. Network Profiler v Android Studio a Network Link Conditioner v Xcode umožňují simulovat pomalé připojení a zjistit, jak se aplikace chová v reálných podmínkách. Chunkované odpovědi bez průběhu a velké JSON náklady jsou typickými zdroji zdánlivých lagů.
Příklad profilování síťového požadavku s OkHttp s měřením času:
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("Časování", "Požadavek trval $duration ms")
return response
}
}
Odstranění lagů vyžaduje systematickou práci: od optimalizace jedné metody až po architektonické změny. Podívejme se na nejúčinnější techniky.
Kotlin Coroutines s dispečerem Dispatchers.IO pro síťové požadavky a Dispatchers.Default pro výpočty zaručují, že hlavní vlákno zůstává volné pro UI. Na iOS je Grand Central Dispatch s queue .global(qos: .userInitiated) pro úlohy na pozadí a .main pro aktualizace UI — standardní přístup. Vyhněte se sync operacím mezi frontami.
RecyclerView na Androidu a UICollectionView na iOS vyžadují správnou konfiguraci: ViewHolder s minimálním vytvářením objektů v onBindViewHolder, DiffUtil pro výpočet změn, prefetching pro předběžné načítání dat. Na iOS používejte diffable data source pro animované aktualizace bez ruční správy.
Načítání stejného obrázku při každém rolování — zaručený lag. Coil (Android) a Kingfisher (iOS) ukládají obrázky do cache v paměti a na disku, což zajišťuje okamžité zobrazení při opakovaném požadavku. Pro data používejte Room s cache vrstvou založenou na Flow nebo Combine.
Příklad konfigurace cache obrázků s Coil na Androidu:
val imageLoader = ImageLoader(context) {
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
crossfade(true)
size(512, 512)
}
// Načítání s povolenou automatickou cache
imageView.load("https://example.com/image.jpg") {
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
Předcházet lagům je levnější než je opravovat v produkci. Preventivní opatření se zabudovávají do procesu vývoje na úrovni nástrojů a architektury.
StrictMode — vestavěný nástroj Androidu, který detekuje náhodné operace vstupu-výstupu a síťová volání na hlavním vlákně ve fázi vývoje. Zapněte jej v Application.onCreate s politikou penaltyDeath pro kritická porušení. To je jediný způsob, jak zaručit, že vývojář uvidí problém před commitem.
Analog pro iOS — Main Thread Checker v Xcode, součást Runtime Sanitization. Automaticky kontroluje, že všechna volání UIKit a AppKit jsou prováděna z hlavního vlákna. Zapněte jej v sestavovacím schématu Debug a dosáhněte nulových varování v CI.
Přidejte do CI pipeline spouštění Macrobenchmark (Android) a XCTMetrics (iOS) pro měření doby spouštění, FPS rolování a využití paměti. Nastavte prahy: pokud nový commit zvýší dobu spouštění o více než 5 % — sestavení selže.
Často kladené otázky
Lag je subjektivní pocit zpoždění, který může nastat i při vysokém FPS, pokud je zpoždění způsobeno dobou zpracování vstupu, nikoli vykreslováním. Nízké FPS (méně než 30 snímků/s) — jedna z příčin lagů, ale ne jediná.
Použijte Frame Timing API na Androidu (Choreographer) a CADisplayLink na iOS pro měření času mezi snímky. Google Play Vitals ukazuje míru jank v reálných podmínkách. Pro přesná měření použijte Macrobenchmark se scénáři rolování.
Stará zařízení mají méně jader CPU, méně RAM a pomalejší paměť. Operace, která trvá 5 ms na vlajkové lodi, může na levném zařízení trvat 50 ms. Testujte výkon na zařízeních nižší třídy a nastavte Baseline Profiles pro AOT kompilaci.
Ano, to je jedna z nejúčinnějších metod. Obrázky ve vysokém rozlišení zabírají mnoho paměti a času CPU na dekódování. Používejte downscale na velikost View, formáty WebP (Android) a HEIC (iOS) a cache přes Coil nebo Kingfisher.
SwiftUI automaticky optimalizuje aktualizace pomocí diffingu, což snižuje riziko lagů při změně dat. Nicméně složité hierarchie a časté přestavby body mohou způsobit pokles FPS. UIKit poskytuje větší kontrolu nad výkonem, ale vyžaduje ruční optimalizaci.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také