फ़्रीज़ (हैंग) एक ऐसी स्थिति है जिसमें मोबाइल ऐप्लिकेशन लंबे समय तक उपयोगकर्ता की किसी भी कार्रवाई पर प्रतिक्रिया देना बंद कर देता है। लैग (धीमापन) और ग्लिच (गलत व्यवहार) के विपरीत, फ़्रीज़ UI को पूरी तरह से ब्लॉक कर देता है: टच प्रोसेस नहीं होते, एनिमेशन रुक जाता है, स्क्रीन “जम जाती है”। कारण है सिंक्रोनस ऑपरेशन द्वारा मुख्य थ्रेड का ब्लॉक होना, मल्टीथ्रेडेड कोड में डेडलॉक, या असामान्य रूप से लंबा गार्बेज कलेक्शन। Apple Main Thread Checker दस्तावेज़ीकरण के अनुसार, iOS के 40% से अधिक क्रैश रिपोर्ट मुख्य थ्रेड ब्लॉकिंग से संबंधित हैं। Android पर, ऐसी ही स्थिति ANR — सिस्टम डायलॉग “ऐप रिस्पॉन्ड नहीं कर रहा” — की ओर ले जाती है।
मुख्य बिंदु
फ़्रीज़ (हैंग) मोबाइल ऐप्लिकेशन में एक ऐसी स्थिति है जहाँ ऐप कई सेकंड या उससे अधिक समय तक इनपुट इवेंट प्रोसेस करना और इंटरफ़ेस अपडेट करना बंद कर देता है। तकनीकी रूप से, इसका मतलब है कि मुख्य थ्रेड ब्लॉक हो गया है और अगला रन लूप इटरेशन निष्पादित नहीं कर सकता।
लैग 500 ms तक की देरी है जिसमें उपयोगकर्ता धीमापन नोटिस करता है लेकिन ऐप काम करता रहता है। फ़्रीज़ 1 सेकंड से लेकर दसियों सेकंड तक रहता है। Android पर ANR फ़्रीज़ का एक विशेष मामला है जो 5 सेकंड से अधिक समय तक रहा और सिस्टम द्वारा डिटेक्ट किया गया। हर फ़्रीज़ ANR नहीं लाता, लेकिन हर ANR एक सिस्टम-डॉक्यूमेंटेड फ़्रीज़ है।
Android पर, 5 सेकंड से अधिक का फ़्रीज़ ANR डायलॉग ट्रिगर करता है जो ऐप बंद करने का सुझाव देता है। iOS पर, सिस्टम में एक वॉचडॉग है — अगर ऐप 10–20 सेकंड तक इवेंट पर प्रतिक्रिया नहीं देता, तो वॉचडॉग प्रोसेस को कोड 0x8badf00d (ate bad food) के साथ समाप्त कर देता है। उपयोगकर्ता केवल ऐप को अचानक होम स्क्रीन पर बंद होते देखता है।
कोई भी ऑपरेशन जो 100 ms से अधिक समय लेता है और मुख्य थ्रेड पर चलाया जाता है, संभावित रूप से फ़्रीज़ का कारण बन सकता है। आइए ब्लॉकेज के मुख्य स्रोतों को देखें।
बड़ी फ़ाइल पढ़ना, बिना एसिंक्रोनी के नेटवर्क रिक्वेस्ट, SharedPreferences में सिंक्रोनस apply विधि के बाद commit द्वारा डेटा सेव करना — ये सभी ऑपरेशन मुख्य थ्रेड को ब्लॉक करते हैं। Android पर, 10 MB फ़ाइल को सिंक्रोनस रूप से पढ़ने में फ़्लैश मेमोरी स्पीड के आधार पर 200–500 ms लग सकते हैं। iOS पर, completionHandler के बिना सिंक्रोनस URLSession लोड सर्वर प्रतिक्रिया समय के लिए UI को ब्लॉक करता है।
जब दो थ्रेड एक-दूसरे द्वारा धारित संसाधनों की प्रतीक्षा करते हैं, तो डेडलॉक होता है। मोबाइल ऐप्लिकेशन में, एक विशिष्ट परिदृश्य है: थ्रेड A Lock1 को लॉक करता है और Lock2 की प्रतीक्षा करता है, जबकि थ्रेड B Lock2 को लॉक करता है और Lock1 की प्रतीक्षा करता है। दोनों थ्रेड हमेशा के लिए फ़्रीज़ हो जाते हैं। यदि उनमें से एक मुख्य थ्रेड है, तो ऐप पूरी तरह फ़्रीज़ हो जाता है।
लॉजिक में त्रुटि — उदाहरण के लिए, बिना exit कंडीशन के while(true) या बिना बेस केस के रिकर्शन — मुख्य थ्रेड पर अनंत निष्पादन की ओर ले जाती है। Android इसे 5 सेकंड के बाद ANR के माध्यम से डिटेक्ट करता है, iOS — स्टैकशॉट के माध्यम से, जो अनंत रूप से दोहराए जाने वाले कॉल स्टैक को कैप्चर करता है।
फ़्रीज़ के निदान के लिए ऐसे उपकरण चाहिए जो ब्लॉकेज के क्षण में सभी थ्रेड्स की स्थिति को कैप्चर कर सकें।
प्रत्येक ANR पर, Android सिस्टम एक फ़ाइल /data/anr/traces.txt सेव करता है जिसमें प्रत्येक ऐप थ्रेड का स्टैक डंप होता है। इस फ़ाइल का विश्लेषण मुख्य निदान विधि है: main थ्रेड खोजें और देखें कि वह किस विधि पर रुका है। यदि स्टैक Thread.sleep, InputStream.read या Lock.lock पर समाप्त होता है — कारण मिल गया।
Xcode ऐप के फ़्रीज़ होने पर (SIGSTOP सिग्नल) स्टैकशॉट — सभी थ्रेड स्टैक का स्नैपशॉट — ले सकता है। स्कीम में “Logging” → “Include Stackshot Logs” सक्षम करें। कोड 0x8badf00d के साथ क्रैश पर, Devices & Simulators से क्रैश लॉग निकालें और अटके स्टैक के साथ com.apple.main-thread खोजें।
Main Thread Checker ऐप चलने के दौरान बैकग्राउंड थ्रेड से UIKit कॉल को स्वचालित रूप से डिटेक्ट करता है। इसे स्कीम में सक्षम करें (Diagnostics → Main Thread Checker)। प्रत्येक चेतावनी फ़्रीज़ का संभावित कारण है, विशेषकर यदि यह नेटवर्क रिक्वेस्ट completionHandler क्लोज़र में होती है।
Android पर StrictMode के माध्यम से ब्लॉकेज डिटेक्शन का उदाहरण:
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())
}
}
फ़्रीज़ को खत्म करना सभी संभावित लंबी कार्रवाइयों को बैकग्राउंड थ्रेड्स पर ले जाने से शुरू होता है। आइए प्रत्येक प्लेटफ़ॉर्म के लिए विशिष्ट तकनीकें देखें।
Kotlin Coroutines viewModelScope.launch(Dispatchers.IO) के साथ गारंटी देते हैं कि नेटवर्क ऑपरेशन या डेटाबेस रीड बैकग्राउंड थ्रेड पर निष्पादित हों। Dispatchers.Main का उपयोग केवल UI अपडेट के लिए किया जाता है। महत्वपूर्ण: सभी suspend फ़ंक्शन स्ट्रक्चर्ड होने चाहिए — चाइल्ड कोरूटीन पैरेंट के कैंसल होने पर कैंसल हो जाते हैं, जिससे थ्रेड लीक रुकता है।
Grand Central Dispatch बैकग्राउंड कार्यों के लिए DispatchQueue.global(qos: .userInitiated) और UI अपडेट के लिए DispatchQueue.main.async के साथ मानक पैटर्न है। मुख्य क्यू पर sync() से बचें — यह गारंटीड डेडलॉक है। अधिक पठनीय एसिंक्रोनस कोड के लिए MainActor के माध्यम से मुख्य थ्रेड पर स्वचालित वापसी के साथ async/await (Swift 5.5+) का उपयोग करें।
मुख्य थ्रेड पर Kotlin में synchronized ब्लॉक और Swift में @synchronized खतरनाक हैं: यदि किसी अन्य थ्रेड ने पहले ही यह लॉक प्राप्त कर लिया है, तो मुख्य थ्रेड प्रतीक्षा में फ़्रीज़ हो जाएगा। लॉक के बजाय एटॉमिक प्रकार (AtomicInteger, Swift में एटॉमिक प्रॉपर्टी) या सीरियल क्यू का उपयोग करें।
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)
}
}
}
उपकरणों, आर्किटेक्चर सिद्धांतों और कोड रिव्यू प्रक्रियाओं का संयोजन व्यवस्थित रूप से फ़्रीज़ को रोकने में मदद करता है।
थ्रेड पॉलिसियों के लिए StrictMode को penaltyDeath के साथ कॉन्फ़िगर करें — इससे मुख्य थ्रेड पर नेटवर्क कॉल या डिस्क I/O डिटेक्ट होने पर तत्काल ऐप क्रैश हो जाएगा। डेवलपर समस्या को अनदेखा नहीं कर सकता। प्रोडक्शन बिल्ड में, बिना क्रैश के आँकड़े इकट्ठा करने के लिए penaltyLog का उपयोग करें।
iOS पर, Debug स्कीम में Main Thread Checker सक्षम करें और CI को इस विकल्प के साथ टेस्ट चलाने के लिए कॉन्फ़िगर करें। यदि किसी टेस्ट में बैकग्राउंड थ्रेड से UIKit कॉल है — तो उसे फेल होना चाहिए। TestFlight पर भेजने से पहले समस्या की पहचान करने का यह एकमात्र विश्वसनीय तरीका है।
कोड रिव्यू प्रक्रिया में एक अनिवार्य बिंदु जोड़ें: जाँच करें कि कोई भी नेटवर्क कॉल, फ़ाइल ऑपरेशन, डेटाबेस एक्सेस या भारी गणना बैकग्राउंड थ्रेड पर चलती है। डेडलॉक को स्थैतिक विश्लेषकों से डिटेक्ट किया जा सकता है: Facebook का Infer और Xcode का Thread Safety Checker रनटाइम से पहले संभावित लॉक ढूँढते हैं।
अक्सर पूछे जाने वाले प्रश्न
ANR (Application Not Responding) एक Android सिस्टम सूचना है जो मुख्य थ्रेड के 5 सेकंड से अधिक फ़्रीज़ होने पर प्रकट होती है। फ़्रीज़ एक व्यापक अवधारणा है: किसी भी अवधि का कोई भी UI ब्लॉकेज। iOS में ANR नहीं है, लेकिन 10–20 सेकंड टाइमआउट वाला Watchdog है।
फ़ाइल /data/anr/traces.txt पर स्थित है। एक्सेस के लिए रूट एक्सेस या adb shell चाहिए: रूट अधिकारों के साथ adb shell cat /data/anr/traces.txt \> traces.txt चलाएँ। स्टैक में “main” थ्रेड खोजें — अंतिम कॉल की गई विधि ब्लॉकेज का कारण बताती है।
यदि फ़्रीज़ 10 सेकंड से कम रहता है, तो Watchdog ट्रिगर नहीं होता, और ऐप बस ब्लॉकिंग ऑपरेशन पूरा होने तक “फँसा” रहता है। उपयोगकर्ता को क्रैश नहीं दिखता लेकिन निराशा होती है। ऐसे मामलों का पता लगाने के लिए, कस्टम निष्पादन समय ट्रेस के साथ MetricKit का उपयोग करें।
UI परीक्षण का उपयोग करें जाँच के साथ कि स्क्रीन < 1 सेकंड में खुलती है। टैप और अगली स्क्रीन की उपस्थिति के बीच समय माप को CI में जोड़ें। Android पर, एसिंक्रोनस ऑपरेशन की प्रतीक्षा के लिए IdlingResource के साथ Espresso का उपयोग करें। iOS पर, लोडिंग समय जाँचने के लिए XCTWaiter के साथ XCTest का उपयोग करें।
SwiftUI स्वयं फ़्रीज़ का कारण नहीं बनता, लेकिन body प्रॉपर्टी में जटिल गणनाएँ करती हैं। यदि भारी ऑपरेशन के कारण body की गणना में 500 ms लगते हैं, तो UI फ़्रीज़ हो जाता है। समाधान है गणनाओं को Task.detached में ले जाना और मुख्य एक्टर पर @State को एसिंक्रोनस रूप से अपडेट करना।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें