Tutma — bu, mobil tətbiq yavaş və qeyri-sabit işlədikdə istifadəçinin vəziyyəti təsvir etməsidir: bəzən normal cavab verir, bəzən birdən bir neçə saniyə donur. Texniki kontekstdə “tutma” GC pauzaları, sinxron əməliyyatlarla əsas axının bloklanması və qeyri-optimal məlumat strukturları nəticəsində yaranan laqların və mikro-donmaların kombinasiyası deməkdir. Android Performance Benchmarking Guide məlumatına görə, cavab müddətinin 300 ms-dən 100 ms-ə endirilməsi istifadəçi saxlanmasını 25% artırır. Tutmanın diaqnostikası CPU və Memory profilləşdirməsinin zibil yığımı tezliyinin təhlili ilə birləşdirilməsini tələb edir.
Əsas məqamlar
Tutma — istifadəçilərin subyektiv olaraq yavaş tətbiq işini təsvir etdiyi qeyri-rəsmi termindir. Daimi gecikmə kimi özünü göstərən laqdan fərqli olaraq, tutma qeyri-müntəzəm donmalardır: tətbiq bir neçə saniyə mükəmməl işləyə bilər, sonra 1–3 saniyə “düşünə” bilər.
Profilləşdirmə baxımından tutma 100 ms-dən çox pik gecikmələri olan buraxılmış kadrlar seriyası (jank) kimi özünü göstərir. FPS qrafikində bu kəskin enişlər kimi görünür: 60 → 20 → 55 → 10 kadr/saniyə. Vahid aşağı FPS-li laqdan fərqli olaraq, tutma aydın dəyişkənliyə malikdir.
Tətbiq tutduqda, istifadəçi yavaşlamaların məntiqini başa düşmür: ekran hamar sürüşə bilər, sonra birdən bir saniyə dayana bilər. Bu, məyusluğa səbəb olur və tətbiqə inamı azaldır. Google məlumatına görə, istifadəçilərin 53%-i yükləmə 3 saniyədən çox çəkərsə, saytı və ya tətbiqi tərk edir.
Tutmanın qeyri-müntəzəm xarakteri problemin daimi yüklənmədən deyil, hadisə faktorlarından qaynaqlandığını göstərir. Tipik ssenariləri nəzərdən keçirək.
Android-də ART mühitində zibil yığımı tətbiqin bütün axınlarını dayandırır. Əgər kodda çoxlu müvəqqəti obyekt yaradılırsa — məsələn, hər onBindViewHolder çağırışında konkatenasiya ilə yeni String yaradılırsa — GC daha tez-tez işə düşür. Pauza yığın ölçüsü və obyekt nəslindən asılı olaraq 5–50 ms davam edə bilər. İstifadəçi bunu qəfil “düşünmə” kimi hiss edir.
Room Android-də və Core Data iOS-da asinxron sorğuları dəstəkləyir, lakin proqramçılar sadəlik üçün tez-tez getValue() çağırır və ya runBlocking ilə sorğu yerinə yetirirlər. 10 000 sətirli cədvəldə join-lərlə ağır SELECT 200–500 ms çəkə bilər və UI-ni tamamilə bloklaya bilər.
Kamera şəklinin (12 Mp, 4000x3000 px) miqyaslanmadan yüklənməsi Bitmap-ə dekodlanmaq üçün 200 ms-ə qədər çəkir. Şəkillər asinxron yüklənirsə, lakin məhdudiyyətli axın hovuzu olmadan, 5–6 dekodlamanın eyni vaxtda işə salınması CPU-nu həddən artıq yükləyərək miqrasiya edən yavaşlamalara səbəb ola bilər.
Qeyri-müntəzəm yavaşlamaların diaqnostikası daimi laqların diaqnostikasından çətindir, çünki problem hər işə salınmada təkrarlanmaya bilər. Uzun müddət ərzində statistika toplanması tələb olunur.
Android Studio Memory Profiler təkcə yaddaş istifadəsini deyil, həm də GC hadisələrini göstərir: tezlik, tip (Concurrent, Full), müddət. Əgər GC sakit vəziyyətdə 5 saniyədə 1 dəfədən çox baş verirsə — bu, həddindən artıq alokasiya əlamətidir. Tutma anında heap dump qeydi hansı obyektlərin yaddaşı tutduğunu görməyə imkan verir.
iOS-da obyektlərin yaradılmasını və buraxılmasını izləmək üçün Instruments-də Allocations şablonundan istifadə edin. Generasiyaları (Generations) aktivləşdirin — onlar hərəkətlər arasında yığın şəkillərini çəkməyə və hansı obyektlərin yaddaşda qaldığını görməyə imkan verir. Buraxılmayan Persistent objects — yaddaş yığılması və sonrakı pauzaların mənbəyidir.
JankStats — real vaxtda buraxılmış kadr metrikalarını toplayan Android kitabxanasıdır. Hər jank-ı cari ssenariyə (məsələn, “siyahının sürüşdürülməsi”, “ekranın açılması”) bağlayır ki, bu da tutmanın hansı hərəkətdə baş verdiyini anlamağa imkan verir.
Android-də donmaları izləmək üçün JankStats inteqrasiyası nümunəsi:
class MainActivity : AppCompatActivity() {
private lateinit var jankStats: JankStats
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
jankStats = JankStats.create(this.window.decorView) { frameData ->
if (frameData.isJank()) {
Log.w("Tutma", "Müddət=${frameData.durationMs}ms")
}
}
}
}
Tutmanın aradan qaldırılması hər səbəblə hədəflənmiş iş tələb edir. Universal həll yoxdur — konkret performans profillərinin təhlili lazımdır.
Siyahıda 1000+ element varsa və hamısı birdən yüklənirsə — bu zəmanətli tutmadır. Android-də Paging 3 və iOS-da NSFetchedResultsController məlumatları sürüşdürmə zamanı hissə-hissə yükləyir. İstifadəçi yalnız ilk 10–20 elementi görür, qalanları fonda yüklənir.
Room Android Studio-da Inspection Tool vasitəsilə sorğuları profilləşdirməyə imkan verir: icra müddəti, qaytarılan sətirlərin sayı və sorğu planı görünür. WHERE və ORDER BY sütunlarına indekslərin əlavə edilməsi sorğu müddətini 300 ms-dən 5 ms-ə endirə bilər. iOS-da analoji yoxlama Instruments-də Core Data Profiler tərəfindən aparılır.
Fon sinxronizasiyaları, fayl yükləmələri, məlumat emalı — bütün bunlar WorkManager (Android) və ya Background Tasks (iOS) vasitəsilə yerinə yetirilməlidir. Sinxronizasiya UI axınında işə salınarsa, tətbiq icra müddətində tutacaq. WorkManager batareya və şəbəkə vəziyyəti nəzərə alınmaqla fon axınında icraya zəmanət verir.
Android-də WorkManager vasitəsilə fon sinxronizasiyası nümunəsi:
class SyncWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
Log.d("Sync", "Fon iplikdə məlumatların sinxronizasiyası")
syncData()
Result.success()
} catch (e: Exception) {
Result.retry()
}
}
}
Tutmanın qarşısını kod yazma mərhələsində, yaddaş və axınlarla effektiv iş prinsiplərinə riayət etməklə almaq olar.
Baseline Profiles — Android-in əvvəlcədən (AOT) kompilə etdiyi sinif və metodların siyahısıdır. Profil olmadan hər yeni ekran ilk açılışda kompilə olunur və 100–500 ms gecikməyə səbəb olur. Əsas ekranlar üçün Baseline Profile hazırlayın və baseline-profile-gradle-plugin vasitəsilə Gradle-də generasiyanı aktivləşdirin.
Hot path — hər kadrda icra olunan koddur: onBindViewHolder, draw, layoutSubviews. Bu metodlarda obyekt yaratmaqdan çəkinin: obyekt hovuzundan istifadə edin, konkatenasiya əvəzinə StringBuilder, formatlanmış sətirləri və formatları keşləyin. Hər əlavə alokasiya növbəti GC-ni yaxınlaşdırır.
CI pipeline-na siyahı sürüşdürmə və ekran açma ssenarisi ilə Macrobenchmark işə salınmasını əlavə edin. Hədd təyin edin: kadr vaxtının 99-cu persentili 16 ms-dən çox olmamalıdır. Hədd aşılırsa, optimallaşdırmaya qədər build rədd edilir.
Tez-tez verilən suallar
Laq — daimi gecikmədir (məsələn, hər toxunuşda 200 ms). Tutma — qeyri-müntəzəm: tətbiq normal işləyir, sonra birdən 1–3 saniyə yavaşlayır, sonra yenə normal. Səbəb — GC pauzaları və ya verilənlər bazasına sinxron sorğular kimi hadisə faktorları.
Android Studio-da Memory Profiler istifadə edin: Memory nişanı GC hadisələrini müddəti ilə göstərir. İstehsalat monitorinqi üçün xüsusi treyslərlə Firebase Performance Monitoring qoşun. iOS-da Malloc Debug aktivləşdirin və Instruments-də alokasiya nəsillərini qeyd edin.
Dolayı — bəli. Server cavabı gecikmə ilə gəlirsə və UI onu sinxron gözləyirsə, tətbiq donur. Sorğu asinxrondursa, lakin cavab emalı UI axınında aparılırsa — bu da tutmaya səbəb olacaq. Həll — korutinlər və gedişat göstəriciləri ilə asinxron emal.
Düzgün istifadə edilmədikdə KMP qarşılıqlı işləmə üçün artıq sarğı obyektləri yarada bilər. iOS-da bu, alokasiya tezliyini və nəticədə ARC pauzalarını artırır. @ObjCName istifadə edin, expect/actual optimallaşdırın və UI-nin isti yollarından paylaşılan koda tez-tez müraciətlərdən çəkinin.
android:largeHeap="true" ilə yığını artırmaq GC-ni təxirə salır, lakin alokasiya səbəbini aradan qaldırmır. GC nəhayət işə düşəndə pauza daha uzun olacaq, çünki daha çox obyekti keçmək lazımdır. Həll — yığını genişləndirmək deyil, alokasiyaların sayını azaltmaqdır.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun