Зависање у развоју — суштина, узроци и превенција

Аутор: IT Sectr Објављено: 2026-07-28 Време читања: 9 мин

Зависање (виснет) — стање у којем мобилна апликација престаје да реагује на било које радње корисника на дужи временски период. За разлику од лагова (успоравање рада) и гличeва (неисправно понашање), зависање потпуно блокира UI: додири се не обрађују, анимација се зауставља, екран „замрзава". Узрок — блокирање главне нити синхроном операцијом, deadlock у вишенитном коду или аномално дуго прикупљање смећа. Према Apple Main Thread Checker Documentation, преко 40% crash извештаја на iOS-у је повезано са блокирањем главне нити. На Android-у аналогна ситуација доводи до ANR-а — системског дијалога „Апликација не одговара".

Главне тачке

  • Зависање — потпуно блокирање UI на дужи временски период (секунде и десетине секунди), различито од лагова и гличeва
  • Главни узроци — блокирање главне нити улазно-излазним операцијама, deadlock између нити, бесконачна петља и цурење меморије са дугим GC
  • Дијагностика укључује Main Thread Checker на iOS-у, ANR логове /data/anr/traces.txt на Android-у и анализу думпова нити
  • Отклањање — премештање свих потенцијално дугих операција у позадинске нити, коришћење Structured Concurrency и избегавање synchronized у UI нити
  • Превенција — StrictMode, Main Thread Checker у Debug шеми, статичка анализа на deadlock и периодично покретање тестова са мерењем времена одговора

Шта је зависање у мобилном развоју

Зависање (freeze, hang) у мобилној апликацији — стање у којем апликација престаје да обрађује догађаје уноса и ажурира интерфејс током неколико секунди или дуже. Технички, то значи да је главна нит (main thread) блокирана и не може да изврши следећи циклус рунера.

Разлика између зависања, лага и ANR-а

Лаг — кашњење до 500 ms, при којем корисник примећује успоравање, али апликација наставља да ради. Зависање траје од 1 секунде до десетина секунди. ANR на Android-у — посебан случај зависања које је трајало дуже од 5 секунди и откривено је од стране система. Не свако зависање доводи до ANR-а, али сваки ANR је документовано зависање од стране система.

Последице зависања

На Android-у зависање дуже од 5 секунди изазива ANR дијалог са предлогом да затворите апликацију. На iOS-у систем има watchdog — ако апликација не реагује на догађаје у року од 10–20 секунди, Watchdog завршава процес кодом 0x8badf00d (ate bad food). Корисник види само изненадно затварање апликације и повратак на главни екран.

Узроци зависања на Android-у и iOS-у

Свака операција која траје дуже од 100 ms и покренута је у главној нити потенцијално изазива зависање. Размотримо главне изворе блокирања.

Синхрони улазно-излаз у UI нити

Читање велике датотеке, мрежни захтев без асинхроности, чување података у SharedPreferences синхроном методом apply са накнадним commit — све ове операције блокирају главну нит. На Android-у синхроно читање датотеке величине 10 MB може трајати 200–500 ms у зависности од брзине флеш меморије. На iOS-у синхроно учитавање URLSession-а без completionHandler блокира UI на време одговора сервера.

Deadlock у вишенитном коду

Када две нити чекају ослобађање ресурса које држе једна другој, настаје deadlock. У мобилним апликацијама типичан сценарио — нит A блокира Lock1 и чека Lock2, а нит B блокира Lock2 и чека Lock1. Обе нити се заувек замрзавају. Ако је једна од њих главна нит, апликација се потпуно замрзава.

Бесконачна петља или рекурзија

Грешка у логици — на пример, while(true) без услова изласка или рекурзија без базног случаја — доводи до бесконачног извршавања на главној нити. Android ово открива путем ANR-а након 5 секунди, iOS — путем Stackshot-а, који бележи бесконачно понављајући стек позива.

  • Android — Cursor без затварања, синхрони захтев путем execute() уместо enqueue(), FileInputStream.read() у UI нити
  • iOS — performSelectorOnMainThread:withObject:waitUntilDone:YES, покретање NSURLConnection sendSynchronousRequest, учитавање слике са dataWithContentsOfURL
  • Крос-платформа — Flutter compute без издвојеног isolate, React Native синхрони NativeModule

Како дијагностиковати зависање

Дијагностика зависања захтева алате способне да забележе стање свих нити у тренутку блокирања.

ANR логови на Android-у

При сваком ANR-у систем Android чува датотеку /data/anr/traces.txt, која садржи думп стека сваке нити апликације. Анализа ове датотеке — главни метод дијагностике: потребно је пронаћи нит main и видети на којој методи се зауставила. Ако се стек завршава са Thread.sleep, InputStream.read или Lock.lock — узрок је пронађен.

Stackshot на iOS-у

Xcode при замрзавању апликације (сигнал SIGSTOP) може да сними Stackshot — снимак стекова свих нити. Укључите у шеми „Logging" → „Include Stackshot Logs". При паду са кодом 0x8badf00d извуците crash лог из Devices & Simulators и пронађите нит com.apple.main-thread са замрзнутим стеком.

Main Thread Checker у Xcode-у

Main Thread Checker аутоматски открива позиве UIKit-а из позадинских нити током рада апликације. Укључите га у шеми (Diagnostics → Main Thread Checker). Свако упозорење је потенцијални узрок зависања, посебно ако се јавља у затварању completionHandler-а мрежног захтева.

Пример откривања блокирања путем StrictMode-а на Android-у:

kotlin
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())
    }
}

Методе отклањања блокирања UI

Отклањање зависања почиње премештањем свих потенцијално дугих операција у позадинске нити. Размотримо конкретне технике за сваку платформу.

Structured Concurrency са корутинама

Kotlin Coroutines са viewModelScope.launch(Dispatchers.IO) гарантују да се мрежна операција или читање из базе података извршавају у позадинској нити. Dispatchers.Main се користи само за ажурирање UI. Важно: све suspend функције морају бити структурисане — подређене корутине се отказују при отказивању родитељске, спречавајући цурење нити.

Асинхрони редови на iOS-у

Grand Central Dispatch са DispatchQueue.global(qos: .userInitiated) за позадинске задатке и DispatchQueue.main.async за ажурирање UI — стандардни образац. Избегавајте sync() на главном реду — то је загарантовани deadlock. Користите async/await (Swift 5.5+) за читљивији асинхрони код са аутоматским повратком на главну нит путем MainActor-а.

Избегавање synchronized у UI нити

Блокови synchronized у Kotlin-у и @synchronized у Swift-у на главној нити су опасни: ако је друга нит већ преузела ово закључавање, главна нит ће се замрзнути у чекању. Користите атомичке типове (AtomicInteger, атомичка својства у Swift-у) или секвенцијалне редове уместо закључавања.

Пример асинхроног учитавања података са корутинама на Android-у:

kotlin
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)
        }
    }
}

Превенција зависања у фази развоја

Спречавање зависања систематски помаже комбинацијом алата, архитектонских принципа и процеса прегледа кода.

StrictMode са penaltyDeath

Подесите StrictMode са penaltyDeath за политике нити — то ће довести до тренутног пада апликације при откривању мрежног позива или улазно-излазне операције на диску у главној нити. Програмер неће моћи да игнорише проблем. У продукцијској верзији користите penaltyLog за прикупљање статистике без падова.

Main Thread Checker у Debug шеми

На iOS-у укључите Main Thread Checker у Debug шеми и подесите CI да покреће тестове са овом опцијом. Ако тест садржи позив UIKit-а из позадинске нити — треба да падне. Ово је једини поуздан начин да се открије проблем пре слања у TestFlight.

Преглед кода са провером вишенитности

Додајте у процес прегледа кода обавезну ставку: проверу да се сваки мрежни позив, рад са датотекама, базом података или тешка израчунавања извршавају у позадинској нити. Deadlock се може открити статичким анализатором: Infer од Facebook-а и Thread Safety Checker од Xcode-а проналазе потенцијална блокирања пре покретања.

  • Android — StrictMode, Infer, Android Lint Multithread, Kotlin Coroutines са viewModelScope
  • iOS — Main Thread Checker, TSAN (Thread Sanitizer), Xcode Analyze, Swift async/await са MainActor
  • Крос-платформа — Flutter compute isolate, React Native interaction manager са requestAnimationFrame

Често постављана питања

Која је разлика између зависања и ANR-а?

ANR (Application Not Responding) — системско обавештење Android-а које се појављује при замрзавању главне нити дуже од 5 секунди. Зависање је шири појам: било које блокирање UI било ког трајања. На iOS-у нема ANR-а, али постоји Watchdog са тајмаутом од 10–20 секунди.

Како прочитати traces.txt на Android-у?

Датотека се налази у /data/anr/traces.txt. За приступ је потребан root или adb shell: извршите adb shell cat /data/anr/traces.txt \> traces.txt са root правима. У стеку пронађите нит „main" — последња позвана метода указује на узрок блокирања.

Зашто апликација замрзава на iOS-у, али не пада?

Ако замрзавање траје мање од 10 секунди, Watchdog се не активира и апликација једноставно „виси" до завршетка блокирајуће операције. Корисник не види пад, али доживљава фрустрацију. За откривање оваквих случајева користите MetricKit са прилагођеним траговима времена извршавања.

Како тестирати апликацију на замрзавање?

Користите UI тестове са провером да се екран отвара за мање од 1 секунде. Додајте у CI мерење времена између додира и појављивања следећег екрана. На Android-у користите Espresso са IdlingResource за чекање асинхроних операција. На iOS-у XCTest са XCTWaiter за проверу времена учитавања.

Може ли SwiftUI изазвати замрзавање?

SwiftUI сам по себи не изазива замрзавање, али сложена израчунавања у својству body — да. Ако се body израчунава 500 ms због тешких операција, UI се замрзава. Решење — преместите израчунавања у Task.detached и ажурирајте @State асинхроно на главном актеру.

Резиме

  • Зависање — потпуно блокирање UI на секунде и десетине секунди, узроковано блокирањем главне нити, deadlock-ом или бесконачном петљом
  • Дијагностика — /data/anr/traces.txt на Android-у, Stackshot и Main Thread Checker на iOS-у
  • Главни узроци — синхрони улазно-излаз, deadlock између нити, бесконачна рекурзија, дуги GC
  • Отклањање — корутине са правим диспечерима, async/await са MainActor, премештање свих IO операција у позадинске нити
  • Превенција — StrictMode са penaltyDeath, Main Thread Checker, статичка анализа deadlock-а (Infer, TSAN)
  • На Android-у замрзавање > 5 с = ANR; на iOS-у > 10–20 с = Watchdog crash (0x8badf00d)
  • Препорука: укључите Thread Sanitizer у Debug шеми и подесите CI за покретање тестова са TSAN-ом за откривање data race и deadlock-а

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође