قفل شدن در توسعه — ماهیت، علل و پیشگیری

نویسنده: IT Sectr منتشر شده: 2026-07-28 زمان مطالعه: 9 دقیقه

قفل شدن (ویسنت) — حالتی است که در آن برنامه موبایل برای مدت طولانی به هیچ اقدام کاربر واکنش نشان نمی‌دهد. بر خلاف لگ‌ها (کند شدن کار) و گلیچ‌ها (رفتار نادرست)، قفل شدن رابط کاربری را کاملاً مسدود می‌کند: لمس‌ها پردازش نمی‌شوند، انیمیشن متوقف می‌شود، صفحه «یخ می‌زند». علت — مسدود شدن نخ اصلی توسط عملیات همزمان، بن‌بست در کد چندنخی یا جمع‌آوری زباله غیرعادی طولانی. طبق Apple Main Thread Checker Documentation, بیش از ۴۰٪ گزارش‌های crash در iOS با مسدود شدن نخ اصلی مرتبط است. در Android وضعیت مشابه منجر به ANR — دیالوگ سیستمی «برنامه پاسخ نمی‌دهد» می‌شود.

نکات اصلی

  • قفل شدن — مسدود شدن کامل رابط کاربری برای مدت طولانی (ثانیه‌ها و ده‌ها ثانیه)، متفاوت از لگ‌ها و گلیچ‌ها
  • علل اصلی — مسدود شدن نخ اصلی توسط ورودی-خروجی، بن‌بست بین نخ‌ها، حلقه بی‌نهایت و نشت حافظه با GC طولانی
  • تشخیص شامل Main Thread Checker در iOS، لاگ‌های ANR /data/anr/traces.txt در Android و تحلیل dump نخ‌ها
  • رفع — انتقال تمام عملیات‌های بالقوه طولانی به نخ‌های پس‌زمینه، استفاده از Structured Concurrency و اجتناب از synchronized در نخ رابط کاربری
  • پیشگیری — StrictMode, Main Thread Checker در طرح Debug, تحلیل ایستا برای بن‌بست و اجرای دوره‌ای تست‌ها با اندازه‌گیری زمان پاسخ

قفل شدن در توسعه موبایل چیست

قفل شدن (freeze, hang) در برنامه موبایل — حالتی است که در آن برنامه پردازش رویدادهای ورودی و به‌روزرسانی رابط را برای چند ثانیه یا بیشتر متوقف می‌کند. از نظر فنی به این معنی است که نخ اصلی (main thread) مسدود شده و نمی‌تواند چرخه بعدی runner را اجرا کند.

تفاوت قفل شدن با لگ و ANR

لگ — تأخیر تا ۵۰۰ میلی‌ثانیه است که در آن کاربر کندی را متوجه می‌شود، اما برنامه به کار ادامه می‌دهد. قفل شدن از ۱ ثانیه تا ده‌ها ثانیه طول می‌کشد. ANR در Android — حالت خاصی از قفل شدن است که بیش از ۵ ثانیه طول کشیده و توسط سیستم شناسایی شده است. هر قفل شدنی منجر به ANR نمی‌شود، اما هر ANR یک قفل شدن مستند شده توسط سیستم است.

عواقب قفل شدن

در Android قفل شدن بیش از ۵ ثانیه باعث دیالوگ ANR با پیشنهاد بستن برنامه می‌شود. در iOS سیستم دارای watchdog است — اگر برنامه به رویدادها در عرض ۱۰–۲۰ ثانیه واکنش نشان ندهد، Watchdog فرآیند را با کد 0x8badf00d (ate bad food) خاتمه می‌دهد. کاربر فقط بسته شدن ناگهانی برنامه و بازگشت به صفحه اصلی را می‌بیند.

علل قفل شدن در Android و iOS

هر عملیاتی که بیش از ۱۰۰ میلی‌ثانیه طول بکشد و در نخ اصلی اجرا شود، به طور بالقوه باعث قفل شدن می‌شود. منابع اصلی مسدودیت را بررسی می‌کنیم.

ورودی-خروجی همزمان در نخ رابط کاربری

خواندن فایل بزرگ، درخواست شبکه بدون ناهمزمانی، ذخیره داده‌ها در SharedPreferences با روش همزمان apply به دنبال commit — همه این عملیات نخ اصلی را مسدود می‌کنند. در Android خواندن همزمان یک فایل ۱۰ مگابایتی بسته به سرعت حافظه فلش می‌تواند ۲۰۰–۵۰۰ میلی‌ثانیه طول بکشد. در iOS بارگذاری همزمان URLSession بدون completionHandler رابط کاربری را برای مدت پاسخ سرور مسدود می‌کند.

بن‌بست در کد چندنخی

وقتی دو نخ منتظر آزاد شدن منابعی هستند که توسط یکدیگر نگه داشته شده‌اند، بن‌بست (deadlock) رخ می‌دهد. در برنامه‌های موبایل سناریوی معمول — نخ A Lock1 را قفل کرده و منتظر Lock2 است، و نخ B Lock2 را قفل کرده و منتظر Lock1 است. هر دو نخ برای همیشه قفل می‌شوند. اگر یکی از آنها نخ اصلی باشد، برنامه کاملاً قفل می‌شود.

حلقه بی‌نهایت یا بازگشت

خطا در منطق — مثلاً while(true) بدون شرط خروج یا بازگشت بدون حالت پایه — منجر به اجرای بی‌نهایت در نخ اصلی می‌شود. Android این را پس از ۵ ثانیه از طریق ANR تشخیص می‌دهد، iOS — از طریق Stackshot که stack فراخوانی بی‌نهایت تکراری را ثبت می‌کند.

  • Android — Cursor بدون بسته شدن، درخواست همزمان از طریق execute() به جای enqueue(), FileInputStream.read() در نخ رابط کاربری
  • iOS — performSelectorOnMainThread:withObject:waitUntilDone:YES, اجرای NSURLConnection sendSynchronousRequest, بارگذاری تصویر با dataWithContentsOfURL
  • کراس-پلتفرم — Flutter compute بدون isolate اختصاصی, React Native NativeModule همزمان

چگونه قفل شدن را تشخیص دهیم

تشخیص قفل شدن نیازمند ابزارهایی است که بتوانند وضعیت همه نخ‌ها را در لحظه مسدودیت ثبت کنند.

لاگ‌های ANR در Android

در هر ANR سیستم Android فایل /data/anr/traces.txt را ذخیره می‌کند که شامل dump stack هر نخ برنامه است. تحلیل این فایل — روش اصلی تشخیص: باید نخ main را پیدا کرد و دید روی چه متدی متوقف شده است. اگر stack به Thread.sleep, InputStream.read یا Lock.lock ختم شود — علت پیدا شده است.

Stackshot در iOS

Xcode هنگام قفل شدن برنامه (سیگنال SIGSTOP) می‌تواند Stackshot — تصویری از stackهای همه نخ‌ها را بگیرد. در طرح «Logging» → «Include Stackshot Logs» را فعال کنید. هنگام crash با کد 0x8badf00d لاگ crash را از Devices & Simulators استخراج کرده و نخ com.apple.main-thread را با stack قفل شده پیدا کنید.

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

روش‌های رفع مسدودیت رابط کاربری

رفع قفل شدن با انتقال تمام عملیات‌های بالقوه طولانی به نخ‌های پس‌زمینه شروع می‌شود. تکنیک‌های خاص هر پلتفرم را بررسی می‌کنیم.

Structured Concurrency با کوروتین‌ها

Kotlin Coroutines با viewModelScope.launch(Dispatchers.IO) تضمین می‌کنند که عملیات شبکه یا خواندن از پایگاه داده در نخ پس‌زمینه اجرا می‌شوند. Dispatchers.Main فقط برای به‌روزرسانی رابط کاربری استفاده می‌شود. مهم: همه توابع suspend باید ساختاریافته باشند — کوروتین‌های فرزند با لغو والد لغو می‌شوند و از نشت نخ جلوگیری می‌کنند.

صف‌های ناهمزمان در iOS

Grand Central Dispatch با DispatchQueue.global(qos: .userInitiated) برای وظایف پس‌زمینه و DispatchQueue.main.async برای به‌روزرسانی رابط کاربری — الگوی استاندارد. از sync() در صف اصلی اجتناب کنید — این بن‌بست تضمینی است. از async/await (Swift 5.5+) برای کد ناهمزمان خواناتر با بازگشت خودکار به نخ اصلی از طریق MainActor استفاده کنید.

اجتناب از synchronized در نخ رابط کاربری

بلوک‌های 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 برای سیاست‌های نخی پیکربندی کنید — این باعث crash فوری برنامه هنگام تشخیص فراخوانی شبکه یا ورودی-خروجی دیسک در نخ اصلی می‌شود. توسعه‌دهنده نمی‌تواند مشکل را نادیده بگیرد. در build تولیدی از penaltyLog برای جمع‌آوری آمار بدون crash استفاده کنید.

Main Thread Checker در طرح Debug

در iOS Main Thread Checker را در طرح Debug فعال کنید و CI را برای اجرای تست‌ها با این گزینه پیکربندی کنید. اگر تست حاوی فراخوانی UIKit از نخ پس‌زمینه باشد — باید ناموفق شود. این تنها راه قابل اعتماد برای شناسایی مشکل قبل از ارسال به TestFlight است.

بازبینی کد با بررسی چندنخی

به فرآیند بازبینی کد یک بند اجباری اضافه کنید: بررسی اینکه هر فراخوانی شبکه، کار با فایل‌ها، پایگاه داده یا محاسبات سنگین در نخ پس‌زمینه اجرا می‌شود. بن‌بست را می‌توان با تحلیلگر ایستا تشخیص داد: 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 است که هنگام قفل شدن نخ اصلی بیش از ۵ ثانیه ظاهر می‌شود. قفل شدن مفهوم گسترده‌تری است: هر مسدودیت رابط کاربری با هر مدتی. در iOS ANR وجود ندارد، اما Watchdog با مهلت ۱۰–۲۰ ثانیه وجود دارد.

چگونه traces.txt را در Android بخوانیم؟

فایل در /data/anr/traces.txt قرار دارد. برای دسترسی نیاز به root یا adb shell است: adb shell cat /data/anr/traces.txt \> traces.txt را با حقوق root اجرا کنید. در stack نخ «main» را پیدا کنید — آخرین متد فراخوانی شده علت مسدودیت را نشان می‌دهد.

چرا برنامه در iOS قفل می‌شود اما crash نمی‌کند؟

اگر قفل شدن کمتر از ۱۰ ثانیه طول بکشد، Watchdog فعال نمی‌شود و برنامه تا پایان عملیات مسدودکننده فقط «قفل می‌ماند». کاربر crash نمی‌بیند اما ناامیدی را تجربه می‌کند. برای تشخیص چنین مواردی از MetricKit با ردگیری‌های سفارشی زمان اجرا استفاده کنید.

چگونه برنامه را برای قفل شدن تست کنیم؟

از تست‌های رابط کاربری با بررسی باز شدن صفحه در کمتر از ۱ ثانیه استفاده کنید. به CI اندازه‌گیری زمان بین لمس و ظاهر شدن صفحه بعدی را اضافه کنید. در Android از Espresso با IdlingResource برای انتظار عملیات ناهمزمان استفاده کنید. در iOS XCTest با XCTWaiter برای بررسی زمان بارگذاری.

آیا SwiftUI می‌تواند باعث قفل شدن شود؟

SwiftUI به خودی خود باعث قفل شدن نمی‌شود، اما محاسبات پیچیده در ویژگی body — بله. اگر body به دلیل عملیات سنگین به مدت ۵۰۰ میلی‌ثانیه محاسبه شود، رابط کاربری قفل می‌شود. راه حل — محاسبات را به Task.detached منتقل کرده و @State را به صورت ناهمزمان در بازیگر اصلی به‌روزرسانی کنید.

خلاصه

  • قفل شدن — مسدودیت کامل رابط کاربری برای ثانیه‌ها و ده‌ها ثانیه، ناشی از مسدود شدن نخ اصلی، بن‌بست یا حلقه بی‌نهایت
  • تشخیص — /data/anr/traces.txt در Android, Stackshot و Main Thread Checker در iOS
  • علل اصلی — ورودی-خروجی همزمان، بن‌بست بین نخ‌ها، بازگشت بی‌نهایت، GC طولانی
  • رفع — کوروتین‌ها با dispatcherهای صحیح, async/await با MainActor, انتقال تمام عملیات‌های IO به نخ‌های پس‌زمینه
  • پیشگیری — StrictMode با penaltyDeath, Main Thread Checker, تحلیل ایستای بن‌بست (Infer, TSAN)
  • در Android قفل شدن > ۵ ثانیه = ANR; در iOS > ۱۰–۲۰ ثانیه = Watchdog crash (0x8badf00d)
  • توصیه: Thread Sanitizer را در طرح Debug فعال کنید و CI را برای اجرای تست‌ها با TSAN برای تشخیص data race و بن‌بست پیکربندی کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید