عیب‌یابی و رفع خطاهای ANR

وقتی رشته رابط کاربری یک برنامه Android برای مدت طولانی مسدود می‌شود، سیستم خطای «برنامه پاسخ نمی‌دهد» (ANR) ارسال می‌کند. این صفحه انواع مختلف خطاهای ANR، نحوه تشخیص آن‌ها، و پیشنهادهایی برای رفع آن‌ها را شرح می‌دهد. همه محدوده‌های زمانی وقفه پیش‌فرض فهرست‌شده برای دستگاه‌های AOSP و Pixel است؛ این زمان‌ها می‌تواند براساس سازنده تجهیزات اصلی متفاوت باشد.

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

وقتی سیستم در وضعیت بدی باشد، مشکلات زیر می‌توانند باعث خطاهای ANR شوند:

  • مشکلات گذرا در سرور سیستم باعث می‌شود که فراخوانی‌های سریع پیونددهنده معمولاً کند شوند.
  • مشکلات مربوط به سرور سیستم و بار زیاد دستگاه باعث می‌شود رشته‌های برنامه زمان‌بندی نشوند.

اگر دراختیارتان باشد، روش خوبی برای تشخیص تفاوت بین مشکلات سیستم و برنامه استفاده از ردیابی‌های Perfetto است:

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

مهلت ارسال ورودی تمام شد

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

دوره زمان‌دار پیش‌فرض: ۵ ثانیه.

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

برای جلوگیری از خطاهای ANR مربوط به توزیع ورودی، این روال‌های مطلوب را دنبال کنید:

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

دلایل متداول

در اینجا برخی‌از دلایل رایج و اصلاحات پیشنهادی برای ANRهای توزیع ورودی آورده شده است.

علت اتفاقی که می‌افتد اصلاح‌های پیشنهادی
تماس پوشه کُند رشته اصلی فراخوانی طولانی و هم‌زمان Binder انجام می‌دهد. اگر مالک میانای برنامه‌سازی کاربردی هستید، تماس را از رشته اصلی خارج کنید یا سعی کنید تماس را بهینه‌سازی کنید.
تماس‌های پیاپی زیاد با پوشه رشته اصلی تماس‌های همگام‌سازی Binder متوالی زیادی برقرار می‌کند. تماس‌های چسباننده را در یک حلقه تنگ انجام ندهید.
مسدود کردن ورودی/خروجی رشته اصلی تماس ورودی/خروجی مسدودکننده، مثل دسترسی به پایگاه داده یا شبکه، برقرار می‌کند. همه ورودی/خروجی‌های مسدودکننده را از رشته اصلی خارج کنید.
درگیری بر سر قفل رشته اصلی مسدود شده است و منتظر است قفل را به‌دست آورد. رقابت قفل بین رشته اصلی و رشته‌های دیگر را کاهش دهید. کد کند را در رشته دیگر بهینه‌سازی کنید.
قاب گران پردازش بیش‌ازحد در یک قاب، که باعث لرزش شدید می‌شود. کار کمتری برای پرداز کردن قاب انجام دهید. از الگوریتم‌های n2 استفاده نکنید. از عناصر کارآمد برای مواردی مثل پیمایش یا صفحه‌بندی استفاده کنید—برای مثال، کتابخانه صفحه‌بندی Jetpack.
توسط عنصر دیگری مسدود شده است عنصر دیگری، مثل گیرنده همه‌فرستی، درحال اجرا است و رشته اصلی را مسدود می‌کند. تا حد امکان، کار غیرواسط کاربر را از رشته اصلی خارج کنید. گیرنده‌های همه‌فرستی را در رشته‌ای متفاوت اجرا کنید.
واحد پردازش گرافیکی معلقه «تعلیق GPU» مشکل سیستم یا سخت‌افزاری است که باعث می‌شود پردازش مسدود شود و درنتیجه خطای ANR توزیع ورودی رخ دهد. متأسفانه، معمولاً هیچ اصلاحی در سمت برنامه وجود ندارد. درصورت امکان، برای عیب‌یابی با تیم سخت‌افزار تماس بگیرید.

نحوه اشکال‌زدایی

با بررسی امضای خوشه ANR در کنسول Google Play یا Firebase Crashlytics، اشکال‌زدایی را شروع کنید. این دسته معمولاً شامل قاب‌های برتر مشکوک به ایجاد ANR است.

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

شکل ۱. نحوه اشکال‌زدایی خطای ANR توزیع ورودی.

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

شکل ۲. تشخیص خطاهای ANR در «معیارهای کلیدی Play».

پنجره‌ای متمرکز نیست

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

دوره زمان‌دار پیش‌فرض: ۵ ثانیه.

دلایل متداول

خطاهای ANR پنجره بدون تمرکز معمولاً به‌دلیل یکی از مشکلات زیر ایجاد می‌شود:

  • برنامه کار زیادی انجام می‌دهد و برای رسم کردن اولین قاب خیلی کند است.
  • پنجره اصلی قابل‌تمرکز نیست. اگر پنجره‌ای با FLAG_NOT_FOCUSABLE پرچم‌گذاری شود، کاربر نمی‌تواند رویدادهای کلید یا دکمه را به آن ارسال کند.

کاتلین

override fun onCreate(savedInstanceState: Bundle) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_main)
  window.addFlags(WindowManager.LayoutParams.FLAG_FLAG_NOT_FOCUSABLE)
}

جاوا

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}

پایان مهلت گیرنده رویداد ثبت‌شده

خطای ANR گیرنده رویداد زمانی رخ می‌دهد که گیرنده رویداد نتواند رویداد را به‌موقع مدیریت کند. برای گیرنده‌های هم‌زمان، یا گیرنده‌هایی که goAsync() را فراخوانی نمی‌کنند، مهلت زمانی یعنی onReceive() در زمان تکمیل نشد. برای گیرنده‌های غیرهم‌زمان، یا گیرنده‌هایی که goAsync() را فراخوانی می‌کنند، مهلت زمانی یعنی PendingResult.finish() به‌موقع فراخوانی نشده است.

خطاهای ANR گیرنده همه‌فرستی اغلب در این رشته‌ها رخ می‌دهد:

  • رشته اصلی، اگر مشکل راه‌اندازی آهسته برنامه است.
  • اگر مشکل مربوط به کد onReceive() کند باشد، گیرنده همه‌فرستی رشته درحال اجرا است.
  • رشته‌های کارگر همه‌فرستی، اگر مشکل مربوط به کند بودن کد همه‌فرستی goAsync() باشد.

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

  • مطمئن شوید که راه‌اندازی برنامه سریع باشد، زیرا اگر برنامه برای مدیریت همه‌فرستی شروع شود، در مهلت زمانی ANR محاسبه می‌شود.
  • اگر از goAsync() استفاده می‌شود، مطمئن شوید PendingResult.finish() به‌سرعت فراخوانی شود. این گیرنده رویداد ثبت‌شده نیز مشمول همان زمان وقفه ANR گیرنده‌های رویداد ثبت‌شده هم‌زمان است.
  • اگر از goAsync() استفاده می‌شود، مطمئن شوید رشته(های) کارگر با دیگر عملیات‌های طولانی‌مدت یا مسدودکننده هم‌رسانی نمی‌شود.
  • برای جلوگیری از مسدود شدن کد واسط کاربر درحال اجرا در رشته اصلی، از registerReceiver() برای اجرای گیرنده‌های همه‌فرستی در رشته غیر اصلی استفاده کنید.

دوره‌های درنگ

دوره‌های زمان‌دار دریافت همه‌فرستی به اینکه پرچم هدف پیش‌زمینه‌ای تنظیم شده است یا نه و به نسخه پلاتفرم بستگی دارد.

نوع هدف ‫Android 13 و پایین‌تر ‫Android 14 و بالاتر

هدف اولویت پیش‌زمینه‌ای

‫(FLAG_RECEIVER_FOREGROUND تنظیم شد)

۱۰ ثانیه

‫۱۰ تا ۲۰ ثانیه، بسته به اینکه آیا پردازشگر مرکزی درگیر است یا نه

قصد اولویت پس‌زمینه

(FLAG_RECEIVER_FOREGROUND تنظیم نشده است)

۶۰ ثانیه

‫۶۰ تا ۱۲۰ ثانیه، بسته به اینکه آیا پردازش از CPU محروم است یا نه

برای اینکه متوجه شوید پرچم FLAG_RECEIVER_FOREGROUND تنظیم شده است یا نه، در موضوع ANR به‌دنبال «flg=» بگردید و وجود 0x10000000 را بررسی کنید. اگر این بیت تنظیم شده باشد، هدف FLAG_RECEIVER_FOREGROUND تنظیم شده است و بنابراین زمان اتمام کوتاه‌تر است.

مثال موضوع ANR با زمان اتمام پخش کوتاه (۱۰ تا ۲۰ ثانیه):

Broadcast of Intent { act=android.inent.action.SCREEN_ON flg=0x50200010 }

موضوع ANR نمونه با زمان اتمام همه‌فرستی طولانی (۶۰ تا ۱۲۰ ثانیه):

Broadcast of Intent { act=android.intent.action.TIME_SET flg=0x25200010 }

نحوه اندازه‌گیری زمان‌های همه‌فرستی

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

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

شکل ۳. خط زمان خطای ANR گیرنده رویداد ثبت‌شده.

اندازه‌گیری مهلت زمانی ANR زمانی پایان می‌یابد که گیرنده پردازش همه‌فرستی را تمام کند: اینکه دقیقاً چه زمانی این اتفاق می‌افتد به این بستگی دارد که گیرنده هم‌زمان یا ناهم‌زمان باشد.

  • برای گیرنده‌های هم‌زمان، اندازه‌گیری وقتی onReceive() برمی‌گردد متوقف می‌شود.
  • برای گیرندگان ناهم‌زمان، اندازه‌گیری وقتی PendingResult.finish() فراخوانده می‌شود متوقف می‌شود.
شکل ۴. نقطه‌های پایان اندازه‌گیری مهلت زمانی ANR برای گیرنده‌های هم‌زمان و ناهم‌زمان.

دلایل متداول

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

علت قابل اعمال در چه اتفاقی افتاد اصلاح پیشنهادی
راه‌اندازی آهسته برنامه همه گیرندگان برنامه برای شروع سرد بیش‌ازحد طول کشید. راه‌اندازی آهسته برنامه را بهینه‌سازی کنید.
‫onReceive() زمان‌بندی نشده است همه گیرندگان رشته گیرنده همه‌فرستی مشغول انجام کار دیگری بود و نتوانست روش onReceive() را شروع کند. تکالیف طولانی‌مدت را در رشته گیرنده انجام ندهید (یا گیرنده را به رشته اختصاصی منتقل کنید).
onReceive() آهسته همه گیرنده‌ها، اما عمدتاً گیرنده‌های هم‌زمان روش onReceive() شروع شد اما مسدود یا کند بود، بنابراین در زمان مقرر تکمیل نشد. بهینه‌سازی کد گیرنده کند.
تکالیف گیرنده غیرهمگام زمان‌بندی نشد goAsync() گیرنده روش onReceive() سعی کرد کار را در استخر رشته کارگر مسدودشده اجرا کند، بنابراین کار هرگز شروع نشد. تماس‌های کند یا مسدودکننده را بهینه‌سازی کنید، یا برای کارگران همه‌فرستی دربرابر دیگر وظایف طولانی‌مدت از رشته‌های مختلف استفاده کنید.
کارگران کند یا مسدود شدند ‫goAsync() گیرنده هنگام پردازش همه‌فرستی، عملیات مسدودکننده یا کندی در جایی از استخر رشته کارگر رخ داد. بنابراین، PendingResult.finish به‌موقع فراخوانده نشد. کد گیرنده async کند را بهینه‌سازی کنید.
فراموش کردید با PendingResult.finish تماس بگیرید ‫goAsync() گیرنده تماس با finish() در مسیر کد وجود ندارد. مطمئن شوید که همیشه با finish() تماس گرفته شود.

نحوه اشکال‌زدایی

براساس امضای خوشه و گزارش ANR، می‌توانید رشته‌ای را که گیرنده در آن اجرا می‌شود و سپس کد خاصی را که وجود ندارد یا به‌کندی اجرا می‌شود پیدا کنید.

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

شکل ۵. نحوه اشکال‌زدایی کردن خطای ANR گیرنده رویداد ثبت‌شده.

پیدا کردن کد گیرنده

«کنسول Google Play» کلاس گیرنده و هدف همه‌فرستی را در امضای ANR نشان می‌دهد. به‌دنبال موارد زیر بگردید:

  • cmp=<receiver class>
  • act=<broadcast_intent>

در اینجا نمونه‌ای از امضای ANR گیرنده رویداد ثبت‌شده آورده شده است:

com.example.app.MyClass.myMethod
Broadcast of Intent { act=android.accounts.LOGIN_ACCOUNTS_CHANGED
cmp=com.example.app/com.example.app.MyAccountReceiver }

رشته‌ای را که روش onReceive() را اجرا می‌کند پیدا کنید

اگر از Context.registerReceiver برای مشخص کردن گرداننده سفارشی استفاده می‌کنید، این گرداننده رشته‌ای است که این گرداننده را اجرا می‌کند. درغیراین‌صورت، رشته اصلی است.

مثال: تکالیف گیرنده غیرهمگام زمان‌بندی نشده است

این بخش مثالی از نحوه اشکال‌زدایی کردن ANR گیرنده همه‌فرستی را ارائه می‌دهد.

فرض کنید امضای ANR به‌صورت زیر باشد:

com.example.app.MyClass.myMethod
Broadcast of Intent {
act=android.accounts.LOG_ACCOUNTS_CHANGED cmp=com.example.app/com.example.app.MyReceiver }

براساس امضا، به‌نظر می‌رسد هدف همه‌فرستی android.accounts.LOG_ACCOUNTS_CHANGED و کلاس گیرنده com.example.app.MyReceiver است.

از کد گیرنده می‌توانید متوجه شوید که استخر رشته «BG Thread [0,1,2,3]» کار اصلی را برای پردازش این همه‌فرستی انجام می‌دهد. با نگاه کردن به تخلیه پشته، می‌توانید ببینید که هر چهار رشته پس‌زمینه (BG) الگوی یکسانی دارند: آن‌ها یک تماس مسدودکننده، getDataSync، را اجرا می‌کنند. ازآنجایی‌که همه رشته‌های BG مشغول بودند، همه‌فرستی نتوانست به‌موقع پردازش شود و این امر منجر به خطای ANR شد.

BG Thread #0 (tid=26) Waiting

at jdk.internal.misc.Unsafe.park(Native method:0)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture:563)
at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture:68)
at com.example.app.getDataSync(<MyClass>:152)

...

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at com.google.android.libraries.concurrent.AndroidExecutorsModule.lambda$withStrictMode$5(AndroidExecutorsModule:451)
at com.google.android.libraries.concurrent.AndroidExecutorsModule$$ExternalSyntheticLambda8.run(AndroidExecutorsModule:1)
at java.lang.Thread.run(Thread.java:1012)
at com.google.android.libraries.concurrent.ManagedPriorityThread.run(ManagedPriorityThread:34)

There are several approaches to fix the issue:

  • Find out why getDataSync is slow and optimize.
  • Don't run getDataSync on all four BG threads.
  • More generally, ensure that the BG thread pool isn't saturated with long-running operations.
  • Use a dedicated thread pool for goAsync worker tasks.
  • Use an unbounded thread pool instead of the bounded BG thread pool

Example: slow app startup

A slow app startup can cause several types of ANRs, especially broadcast receiver and execute service ANRs. The cause of an ANR is likely slow app startup if you see ActivityThread.handleBindApplication in the main thread stacks.

Execute service timeout

An execute service ANR happens when the app's main thread doesn't start a service in time. Specifically, a service doesn't finish executing onCreate() and onStartCommand() or onBind() within the timeout period.

Default timeout period: 20 seconds for foreground service; 200 seconds for background service. The ANR timeout period includes the app cold start, if necessary, and calls to onCreate(), onBind(), or onStartCommand().

To avoid execute service ANRs, follow these general best practices:

  • Make sure that app startup is fast, since it's counted in the ANR timeout if the app is started to run the service component.
  • Make sure that the service's onCreate(), onStartCommand(), and onBind() methods are fast.
  • Avoid running any slow or blocking operations on the main thread from other components; these operations can prevent a service from starting quickly.

Common causes

The following table lists common causes of execute service ANRs and suggested fixes.

Cause What Suggested fix
Slow app startup The app takes too long to perform a cold start. Optimize slow app start.
Slow onCreate(), onStartCommand(), or onBind() The service component's onCreate(), onStartCommand(), or onBind() method takes too long to execute on the main thread. Optimize slow code. Move slow operations off the critical path where possible.
Not scheduled (main thread blocked before onStart()) The app's main thread is blocked by another component before the service can be started. Move other component's work off the main thread. Optimize other component's blocking code.

How to debug

From the cluster signature and ANR report in Google Play Console or Firebase Crashlytics, you can often determine the cause of the ANR based on what the main thread is doing.

The following flow chart describes how to debug an execute service ANR.

Figure 6. How to debug an execute service ANR.

If you've determined that the execute service ANR is actionable, follow these steps to help resolve the issue:

  1. Find the service component class in the ANR signature. In Google Play Console, the service component class is shown in the ANR signature. In the following example ANR details, it's com.example.app/MyService.

    com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly
    Executing service com.example.app/com.example.app.MyService
    
  2. Determine whether the slow or block operation is part of app startup, the service component, or elsewhere by checking for the following important function call(s) in the main threads.

    Function call(s) in main thread stacks What it means
    android.app.ActivityThread.handleBindApplication App was starting up, so the ANR was caused by slow app start.

    <ServiceClass>.onCreate()

    [...]

    android.app.ActivityThread.handleCreateService

    Service was being created, so the ANR was likely caused by slow onCreate() code.

    <ServiceClass>.onBind()

    [...]

    android.app.ActivityThread.handleBindService

    Service was being bound, so the ANR was likely caused by slow onBind() code.

    <ServiceClass>.onStartCommand()

    [...]

    android.app.ActivityThread.handleServiceArgs

    Service was being started, so the ANR was likely caused by slow onStartCommand() code.

    For example, if the onStartCommand() method in the MyService class is slow, the main threads will look like this:

    at com.example.app.MyService.onStartCommand(FooService.java:25)
    at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4820)
    at android.app.ActivityThread.-$$Nest$mhandleServiceArgs(unavailable:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2289)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8176)
    at java.lang.reflect.Method.invoke(Native method:0)
    

    اگر نمی‌توانید هیچ‌یک از تماس‌های عملکرد مهم را ببینید، چند احتمال دیگر وجود دارد:

    • سرویس درحال اجرا یا خاموش شدن است، که یعنی پشته‌ها خیلی دیر گرفته می‌شوند. در این مورد، می‌توانید ANR را به‌عنوان مثبت کاذب نادیده بگیرید.
    • جزء برنامه دیگری درحال اجرا است، مثلاً گیرنده رویداد ثبت‌شده. در این مورد، رشته اصلی احتمالاً در این مؤلفه مسدود شده است و مانع از شروع سرویس می‌شود.
  3. اگر فراخوانی تابع کلیدی را می‌بینید و می‌توانید به‌طور کلی محل وقوع خطای ANR را تعیین کنید، پشته‌های بقیه رشته اصلی را بررسی کنید تا عملکرد کند را پیدا کنید و آن را بهینه‌سازی کنید یا از مسیر بحرانی خارج کنید.

  4. برای اطلاعات بیشتر درباره سرویس‌ها، صفحه‌های زیر را ببینید:

    ارائه‌دهنده محتوا پاسخ نمی‌دهد

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

    دوره مهلت زمانی پیش‌فرض: توسط ارائه‌دهنده محتوا بااستفاده از ContentProviderClient.setDetectNotResponding مشخص می‌شود. دوره زمان اتمام ANR شامل کل زمان اجرای پُرسمان ارائه‌دهنده محتوای راه دور است که شامل راه‌اندازی سرد برنامه راه دور درصورت اجرا نشدن آن می‌شود.

    برای جلوگیری از خطاهای ANR ارائه‌دهنده محتوا، این روال‌های مطلوب را دنبال کنید:

    • مطمئن شوید که راه‌اندازی برنامه سریع باشد، زیرا اگر برنامه برای اجرای ارائه‌دهنده محتوا راه‌اندازی شود، در مهلت زمانی ANR محاسبه می‌شود.
    • مطمئن شوید که پُرسمان‌های ارائه‌دهنده محتوا سریع هستند.
    • تعداد زیادی تماس هم‌زمان با مسدودسازی چسباننده انجام ندهید که می‌تواند همه رشته‌های چسباننده برنامه را مسدود کند.

    دلایل متداول

    جدول زیر دلایل رایج خطاهای ANR ارائه‌دهنده محتوا و راه‌حل‌های پیشنهادی را فهرست می‌کند.

    علت اتفاقی که می‌افتد سیگنال اصلاح پیشنهادی
    پُرسمان ارائه‌دهنده محتوای کُند ارائه‌دهنده محتوا برای اجرا خیلی طول می‌کشد یا مسدود شده است. قاب android.content.ContentProvider$Transport.query در رشته پوشه است. پُرسمان ارائه‌دهنده محتوا را بهینه‌سازی کنید. ببینید چه چیزی رشته پیونددهنده را مسدود کرده است.
    راه‌اندازی آهسته برنامه برنامه ارائه‌دهنده محتوا برای راه‌اندازی بیش‌ازحد طول می‌کشد. قاب ActivityThread.handleBindApplication در رشته اصلی است. راه‌اندازی برنامه را بهینه‌سازی کنید.
    اتمام رشته پیونددهنده—همه رشته‌های پیونددهنده مشغول هستند همه رشته‌های پیونددهنده مشغول ارائه خدمات به درخواست‌های هم‌زمان دیگر هستند، بنابراین تماس پیونددهنده ارائه‌دهنده محتوا نمی‌تواند اجرا شود. برنامه شروع نمی‌شود، همه رشته‌های پوشه مشغول هستند، و ارائه‌دهنده محتوا اجرا نمی‌شود. بار روی رشته‌های پوشه را کاهش دهید. یعنی تماس‌های خروجی هم‌زمان کمتری برقرار کنید یا هنگام رسیدگی به تماس‌های ورودی کار کمتری انجام دهید.

    نحوه اشکال‌زدایی

    برای اشکال‌زدایی ANR ارائه‌دهنده محتوا بااستفاده از امضای خوشه و گزارش ANR در «کنسول Google Play» یا Firebase Crashlytics، ببینید رشته اصلی و رشته(های) پیونددهنده چه کاری انجام می‌دهند.

    جریان نمودار زیر نحوه اشکال‌زدایی کردن خطای ANR ارائه‌دهنده محتوا را شرح می‌دهد:

    شکل ۷. نحوه اشکال‌زدایی کردن ANR ارائه‌دهنده محتوا.

    تکه کد زیر نشان می‌دهد که رشته چسباننده وقتی به‌دلیل پُرسمان کند ارائه‌دهنده محتوا مسدود می‌شود چگونه به‌نظر می‌رسد. در این مورد، پُرسمان ارائه‌دهنده محتوا هنگام باز کردن پایگاه داده درانتظار قفل است.

    binder:11300_2 (tid=13) Blocked
    
    Waiting for osm (0x01ab5df9) held by at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers:182)
    at com.example.app.MyClass.blockingGetOpenDatabase(FooClass:171)
    [...]
    at com.example.app.MyContentProvider.query(MyContentProvider.java:915)
    at android.content.ContentProvider$Transport.query(ContentProvider.java:292)
    at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:107)
    at android.os.Binder.execTransactInternal(Binder.java:1339)
    at android.os.Binder.execTransact(Binder.java:1275)
    

    تکه کد زیر نشان می‌دهد که رشته اصلی وقتی به‌دلیل راه‌اندازی کند برنامه مسدود می‌شود چگونه است. در این مورد، راه‌اندازی برنامه به‌دلیل رقابت قفل درطول مقداردهی اولیه Dagger کند است.

    main (tid=1) Blocked
    
    [...]
    at dagger.internal.DoubleCheck.get(DoubleCheck:51)
    - locked 0x0e33cd2c (a qsn)at dagger.internal.SetFactory.get(SetFactory:126)
    at com.myapp.Bar_Factory.get(Bar_Factory:38)
    [...]
    at com.example.app.MyApplication.onCreate(DocsApplication:203)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6991)
    at android.app.ActivityThread.-$$Nest$mhandleBindApplication(unavailable:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2235)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8170)
    at java.lang.reflect.Method.invoke(Native method:0)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
    

    پاسخ کند به شغل

    «عدم پاسخگویی برنامه» (ANR) با پاسخ کند کار زمانی رخ می‌دهد که برنامه برای پاسخ دادن به JobService.onStartJob() یا JobService.onStopJob() خیلی طول بکشد، یا برای ارائه اعلان بااستفاده از JobService.setNotification() خیلی طول بکشد. این نشان می‌دهد که رشته اصلی برنامه درحال انجام کار دیگری مسدود شده است.

    اگر مشکل از JobService.onStartJob() یا JobService.onStopJob() است، ببینید در رشته اصلی چه اتفاقی می‌افتد. اگر مشکل مربوط به JobService.setNotification() است، در اسرع وقت با آن تماس بگیرید. قبل‌از ارائه اعلان، کار زیادی انجام ندهید.

    مشاهده نقض رشته‌سازی

    امتحان کردن روش «نوشتن»
    ‫Jetpack Compose جعبه‌ابزار واسط کاربر توصیه‌شده برای Android است.

    اگر برنامه شما نمایشی را در رشته پس‌زمینه تغییر دهد، می‌تواند وضعیت مسابقه‌ای را در وضعیت داخلی سلسله‌مراتب نما ایجاد کند. این نقض می‌تواند رشته اصلی (واسط کاربر) را از اجرای هرگونه پیام هم‌زمان مسدود کند و منجر به مشکلات جدی پایداری شود:

    • ثابت شدن بی‌صدای واسط کاربر: واسط کاربر برنامه به ورودی کاربر پاسخ نمی‌دهد.
    • ازکارافتادن‌ها: برنامه ممکن است با illegalStateException ازکار بیفتد.
    • خطاهای ANR: سیستم ممکن است باعث ایجاد مهلت زمانی ANR شود.

    این نقض رشته یکی از دلایل اصلی ردیابی‌های پشته‌ای ANR است که در آن رشته اصلی بدون فعالیت به‌نظر می‌رسد (برای نمونه، در «nativePollOnce» یا «رشته اصلی بدون فعالیت» ضبط شده است).

    نشت مانع همگام‌سازی

    نماها می‌توانند به‌صورت غیرمستقیم با تغییر وضعیتشان (برای مثال، فراخوانی TextView.setText، View.setVisibility) یا به‌صورت مستقیم با فراخوانی View.invalidate یا View.requestLayout نامعتبر شوند. وقتی نمایشی نامعتبر می‌شود، ViewRootImpl پیمایشی را زمان‌بندی می‌کند تا اندازه‌گیری، چیدمان، و رسم را انجام دهد و وضعیت میانای کاربر را به‌روز کند.

    1. زمان‌بندی پیمایش‌ها: ViewRootImpl پیمایش—گذر چیدمان و طراحی—را با پست کردن مانع همگام‌سازی در رشته رابط کاربر MessageQueue زمان‌بندی می‌کند. این مانع پردازش پیام عادی را موقتاً متوقف می‌کند تا چیدمان رابط کاربری اولویت پیدا کند.
    2. وضعیت مسابقه: وقتی چندین رشته تلاش می‌کنند نمایشی را به‌طور هم‌زمان نامعتبر کنند، برای زمان‌بندی این پیمایش با هم رقابت می‌کنند. هر دو رشته ممکن است با موفقیت مانع هم‌زمان‌سازی درج کنند، اما چارچوب فقط نشانه یکی از آن‌ها را ذخیره می‌کند.
    3. توقف موقت واسط کاربر: وقتی پیمایش اجرا می‌شود، فقط مانع ذخیره‌شده تکی را برمی‌دارد. مانع‌های ثانویه «فاش‌شده» برای همیشه در صف می‌مانند و رشته واسط کاربر را برای همیشه از پردازش پیام‌های هم‌زمان مسدود می‌کنند.

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

    برای جلوگیری از نقض‌های رشته‌سازی نما، این روال‌های مطلوب را دنبال کنید:

    • فقط با View شیء تعامل برقرار کنید—ازجمله خواندن ویژگی‌هایی مثل width یا height یا تنظیم ویژگی‌هایی مثل TextView's text—در رشته‌ای که در آن سلسله‌مراتب نما را ایجاد کرده‌اید. این تقریباً همیشه رشته اصلی یا رشته رابط کاربری است.
    • اگر نمی‌توانید تضمین کنید که هنگام دسترسی به نماها در رشته واسط کاربر اجرا می‌شوید، فرض کنید انجام این کار ناامن است. برای مثال، اگر از Coroutines برای کار پس‌زمینه‌ای استفاده می‌کنید، باید قبل‌از دستکاری کردن اشیاء View، به توزیع‌کننده اصلی بروید.

    lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
        val data = myRepository.getData()
        withContext(Dispatchers.Main) { // Switch context to Main
            myTextView.text = data.title
        }
    }

    برای کمک به شما در پیروی از روال‌های مطلوب، Android چندین روش برای دسترسی به رشته رابط کاربری از رشته‌های دیگر ارائه می‌دهد:

    بارکننده‌های تصویر محبوب، چارچوب‌های افزونه واکنشی، کتابخانه‌های گذرگاه رویداد، و دیگر راه‌حل‌های رشته‌بندی معمولاً روش‌هایی برای مشاهده رویدادهای خاص در رشته اصلی ارائه می‌دهند. این برای ناظران و شنوندگان رویدادی که نیاز به دریافت و تنظیم وضعیت نماها دارند مفید است.

    نحوه اشکال‌زدایی

    ‫Android 17 ابزارهای جدیدی را معرفی می‌کند تا به شما کمک کند نقض‌های مربوط به رشته‌بندی نما را درطول توسعه شناسایی، ردیابی، و برطرف کنید.

    ۱. ابزارهای چارچوب سازگاری

    ابزارهای چارچوب سازگاری به توسعه‌دهندگان برنامه امکان می‌دهد تغییرات رفتار را به‌صورت جداگانه بااستفاده از گزینه‌های توسعه‌دهندگان یا ADB روشن و خاموش کنند. بااستفاده از مراحل زیر می‌توانید تغییرات رفتار نقض رشته‌بندی را روشن/خاموش کنید:

    1. نسخه اشکال‌زدایی برنامه‌تان را در دستگاهی که Android 17 یا بالاتر را اجرا می‌کند نصب کنید.
    2. برنامه «تنظیمات» دستگاه را باز کنید و به سیستم > پیشرفته > گزینه‌های توسعه‌دهنده > تغییرات سازگاری برنامه بروید.
    3. برنامه‌تان را از فهرست انتخاب کنید.
    4. از فهرست تغییرات، کلید ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS را پیدا کنید و آن را روشن کنید.

    همچنین می‌توانید پرچم را بااستفاده از ADB روشن یا خاموش کنید:

    $ adb shell am compat enable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
    $ adb shell am compat disable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
    

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

    ۲. CalledFromWrongThreadListener API

    همچنین می‌توانید API جهانی View#registerCalledFromWrongThreadListener را برای شناسایی دسترسی غیرمجاز به رشته به‌صورت برنامه‌نویسی پیاده‌سازی کنید.

    • دورسنجی و ردیابی: این شنونده به برنامه شما اجازه می‌دهد وقتی «میانای برنامه‌سازی کاربردی نما» از یک رشته نادرست فراخوانی می‌شود، بازخوان دریافت کند و ثبت دورسنجی و ردیابی اشکالات را آسان‌تر کند.
    • اجرای به‌خط: شنونده به‌خط فراخوانده می‌شود و به شما امکان می‌دهد ردپشته دقیق را در لحظه وقوع نقض ضبط و بازرسی کنید.
    android {
       //...
       compileSdk = 37
    }
    

    val listener = object : View.CalledFromWrongThreadListener {
        override fun onCalledFromWrongThread() {
            // Handle the issue, e.g. crash if this is a dev build, or log an event
            // e.g. Log.d(TAG, "CalledFromWrongThread: ${Exception().stackTraceToString()}")
            // Unregister the listener to avoid redundant notifications for the same issue
            View.unregisterCalledFromWrongThreadListener(this)
        }
    }
    View.registerCalledFromWrongThreadListener(listener)

    nativePollOnce

    اگر قاب «nativePollOnce» یا «message queue idle» را در پشته‌های ANR مشاهده کردید، اغلب نشان می‌دهد که رشته مشکوک به عدم پاسخگویی درواقع در حالت آماده‌به‌کار بوده است و منتظر پیام‌های حلقه بوده است. در «کنسول Google Play»، جزئیات ANR به‌شکل زیر است:

    Native method - android.os.MessageQueue.nativePollOnce
    Executing service com.example.app/com.example.app.MyService
    

    برای مثال، اگر رشته اصلی بدون فعالیت باشد، پشته‌ها به این شکل است:

    "main" tid=1 NativeMain threadIdle
    
    #00  pc 0x00000000000d8b38  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
    #01  pc 0x0000000000019d88  /system/lib64/libutils.so (android::Looper::pollInner(int)+184)
    #02  pc 0x0000000000019c68  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112)
    #03  pc 0x000000000011409c  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
    at android.os.MessageQueue.nativePollOnce (Native method)
    at android.os.MessageQueue.next (MessageQueue.java:339)  at android.os.Looper.loop (Looper.java:208)
    at android.app.ActivityThread.main (ActivityThread.java:8192)
    at java.lang.reflect.Method.invoke (Native method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:626)
    at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1015)
    

    چند دلیل وجود دارد که چرا رشته مشکوک غیرپاسخگو می‌تواند بیکار باشد:

    • مشکل سراسری سیستم. این فرایند به‌دلیل بار سنگین سیستم یا مشکلی در سرور سیستم زمان‌بندی نشد.
    • گلچین آخر هفته. رشته درطول دوره کوتاه بین راه‌اندازی ANR و تخلیه پشته‌ها بازیابی شد. تأخیر در Pixel با Android 13 حدود ۱۰۰ میلی‌ثانیه است، اما می‌تواند از ۱ ثانیه بیشتر شود. تأخیر در Pixel در Android 14 معمولاً کمتر از ۱۰ میلی‌ثانیه است.
    • ارجاع نادرست به Thread. رشته‌ای که برای ساختن امضای ANR استفاده شده است رشته غیرپاسخگوی واقعی که باعث ANR شده است نیست.
    • مشاهده نقض رشته‌بندی. اگر برنامه شما نمایشی را در رشته پس‌زمینه تغییر دهد، می‌تواند باعث ایجاد وضعیت مسابقه در بخش‌های داخلی نما شود که مانع اجرای وظایف رشته میانای کاربری می‌شود

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

    دسته علت اصلاح پیشنهادی
    مهلت ارسال ورودی تمام شد مشکل سراسری
    تخلیه پشته با تأخیر
    لازم نیست اقدامی انجام دهید.
    پنجره‌ای متمرکز نیست مشکل سراسری سیستم
    تخلیه پشته دیررس
    مشاهده نقض رشته‌بندی
    پایگاه کد را ازنظر نقض‌های رشته‌بندی نما بررسی کنید.
    پایان مهلت گیرنده رویداد ثبت‌شده تخلیه پشته دیررس
    مشکل سراسری سیستم
    انتساب نادرست رشته
    مشاهده نقض رشته‌بندی
    رشته‌های مربوطه را در تخلیه پشته بازرسی کنید.
    پایگاه کد را برای نقض‌های رشته‌بندی نما بررسی کنید.
    مهلت زمانی «اجرای سرویس» تمام شد تخلیه پشته دیررس
    مشکل سراسری سیستم
    مشاهده نقض رشته‌بندی
    پایگاه کد را ازنظر نقض‌های رشته‌بندی نما بررسی کنید.
    ارائه‌دهنده محتوا پاسخ نمی‌دهد دیر رسیدن اطلاعات پشته
    مشکل سراسری سیستم
    انتساب نادرست رشته
    رشته‌های مربوطه را در تخلیه پشته بازرسی کنید.

    مراحل توصیه‌شده برای تجزیه‌وتحلیل ANRهای nativePollOnce در اینجا آمده است.

    1. بار سنگین سیستم: فشار کلی منابع دستگاه مانند کمبود CPU، حافظه، یا I/O در کل سیستم را به‌عنوان علت اصلی عدم پاسخ‌دهی ارزیابی کنید.
    2. اختصاص نادرست رشته: رشته‌های کارگر و پیونددهنده را برای بن‌بست‌ها، رقابت قفل که بر رشته اصلی تأثیر می‌گذارد، یا رشته‌های پس‌زمینه‌ای معلق که عناصر ناهمگام را پردازش می‌کنند (برای نمونه، goAsync()) ممیزی کنید.
    3. مشاهده نقض‌های رشته‌بندی: رشته‌های پس‌زمینه را برای تغییرات غیرقانونی سلسله‌مراتب نمای واسط کاربر اسکن می‌کند، که می‌تواند باعث شود مانع همگام‌سازی در MessageQueue بدون والد شود و همه پیام‌های هم‌زمان را برای همیشه مسدود کند.

    هیچ قاب پشته‌ای وجود ندارد

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

    • برداشتن پشته خیلی طول می‌کشد و مهلت زمانی آن به‌پایان می‌رسد.
    • فرایند قبل‌از اینکه پشته‌ها گرفته شوند متوقف شد یا کشته شد.
    [...]
    
    --- CriticalEventLog ---
    capacity: 20
    timestamp_ms: 1666030897753
    window_ms: 300000
    
    libdebuggerd_client: failed to read status response from tombstoned: timeout reached?
    
    ----- Waiting Channels: pid 7068 at 2022-10-18 02:21:37.<US_SOCIAL_SECURITY_NUMBER>+0800 -----
    
    [...]
    

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

    مشکلات شناخته‌شده

    نگه داشتن زمان‌سنج در فرایند برنامه برای اهداف تکمیل مدیریت همه‌فرستی قبل‌از راه‌اندازی ANR ممکن است به‌دلیل روش ناهمزمان سیستم برای نظارت بر ANR به‌درستی کار نکند.