ANR ها

وقتی رابط کاربری یک برنامه اندروید برای مدت طولانی مسدود شود، خطای "برنامه پاسخ نمی‌دهد" (ANR) ایجاد می‌شود. اگر برنامه در پیش‌زمینه باشد، سیستم یک کادر محاوره‌ای به کاربر نمایش می‌دهد، همانطور که در شکل ۱ نشان داده شده است. کادر محاوره‌ای ANR به کاربر این امکان را می‌دهد که برنامه را به اجبار ببندد.

کادر محاوره‌ای ANR به کاربر نمایش داده می‌شود.
شکل ۱. پنجره ANR که به کاربر نمایش داده می‌شود

ANRها مشکل‌ساز هستند زیرا رشته اصلی برنامه که مسئول به‌روزرسانی رابط کاربری است، نمی‌تواند رویدادهای ورودی کاربر یا ترسیم را پردازش کند و این باعث ناامیدی کاربر می‌شود. برای اطلاعات بیشتر در مورد رشته اصلی برنامه، به مرور کلی فرآیندها و رشته‌ها مراجعه کنید.

زمانی که یکی از شرایط زیر رخ دهد، یک ANR برای برنامه شما فعال می‌شود:

  • مهلت ارسال ورودی به پایان رسیده است: اگر برنامه شما ظرف 5 ثانیه به یک رویداد ورودی (مانند فشار دادن کلید یا لمس صفحه) پاسخ نداده باشد.
  • اجرای سرویس: اگر سرویسی که توسط برنامه شما تعریف شده است نتواند اجرای Service.onCreate و Service.onStartCommand / Service.onBind را ظرف چند ثانیه به پایان برساند.
  • فراخوانی نشدن Service.startForeground : اگر برنامه شما از Context.startForegroundService برای شروع یک سرویس جدید در پیش‌زمینه استفاده می‌کند، اما سرویس ظرف ۵ ثانیه startForeground فراخوانی نمی‌کند.
  • پخش هدف: اگر یک BroadcastReceiver اجرای خود را در مدت زمان مشخصی به پایان نرسانده باشد. اگر برنامه فعالیتی در پیش‌زمینه داشته باشد، این زمان انتظار ۵ ثانیه است.
  • تعاملات JobScheduler : اگر یک JobService ظرف چند ثانیه از JobService.onStartJob یا JobService.onStopJob برنگردد، یا اگر یک کار آغاز شده توسط کاربر شروع شود و برنامه شما ظرف چند ثانیه پس از فراخوانی JobService.onStartJob JobService.setNotification را فراخوانی نکند. برای برنامه‌هایی که اندروید ۱۳ و پایین‌تر را هدف قرار می‌دهند، ANRها خاموش هستند و به برنامه گزارش نمی‌شوند. برای برنامه‌هایی که اندروید ۱۴ و بالاتر را هدف قرار می‌دهند، ANRها صریح هستند و به برنامه گزارش می‌شوند.

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

مشکل را تشخیص دهید

اگر برنامه خود را قبلاً منتشر کرده‌اید، می‌توانید از Android Vitals برای مشاهده اطلاعات ANRهای برنامه خود استفاده کنید. می‌توانید از ابزارهای دیگر برای تشخیص ANRها در محل استفاده کنید، اما توجه داشته باشید که ابزارهای 3P برخلاف Android Vitals نمی‌توانند ANRها را در اندروید 10 و پایین‌تر گزارش دهند.

نکات مهم اندروید

Android Vitals می‌تواند به شما در نظارت و بهبود نرخ ANR برنامه‌تان کمک کند. Android Vitals چندین نرخ ANR را اندازه‌گیری می‌کند:

  • نرخ ANR: درصد کاربران فعال روزانه شما که هر نوع ANR را تجربه کرده‌اند.
  • نرخ ANR درک شده توسط کاربر: درصد کاربران فعال روزانه شما که حداقل یک ANR درک شده توسط کاربر را تجربه کرده‌اند. در حال حاضر فقط ANRهایی از نوع Input dispatching timed out عنوان ANR درک شده توسط کاربر در نظر گرفته می‌شوند.
  • نرخ ANR چندگانه: درصد کاربران فعال روزانه شما که حداقل دو ANR را تجربه کرده‌اند.

کاربر فعال روزانه ، کاربری منحصر به فرد است که در یک روز و روی یک دستگاه و احتمالاً طی چندین جلسه از اپلیکیشن شما استفاده می‌کند. اگر کاربری در یک روز از اپلیکیشن شما روی بیش از یک دستگاه استفاده کند، هر دستگاه در تعداد کاربران فعال آن روز نقش خواهد داشت.

نرخ ANR درک شده توسط کاربر یک عامل حیاتی است، به این معنی که بر قابلیت کشف برنامه شما در گوگل پلی تأثیر می‌گذارد. این مهم است زیرا ANR هایی که در نظر گرفته می‌شوند همیشه زمانی رخ می‌دهند که کاربر با برنامه درگیر است و بیشترین اختلال را ایجاد می‌کند.

پلی دو آستانه رفتار بد را در این معیار تعریف کرده است:

  • آستانه کلی رفتار بد: حداقل ۰.۴۷٪ از کاربران فعال روزانه، در تمام مدل‌های دستگاه، ANR ادراک‌شده توسط کاربر را تجربه می‌کنند.
  • آستانه رفتار بد به ازای هر دستگاه: حداقل ۸٪ از کاربران روزانه، برای یک مدل دستگاه ، ANR ادراک‌شده توسط کاربر را تجربه می‌کنند.

اگر برنامه شما از آستانه کلی رفتار بد عبور کند، احتمالاً در همه دستگاه‌ها کمتر قابل شناسایی خواهد بود. اگر برنامه شما در برخی دستگاه‌ها از آستانه رفتار بد برای هر دستگاه عبور کند، احتمالاً در آن دستگاه‌ها کمتر قابل شناسایی خواهد بود و ممکن است هشداری در فهرست فروشگاه شما نمایش داده شود.

وقتی برنامه شما ANR بیش از حد نشان می‌دهد، Android Vitals می‌تواند از طریق Play Console به شما هشدار دهد.

برای اطلاعات بیشتر در مورد نحوه جمع‌آوری داده‌های حیاتی اندروید توسط گوگل پلی، به مستندات کنسول پلی مراجعه کنید.

تشخیص ANR ها

هنگام تشخیص ANRها، الگوهای رایجی برای بررسی وجود دارد:

  • برنامه عملیات ورودی/خروجی (I/O) را روی نخ اصلی (main thread) به کندی انجام می‌دهد.
  • برنامه در حال انجام یک محاسبه طولانی در رشته اصلی است.
  • نخ اصلی در حال انجام یک فراخوانی همزمان binder به یک فرآیند دیگر است و بازگشت آن فرآیند دیگر زمان زیادی می‌برد.
  • نخ اصلی در انتظار یک بلوک هماهنگ‌شده برای یک عملیات طولانی که در نخ دیگری در حال انجام است، مسدود شده است.
  • نخ اصلی با نخ دیگری در بن‌بست قرار دارد، چه در فرآیند شما و چه از طریق فراخوانی binder. نخ اصلی فقط منتظر پایان یک عملیات طولانی نیست، بلکه در وضعیت بن‌بست قرار دارد.

تکنیک‌های زیر می‌توانند به شما در تعیین علت ANR های شما کمک کنند.

آمار سلامت

HealthStats با ثبت کل زمان استفاده کاربر و سیستم، زمان استفاده از CPU، شبکه، آمار رادیو، زمان روشن/خاموش شدن صفحه نمایش و آلارم‌های بیدارباش، معیارهایی در مورد سلامت یک برنامه ارائه می‌دهد. این می‌تواند به شما در اندازه‌گیری میزان کلی استفاده از CPU و تخلیه باتری کمک کند.

اشکال‌زدایی

Debug به شما کمک می‌کند تا برنامه‌های اندروید را در طول توسعه بررسی کنید، از جمله ردیابی و شمارش تخصیص برای شناسایی موارد ناخواسته و تأخیر در برنامه‌ها. همچنین می‌توانید Debug برای دریافت شمارنده‌های حافظه زمان اجرا و بومی و معیارهای حافظه استفاده کنید که می‌تواند به شما در شناسایی ردپای حافظه یک فرآیند خاص کمک کند.

اطلاعات خروج از برنامه

ApplicationExitInfo در اندروید ۱۱ (سطح API 30) یا بالاتر موجود است و اطلاعاتی در مورد دلیل خروج از برنامه ارائه می‌دهد. این اطلاعات شامل ANRها، کمبود حافظه، خرابی برنامه، استفاده بیش از حد از CPU، وقفه‌های کاربر، وقفه‌های سیستم و تغییرات مجوز زمان اجرا می‌شود.

حالت سختگیرانه

استفاده از StrictMode به شما کمک می‌کند تا عملیات ورودی/خروجی تصادفی را در نخ اصلی (main thread) هنگام توسعه برنامه خود پیدا کنید. می‌توانید StrictMode در سطح برنامه یا فعالیت (activity) استفاده کنید.

فعال کردن پنجره‌های محاوره‌ای ANR در پس‌زمینه

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

تنگناهای بازسازی

از Android Studio Profiler و Layout Inspector برای ردیابی گلوگاه‌های recomposition استفاده کنید. برای اطلاعات بیشتر، به Jetpack Compose Performance مراجعه کنید.

یک فایل ردیابی را استخراج کنید

اندروید هنگام مواجهه با یک خطای ANR، اطلاعات ردیابی را ذخیره می‌کند. در نسخه‌های قدیمی‌تر سیستم عامل، یک فایل /data/anr/traces.txt در دستگاه وجود دارد. در نسخه‌های جدیدتر سیستم عامل، چندین فایل /data/anr/anr_* وجود دارد. می‌توانید با استفاده از Android Debug Bridge (adb) به عنوان کاربر روت، به ردیابی‌های ANR از یک دستگاه یا شبیه‌ساز دسترسی پیدا کنید:

adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>

شما می‌توانید با استفاده از گزینه‌ی Take bug report developer در دستگاه یا دستور adb bugreport در دستگاه توسعه‌دهنده‌ی خود، یک گزارش اشکال از یک دستگاه فیزیکی دریافت کنید. برای اطلاعات بیشتر، به بخش Capture and read bug reports مراجعه کنید.

مشکلات را برطرف کنید

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

کند بودن کد در نخ اصلی

مکان‌هایی را در کد خود که در آن‌ها رشته اصلی برنامه بیش از ۵ ثانیه مشغول است، شناسایی کنید. موارد استفاده مشکوک را در برنامه خود جستجو کنید و سعی کنید ANR را دوباره ایجاد کنید.

یک مشکل رایج، اجرای طولانی مدت یک وظیفه به طور مستقیم در یک composable است:

@Composable
fun BadList(rawStrings: List<String>) {
    // Math or sorting inside the composable runs on EVERY recomposition pass!
    val heavilyProcessedList = rawStrings
        .filter { it.isNotBlank() }
        .map { it.uppercase().reversed() }
        .map { it.computationallyHeavyFunction() }
.sortedBy { it.length }
    LazyColumn { items(sortedList) { Text(it) } }
}

// Modern Compose-first fix
@Composable
fun GoodList(viewModel: MyViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    // UI simply renders state; no heavy processing allowed here
    LazyColumn { items(uiState.sortedData) { Text(it) } }
}

ورودی/خروجی روی نخ اصلی

اجرای عملیات ورودی/خروجی (I/O) در نخ اصلی (Main Thread) یکی از دلایل رایج کندی عملیات در نخ اصلی است که می‌تواند باعث ANR شود. در Compose، توسعه‌دهندگان اغلب هنگام تلاش برای استخراج حالت اولیه، به‌طور تصادفی خواندن دیسک (مانند SharedPreferences یا فراخوانی‌های پایگاه داده) را آغاز می‌کنند.

عملیات ورودی/خروجی طولانی مدت را خارج از لایه رابط کاربری اجرا کنید. از withContext(Dispatchers.IO) در ViewModel یا حتی بهتر از آن، از یک Repository در لایه داده استفاده کنید.

بن‌بست‌ها

بن‌بست زمانی رخ می‌دهد که یک نخ به دلیل در اختیار داشتن منبع مورد نیاز توسط نخ دیگری که آن هم منتظر منبعی است که توسط نخ اول در اختیار دارد، وارد حالت انتظار شود. اگر نخ اصلی برنامه در این وضعیت باشد، احتمال وقوع ANR وجود دارد.

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

برای اطلاعات بیشتر، به الگوریتم‌های پیشگیری از بن‌بست و بن‌بست در ویکی‌پدیا مراجعه کنید.

هنگام استفاده از Kotlin و Compose، می‌توانید قفل‌های اولیه را با Mutexeهای کوروتین غیر مسدودکننده ( Mutex.withLock ) جایگزین کنید تا با تعلیق زمینه اجرا به جای فریز کردن نخ رابط کاربری، از مسدود شدن نخ جلوگیری کنید. به عنوان مثال:

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

// Modern non-blocking concurrency state architecture
class SecureDataRepository {
    private val mutex = Mutex()

    suspend fun safeUIAccess() {
        // If locked, the main thread suspends seamlessly, preventing an ANR
        mutex.withLock {
            performSafeOperation()
        }
    }
}

گیرنده‌های پخش کند

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

ANR در موارد زیر رخ می‌دهد:

  • یک گیرنده‌ی اعلانات (Broadcast Receiver) اجرای متد onReceive خود را در مدت زمان قابل توجهی به پایان نرسانده است.
  • یک گیرنده‌ی اعلان، تابع goAsync فراخوانی می‌کند و در فراخوانی finish روی شیء PendingResult با شکست مواجه می‌شود.

برنامه شما فقط باید عملیات کوتاه را در متد onReceive از BroadcastReceiver انجام دهد. با این حال، اگر برنامه شما به دلیل یک پیام پخش به پردازش پیچیده‌تری نیاز دارد، باید اگر انتظار می‌رود که این کار حداکثر چند ثانیه طول بکشد، آن را به یک ViewModel (با استفاده از قدرت Coroutineها، Scopeها و dispatchersهای کاتلین) یا هر نوع نگهدارنده وضعیت (state holder) یا به WorkManager برای کارهایی که انتظار می‌رود بیش از چند ثانیه طول بکشد، موکول کنید.

فعالیت بازی

کتابخانه GameActivity در مطالعات موردی بازی‌ها و برنامه‌هایی که با زبان C یا C++ نوشته شده‌اند، ANRها را کاهش داده است. اگر Activity بومی فعلی خود را با GameActivity جایگزین کنید، می‌توانید انسداد نخ رابط کاربری را کاهش داده و از وقوع برخی ANRها جلوگیری کنید.

برای اطلاعات بیشتر در مورد ANRها، به «برنامه خود را پاسخگو نگه دارید» مراجعه کنید. برای اطلاعات بیشتر در مورد رشته‌ها، به «عملکرد بهتر از طریق رشته‌سازی» مراجعه کنید.

منابع اضافی

محتوا را مشاهده می‌کند

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}