جمود (پھنسنا) ایک ایسی حالت ہے جس میں موبائل ایپلیکیشن طویل عرصے تک صارف کے کسی بھی عمل کا جواب دینا بند کر دیتی ہے۔ لیگ (سستی) اور گلچ (غلط رویہ) کے برعکس، جمود 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 کا انتظار کرتا ہے۔ دونوں تھریڈ ہمیشہ کے لیے جم جاتے ہیں۔ اگر ان میں سے ایک مرکزی تھریڈ ہے تو ایپلیکیشن مکمل طور پر جم جاتی ہے۔
منطق میں غلطی — مثال کے طور پر، باہر نکلنے کی شرط کے بغیر 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 closure میں ہوتا ہے۔
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 سیکنڈ ٹائم آؤٹ کے ساتھ ایک واچ ڈاگ ہے۔
فائل /data/anr/traces.txt پر واقع ہے۔ رسائی کے لیے روٹ رسائی یا adb shell درکار ہے: روٹ اختیارات کے ساتھ adb shell cat /data/anr/traces.txt \> traces.txt چلائیں۔ اسٹیک میں “main” تھریڈ تلاش کریں — آخری بلایا گیا طریقہ مسدودی کی وجہ بتاتا ہے۔
اگر جمود 10 سیکنڈ سے کم رہے تو واچ ڈاگ فعال نہیں ہوتا اور ایپ مسدود کرنے والی کارروائی مکمل ہونے تک بس “پھنسی” رہتی ہے۔ صارف کریش نہیں دیکھتا لیکن مایوسی محسوس کرتا ہے۔ ایسے معاملات کا پتہ لگانے کے لیے، اپنی مرضی کے عملدرآمد کے وقت کے نشانات کے ساتھ MetricKit استعمال کریں۔
UI ٹیسٹ استعمال کریں اس تصدیق کے ساتھ کہ اسکرین 1 سیکنڈ سے کم میں کھلتی ہے۔ ٹیپ اور اگلی اسکرین کے ظاہر ہونے کے درمیان وقت کی پیمائش CI میں شامل کریں۔ Android پر، غیر متزامن کارروائیوں کے انتظار کے لیے IdlingResource کے ساتھ Espresso استعمال کریں۔ iOS پر، لوڈنگ وقت کی جانچ کے لیے XCTWaiter کے ساتھ XCTest استعمال کریں۔
SwiftUI خود جمود کا سبب نہیں بنتا، لیکن body پراپرٹی میں پیچیدہ حسابات سبب بنتے ہیں۔ اگر بھاری کارروائیوں کی وجہ سے body کا حساب 500 ms لیتا ہے تو UI جم جاتا ہے۔ حل یہ ہے کہ حسابات کو Task.detached میں منتقل کریں اور @State کو مرکزی اداکار پر غیر متزامن طور پر اپ ڈیٹ کریں۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں