Donma (visnet) — mobil proqramın istifadəçinin hər hansı hərəkətinə uzun müddət cavab verməməsi halıdır. Laqlar (yavaşlama) və qlitchlərdən (səhv davranış) fərqli olaraq, donma UI-ni tamamilə bloklayır: toxunuşlar işlənmir, animasiya dayanır, ekran „donur". Səbəb — əsas axının sinxron əməliyyatla bloklanması, çoxaxınlı kodda deadlock və ya anormal uzun zibil yığımıdır. Apple Main Thread Checker Documentation-nın məlumatına görə, iOS crash raportlarının 40%-dən çoxu əsas axının bloklanması ilə bağlıdır. Android-də oxşar vəziyyət ANR — „Proqram cavab vermir" sistem dialoquna gətirib çıxarır.
Əsas məqamlar
Donma (freeze, hang) mobil proqramda — proqramın bir neçə saniyə və daha çox müddət ərzində giriş hadisələrini emal etməyi və interfeysi yeniləməyi dayandırması halıdır. Texniki olaraq bu o deməkdir ki, əsas axın (main thread) bloklanıb və növbəti runner dövrünü icra edə bilmir.
Laq — 500 ms-ə qədər gecikmədir, bu zaman istifadəçi yavaşlama hiss edir, lakin proqram işləməyə davam edir. Donma 1 saniyədən onlarla saniyəyə qədər davam edir. Android-də ANR — 5 saniyədən çox davam edən və sistem tərəfindən aşkarlanan donmanın xüsusi halıdır. Hər donma ANR-yə gətirib çıxarmır, lakin hər ANR sənədləşdirilmiş donmadır.
Android-də 5 saniyədən uzun donma proqramı bağlamaq təklifi ilə ANR dialoqu yaradır. iOS-da sistem watchdoga malikdir — proqram 10–20 saniyə ərzində hadisələrə reaksiya vermirsə, Watchdog prosesi 0x8badf00d (ate bad food) kodu ilə bitirir. İstifadəçi proqramın qəfil bağlanmasını və ana ekrana qayıtmasını görür.
100 ms-dən uzun çəkən və əsas axında işə salınan hər hansı əməliyyat potensial olaraq donmaya səbəb olur. Blokadaların əsas mənbələrinə baxaq.
Böyük faylın oxunması, asinxron olmayan şəbəkə sorğusu, SharedPreferences-də apply və ardınca commit sinxron metodu ilə məlumatların saxlanması — bütün bu əməliyyatlar əsas axını bloklayır. Android-də 10 MB faylın sinxron oxunması flash yaddaşın sürətindən asılı olaraq 200–500 ms çəkə bilər. iOS-da completionHandler olmadan URLSession sinxron yüklənməsi server cavabı müddətində UI-ni bloklayır.
İki axın bir-birinin saxladığı resursların boşalmasını gözlədikdə deadlock yaranır. Mobil proqramlarda tipik ssenari — A axını Lock1-i bloklayır və Lock2-ni gözləyir, B axını isə Lock2-ni bloklayır və Lock1-i gözləyir. Hər iki axın əbədi donur. Əgər onlardan biri əsas axındırsa, proqram tamamilə donur.
Məntiq səhvi — məsələn, çıxış şərti olmayan while(true) və ya baza halı olmayan rekursiya — əsas axında sonsuz icraya gətirib çıxarır. Android bunu 5 saniyədən sonra ANR vasitəsilə aşkarlayır, iOS isə sonsuz təkrarlanan çağırış stackini qeyd edən Stackshot vasitəsilə.
Donmanın diaqnostikası blokada anında bütün axınların vəziyyətini qeyd edə bilən alətlər tələb edir.
Hər ANR zamanı Android sistemi proqramın hər bir axınının stack dumpini ehtiva edən /data/anr/traces.txt faylını saxlayır. Bu faylın təhlili — əsas diaqnostika üsuludur: main axınını tapmaq və onun hansı metoddə dayandığını görmək lazımdır. Stack Thread.sleep, InputStream.read və ya Lock.lock ilə bitirsə, səbəb tapılıb.
Xcode proqram donduqda (SIGSTOP siqnalı) Stackshot — bütün axınların stack şəklini çəkə bilər. Sxemdə „Logging" → „Include Stackshot Logs"-u aktivləşdirin. 0x8badf00d kodu ilə çökdükdə Devices & Simulators-dən crash loqunu çıxarın və donmuş stack ilə com.apple.main-thread axınını tapın.
Main Thread Checker proqramın işləməsi zamanı fon axınlarından UIKit çağırışlarını avtomatik aşkarlayır. Sxemdə aktivləşdirin (Diagnostics → Main Thread Checker). Hər xəbərdarlıq potensial donma səbəbidir, xüsusilə şəbəkə sorğusunun completionHandler bağlanmasında baş verərsə.
Android-də StrictMode vasitəsilə blokadanın aşkarlanması nümunəsi:
class App : Application() {
override fun onCreate() {
super.onCreate()
StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.build())
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyDeath()
.build())
}
}
Donmanın aradan qaldırılması bütün potensial uzun əməliyyatların fon axınlarına daşınması ilə başlayır. Hər platforma üçün konkret texnikalara baxaq.
Kotlin Coroutines viewModelScope.launch(Dispatchers.IO) ilə şəbəkə əməliyyatının və ya verilənlər bazası oxumasının fon axınında yerinə yetirilməsini təmin edir. Dispatchers.Main yalnız UI yeniləmək üçün istifadə olunur. Vacibdir: bütün suspend-funksiyalar strukturlaşdırılmış olmalıdır — uşaq korutinlar valideyn ləğv edildikdə ləğv edilir, axın sızmasının qarşısını alır.
Grand Central Dispatch fon tapşırıqları üçün DispatchQueue.global(qos: .userInitiated) və UI yeniləmək üçün DispatchQueue.main.async — standart nümunədir. Əsas növbədə sync()-dən qaçının — bu zəmanətli deadlockdur. Daha oxunaqlı asinxron kod üçün async/await (Swift 5.5+) istifadə edin, MainActor vasitəsilə əsas axına avtomatik qayıdışla.
Kotlin-də synchronized blokları və Swift-də @synchronized əsas axınında təhlükəlidir: əgər başqa bir axın artıq bu blokadanı ələ keçiribsə, əsas axın gözləmədə donacaq. Blokadalar əvəzinə atomik tiplərdən (AtomicInteger, Swift-də atomik xüsusiyyətlər) və ya ardıcıl növbələrdən istifadə edin.
Android-də korutinlər ilə asinxron məlumat yükləmə nümunəsi:
class DataViewModel : ViewModel() {
private val _data = MutableStateFlow<List<Item>>(emptyList())
val data: StateFlow<List<Item>> = _data.asStateFlow()
fun loadData() {
viewModelScope.launch(Dispatchers.IO) {
val result = fetchFromNetwork()
_data.emit(result)
}
}
}
Donmanın qarşısının alınması alətlər kombinasiyası, memarlıq prinsipləri və kod icmalı prosesləri ilə sistemli şəkildə kömək edir.
Axın siyasətləri üçün StrictMode-u penaltyDeath ilə konfiqurasiya edin — bu, əsas axında şəbəkə çağırışı və ya disk giriş-çıxışı aşkar edildikdə proqramın dərhal çökməsinə səbəb olacaq. Tərtibatçı problemi görməməzlikdən gələ bilməyəcək. İstehsal quruluşunda çökmədən statistik toplamaq üçün penaltyLog istifadə edin.
iOS-da Debug sxemində Main Thread Checker-ı aktivləşdirin və CI-ni bu seçimlə testləri işə salmaq üçün konfiqurasiya edin. Test fon axınından UIKit çağırışı ehtiva edərsə, uğursuz olmalıdır. Bu, TestFlight-a göndərməzdən əvvəl problemi aşkar etməyin yeganə etibarlı yoludur.
Kod icmalı prosesinə məcburi bənd əlavə edin: hər hansı şəbəkə çağırışının, fayllarla işin, verilənlər bazası və ya ağır hesablamaların fon axınında yerinə yetirildiyini yoxlayın. Deadlock statik analizatorla aşkar edilə bilər: Facebook-dan Infer və Xcode-dan Thread Safety Checker işə salınmadan əvvəl potensial blokadaları tapır.
Tez-tez verilən suallar
ANR (Application Not Responding) — Android-in sistem bildirişidir, əsas axının 5 saniyədən çox donması zamanı görünür. Donma daha geniş anlayışdır: istənilən müddətli UI blokadası. iOS-da ANR yoxdur, lakin 10–20 saniyə vaxt aşımı olan Watchdog var.
Fayl /data/anr/traces.txt ünvanında yerləşir. Giriş üçün root və ya adb shell tələb olunur: adb shell cat /data/anr/traces.txt \> traces.txt root hüquqları ilə yerinə yetirin. Stackdə „main" axınını tapın — son çağırılan metod blokadanın səbəbini göstərir.
Donma 10 saniyədən az davam edərsə, Watchdog işə düşmür və proqram sadəcə blokada əməliyyatı bitənə qədər „donur". İstifadəçi çökmə görmür, lakin məyusluq yaşayır. Belə halları aşkar etmək üçün MetricKit-dən icra müddətinin xüsusi izləri ilə istifadə edin.
Ekranın 1 saniyədən az müddətdə açıldığını yoxlayan UI testləri istifadə edin. CI-ə toxunma və növbəti ekranın görünməsi arasında vaxt ölçməsi əlavə edin. Android-də asinxron əməliyyatları gözləmək üçün IdlingResource ilə Espresso istifadə edin. iOS-da yükləmə vaxtını yoxlamaq üçün XCTWaiter ilə XCTest.
SwiftUI özlüyündə donmaya səbəb olmur, lakin body xassəsində mürəkkəb hesablamalar — bəli. Body ağır əməliyyatlar səbəbindən 500 ms hesablanırsa, UI donur. Həll yolu — hesablamaları Task.detached-ə daşımaq və @State-i əsas aktyorda asinxron yeniləməkdir.
Xülasə
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