Pag-freeze (visnet) — ay isang estado kung saan ang mobile app ay tumitigil sa pagtugon sa anumang aksyon ng user sa mahabang panahon. Hindi tulad ng lag (pagbagal) at glitches (maling pag-uugali), ang pag-freeze ay ganap na hinaharangan ang UI: hindi pinoproseso ang mga pagpindot, humihinto ang animation, ang screen ay „nag-freeze". Ang dahilan — pag-block ng main thread ng isang synchronous na operasyon, deadlock sa multi-thread code, o abnormal na mahabang garbage collection. Ayon sa Apple Main Thread Checker Documentation, mahigit 40% ng mga crash report sa iOS ay nauugnay sa pag-block ng main thread. Sa Android, ang katulad na sitwasyon ay humahantong sa ANR — system dialog na „Hindi tumutugon ang app".
Mga pangunahing punto
Pag-freeze (freeze, hang) sa isang mobile app — ay isang estado kung saan ang app ay tumitigil sa pagproseso ng input events at pag-update ng interface sa loob ng ilang segundo o higit pa. Sa teknikal, ito ay nangangahulugan na ang main thread ay naka-block at hindi maisakatuparan ang susunod na runner cycle.
Lag — ay isang pagkaantala hanggang 500 ms, kung saan napapansin ng user ang pagbagal, ngunit patuloy na gumagana ang app. Pag-freeze ay tumatagal mula 1 segundo hanggang sampung segundo. ANR sa Android — ay isang espesyal na kaso ng pag-freeze na tumagal nang higit sa 5 segundo at natukoy ng system. Hindi lahat ng pag-freeze ay humahantong sa ANR, ngunit ang bawat ANR ay isang dokumentadong pag-freeze ng system.
Sa Android, ang pag-freeze ng higit sa 5 segundo ay nagdudulot ng ANR dialog na may mungkahing isara ang app. Sa iOS, ang system ay may watchdog — kung ang app ay hindi tumugon sa mga event sa loob ng 10–20 segundo, tinatapos ng Watchdog ang proseso gamit ang code 0x8badf00d (ate bad food). Nakikita lamang ng user ang biglaang pagsasara ng app at pagbabalik sa home screen.
Ang bawat operasyon na tumatagal ng higit sa 100 ms at pinapatakbo sa main thread ay potensyal na nagdudulot ng pag-freeze. Tingnan natin ang mga pangunahing pinagmumulan ng pag-block.
Pagbasa ng malaking file, network request na walang asynchrony, pag-save ng data sa SharedPreferences sa pamamagitan ng synchronous method na apply na sinusundan ng commit — lahat ng operasyong ito ay humaharang sa main thread. Sa Android, ang synchronous na pagbasa ng 10 MB file ay maaaring tumagal ng 200–500 ms depende sa bilis ng flash memory. Sa iOS, ang synchronous na URLSession load na walang completionHandler ay humaharang sa UI habang naghihintay ng tugon ng server.
Kapag ang dalawang thread ay naghihintay na pakawalan ang mga resources na hawak ng isa't isa, nangyayari ang deadlock. Sa mga mobile app, tipikal na senaryo — thread A ay humaharang sa Lock1 at naghihintay ng Lock2, habang thread B ay humaharang sa Lock2 at naghihintay ng Lock1. Parehong thread ay nag-freeze magpakailanman. Kung ang isa sa kanila ay ang main thread, ang app ay ganap na nag-freeze.
Error sa logic — halimbawa while(true) na walang exit condition o recursion na walang base case — humahantong sa walang katapusang pag-execute sa main thread. Android ay natutukoy ito sa pamamagitan ng ANR pagkalipas ng 5 segundo, iOS — sa pamamagitan ng Stackshot, na nagre-record ng walang katapusang umuulit na call stack.
Ang diagnosis ng pag-freeze ay nangangailangan ng mga tool na may kakayahang mag-record ng estado ng lahat ng thread sa sandali ng pag-block.
Sa bawat ANR, ang Android system ay nagse-save ng file na /data/anr/traces.txt, na naglalaman ng stack dump ng bawat thread ng app. Ang pagsusuri ng file na ito — pangunahing paraan ng diagnosis: hanapin ang main thread at tingnan kung saang metodo ito huminto. Kung ang stack ay nagtatapos sa Thread.sleep, InputStream.read o Lock.lock — natagpuan na ang dahilan.
Xcode kapag nag-freeze ang app (signal SIGSTOP) ay maaaring kumuha ng Stackshot — snapshot ng stacks ng lahat ng thread. I-on sa schema ang „Logging" → „Include Stackshot Logs". Sa crash na may code 0x8badf00d, kunin ang crash log mula sa Devices & Simulators at hanapin ang thread com.apple.main-thread na may frozen na stack.
Main Thread Checker ay awtomatikong nakakatuklas ng mga tawag sa UIKit mula sa background threads habang tumatakbo ang app. I-on ito sa schema (Diagnostics → Main Thread Checker). Ang bawat babala ay potensyal na dahilan ng pag-freeze, lalo na kung ito ay nangyayari sa pagsasara ng completionHandler ng network request.
Halimbawa ng pagtuklas ng pag-block sa pamamagitan ng StrictMode sa Android:
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())
}
}
Ang pag-aayos ng pag-freeze ay nagsisimula sa paglipat ng lahat ng potensyal na mahabang operasyon sa background threads. Tingnan natin ang mga tiyak na pamamaraan para sa bawat platform.
Kotlin Coroutines na may viewModelScope.launch(Dispatchers.IO) ay ginagarantiyahan na ang network operation o pagbasa ng database ay isinasagawa sa background thread. Ang Dispatchers.Main ay ginagamit lamang para sa pag-update ng UI. Mahalaga: lahat ng suspend functions ay dapat naka-structure — ang mga child coroutine ay kinakansela kapag kinakansela ang parent, pumipigil sa thread leak.
Grand Central Dispatch na may DispatchQueue.global(qos: .userInitiated) para sa background tasks at DispatchQueue.main.async para sa UI updates — karaniwang pattern. Iwasan ang sync() sa main queue — ito ay garantisadong deadlock. Gamitin ang async/await (Swift 5.5+) para sa mas madaling basahin na asynchronous code na may awtomatikong pagbabalik sa main thread sa pamamagitan ng MainActor.
Ang synchronized blocks sa Kotlin at @synchronized sa Swift sa main thread ay mapanganib: kung ang ibang thread ay nakuha na ang lock na ito, ang main thread ay mag-freeze sa paghihintay. Gamitin ang atomic types (AtomicInteger, atomic properties sa Swift) o sequential queues sa halip na locks.
Halimbawa ng asynchronous na pag-load ng data gamit ang coroutines sa Android:
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)
}
}
}
Ang pagpigil sa pag-freeze nang sistematiko ay tinutulungan ng kombinasyon ng mga tool, prinsipyo ng arkitektura, at proseso ng code review.
I-configure ang StrictMode na may penaltyDeath para sa thread policies — ito ay magdudulot ng agarang crash ng app kapag may nakitang network call o disk I/O sa main thread. Hindi maaaring balewalain ng developer ang problema. Sa production build, gamitin ang penaltyLog para sa pagkolekta ng statistics nang walang crashes.
Sa iOS, i-on ang Main Thread Checker sa Debug schema at i-configure ang CI na magpatakbo ng mga test gamit ang opsyong ito. Kung ang test ay naglalaman ng UIKit call mula sa background thread — dapat itong mabigo. Ito ang tanging maaasahang paraan upang matukoy ang problema bago ipadala sa TestFlight.
Magdagdag sa proseso ng code review ng mandatoryong punto: suriin na ang bawat network call, trabaho sa files, database, o mabibigat na computations ay isinasagawa sa background thread. Ang Deadlock ay maaaring matukoy ng static analyzer: Infer mula sa Facebook at Thread Safety Checker mula sa Xcode ay nakakahanap ng potensyal na pag-block bago pa man patakbuhin.
Mga madalas itanong
ANR (Application Not Responding) — ay isang system notification ng Android na lumalabas kapag nag-freeze ang main thread nang higit sa 5 segundo. Ang pag-freeze ay mas malawak na konsepto: anumang pag-block ng UI ng anumang tagal. Sa iOS ay walang ANR, ngunit mayroong Watchdog na may timeout na 10–20 segundo.
Ang file ay matatagpuan sa /data/anr/traces.txt. Para sa access, kinakailangan ang root o adb shell: isagawa ang adb shell cat /data/anr/traces.txt \> traces.txt na may root privileges. Sa stack, hanapin ang thread na „main" — ang huling tinawag na metodo ay nagpapahiwatig ng dahilan ng pag-block.
Kung ang pag-freeze ay tumatagal nang mas mababa sa 10 segundo, hindi nag-activate ang Watchdog, at ang app ay „nag-freeze" lamang hanggang matapos ang blocking operation. Hindi nakakakita ang user ng crash, ngunit nakakaranas ng pagkabigo. Para matukoy ang ganitong mga kaso, gamitin ang MetricKit na may custom na execution time traces.
Gumamit ng UI tests na may check na ang screen ay nagbubukas sa loob ng mas mababa sa 1 segundo. Magdagdag sa CI ng pagsukat ng oras sa pagitan ng pagpindot at paglitaw ng susunod na screen. Sa Android, gamitin ang Espresso na may IdlingResource para maghintay ng asynchronous operations. Sa iOS, XCTest na may XCTWaiter para suriin ang oras ng pag-load.
SwiftUI mismo ay hindi nagdudulot ng pag-freeze, ngunit ang mga kumplikadong computation sa body property — oo. Kung ang body ay nagko-compute nang 500 ms dahil sa mabibigat na operasyon, nag-freeze ang UI. Solusyon — ilipat ang computations sa Task.detached at i-update ang @State nang asynchronously sa main actor.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din