Лаг у мобилној апликацији је приметно кашњење између радње корисника и реакције интерфејса, које настаје због преоптерећености главне нити, цурења меморије или неоптималних операција улаза-излаза. За разлику од грешака повезаних са логичким испадима, лаг је проблем перформанси: апликација ради исправно, али споро. Према AppDynamics Mobile App Performance Report 2024, 62% корисника брише апликацију ако успорава дуже од 3 секунде. Дијагностика лагова захтева профилисање CPU, меморије и мреже помоћу Android Studio Profiler и Xcode Instruments.
Главно
Лаг (од енгл. lag) у мобилној апликацији је субјективно приметно кашњење између радње корисника (додир, превлачење, унос текста) и реакције интерфејса. Технички, лаг се мери као време између улазног догађаја и потпуног рендеровања оквира: удобан праг — до 100 ms, приметан — од 200 ms, критичан — преко 500 ms.
У корисничкој терминологији „лагује” и „успорава” се често користе као синоними, али технички лаг је фиксно кашњење (нпр. 300 ms при сваком клику), а „успорава” — нестално успоравање: апликација понекад ради глатко, понекад се замрзне на секунд. Грешка, за разлику од лага, везана је не за брзину, већ за исправност приказа.
Google Play и App Store узимају у обзир показатеље перформанси при рангирању апликација. ANR стопа, учесталост џанка (jank) и време покретања утичу на видљивост у претрази и конверзију инсталација. Апликација са сталним лаговима губи до 40% корисника након првог покретања.
Лагови настају када главна UI нит не стигне да обради оквире са учесталошћу од 60 FPS (16.6 ms по оквиру) или 120 FPS (8.3 ms). Размотримо главне изворе кашњења.
Свака синхрона операција у UI нити — читање из SharedPreferences, рад са базом података преко Room без suspend, декодирање слике у Bitmap — блокира цртање оквира. На Android-у ово доводи до прескакања оквира (jank), на iOS-у — до кашњења рендеровања Core Animation.
Када Garbage Collector на Android-у или ARC на iOS-у ослобађа меморију, све нити се заустављају. Честе GC паузе настају при стварању великог броја привремених објеката — на пример, при сваком позиву адаптера листе ствара се нова инстанца ViewHolder. Ово се манифестује као трзаво скроловање.
Угнежђени ConstraintLayout, вишеструки LinearLayout, преклапајући View — свако угнежђење повећава време measure и layout pass-а. Xcode указује да дубока хијерархија слојева (више од 10 нивоа) изазива пад FPS за 20-30%.
За откривање узрока лагова користе се профилери уграђени у IDE и алати системског надзора. Сваки алат решава свој задатак.
CPU Profiler показује које методе заузимају процесорско време и у којим нитима се извршавају. Ако се метод са тешким прорачунима извршава у main thread-у — то је корен проблема. Снимак трасирања са укљученим sample Java Method омогућава да се види стек позива у сваком тренутку и пронађу „вруће тачке”.
Аналогни алат за iOS — Time Profiler — прикупља узорке стека сваке милисекунде и показује који проценат CPU времена заузима сваки метод. Комбинација са заставицом Main Thread Only филтрира само операције на главној нити, што директно указује на изворе лагова.
Спори мрежни захтеви стварају утисак лагова, чак и ако UI нит није блокирана. Network Profiler у Android Studio и Network Link Conditioner у Xcode омогућавају симулацију споре везе и откривање како се апликација понаша у реалним условима. Chunked одговори без напретка и велики JSON товари — типични извори привидних лагова.
Пример профилисања мрежног захтева са OkHttp уз мерење времена:
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("Тајминг", "Захтев је трајао $duration ms")
return response
}
}
Отклањање лагова захтева систематски рад: од оптимизације једног метода до архитектонских промена. Размотримо најефикасније технике.
Kotlin Coroutines са диспечером Dispatchers.IO за мрежне захтеве и Dispatchers.Default за прорачуне гарантују да главна нит остаје слободна за UI. На iOS-у Grand Central Dispatch са queue .global(qos: .userInitiated) за позадинске задатке и .main за ажурирање UI — стандардни приступ. Избегавајте sync операције између редова.
RecyclerView на Android-у и UICollectionView на iOS-у захтевају правилно подешавање: ViewHolder са минималним стварањем објеката у onBindViewHolder, DiffUtil за израчунавање промена, prefetching за раније учитавање података. На iOS-у користите diffable data source за анимирана ажурирања без ручног управљања.
Учитавање исте слике при сваком скроловању — гарантовани лаг. Coil (Android) и Kingfisher (iOS) кеширају слике у меморији и на диску, обезбеђујући тренутни приказ при поновном захтеву. За податке користите Room са кеширајућим слојем заснованим на Flow или Combine.
Пример подешавања кеширања слика са Coil на Android-у:
val imageLoader = ImageLoader(context) {
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
crossfade(true)
size(512, 512)
}
// Учитавање са укљученим аутоматским кеширањем
imageView.load("https://example.com/image.jpg") {
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
Спречавање лагова је јефтиније него њихово исправљање у продукцији. Превентивне мере се уграђују у процес развоја на нивоу алата и архитектуре.
StrictMode — уграђени Android алат који открива случајне операције улаза-излаза и мрежне позиве на главној нити у фази развоја. Укључите га у Application.onCreate са политиком penaltyDeath за критичне прекршаје. Ово је једини начин да се гарантује да ће програмер видети проблем пре комита.
Аналог за iOS — Main Thread Checker у Xcode, који је део Runtime Sanitization. Он аутоматски проверава да се сви позиви UIKit и AppKit извршавају из главне нити. Укључите га у шеми компилације Debug и постигните нула упозорења у CI.
Додајте у CI цевовод покретање Macrobenchmark (Android) и XCTMetrics (iOS) за мерење времена покретања, FPS скроловања и коришћења меморије. Поставите прагове: ако нови комит повећава време покретања за више од 5% — изградња пада.
Често постављана питања
Лаг је субјективни осећај кашњења који може настати и при високом FPS, ако је кашњење изазвано временом обраде улаза, а не рендеровања. Низак FPS (мање од 30 кадрова/с) — један од узрока лагова, али не и једини.
Користите Frame Timing API на Android-у (Choreographer) и CADisplayLink на iOS-у за мерење времена између кадрова. Google Play Vitals показује стопу jank у реалним условима. За прецизна мерења примените Macrobenchmark са сценаријима скроловања.
Стари уређаји имају мање CPU језгара, мање RAM-а и спорију меморију. Операција која се извршава за 5 ms на водећем моделу, на буџетном уређају може трајати 50 ms. Тестирајте перформансе на уређајима нижег сегмента и подесите Baseline Profiles за AOT компилацију.
Да, ово је један од најефикаснијих начина. Слике високе резолуције заузимају много меморије и CPU времена за декодирање. Користите downscale до величине View, формате WebP (Android) и HEIC (iOS), као и кеширање преко Coil или Kingfisher.
SwiftUI аутоматски оптимизује ажурирања кроз diffing, што смањује ризик од лагова при промени података. Међутим, сложене хијерархије и честа преправљања body могу изазвати пад FPS. UIKit даје већу контролу над перформансама, али захтева ручну оптимизацију.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође