ANR ها را تشخیص داده و اصلاح کنید

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

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

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

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

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

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

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

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

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

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

برای جلوگیری از ANR های ارسال ورودی، این بهترین شیوه‌ها را دنبال کنید:

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

علل شایع

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

علت چه اتفاقی می‌افتد؟ اصلاحات پیشنهادی
تماس گیرنده آهسته نخ اصلی یک فراخوانی همزمان و طولانی بایندر انجام می‌دهد. فراخوانی را از نخ اصلی خارج کنید یا اگر API را در اختیار دارید، سعی کنید فراخوانی را بهینه کنید.
بسیاری از تماس‌های متوالی با binder نخ اصلی فراخوانی‌های متوالی و همزمانِ binder زیادی انجام می‌دهد. فراخوانی‌های binder را در یک حلقه‌ی تنگ انجام ندهید.
مسدود کردن ورودی/خروجی نخ اصلی (Main thread) فراخوانی‌های ورودی/خروجی (I/O) مانند دسترسی به پایگاه داده یا شبکه را مسدود می‌کند. تمام ورودی/خروجی‌های مسدودکننده را از نخ اصلی خارج کنید.
مشاجره قفل رشته اصلی در انتظار دریافت قفل مسدود شده است. کاهش درگیری قفل بین نخ اصلی و نخ دیگر. بهینه‌سازی کد کند در نخ دیگر.
قاب گران‌قیمت رندر کردن بیش از حد در یک فریم، باعث ایجاد لرزش شدید می‌شود. کار کمتری روی رندر کردن فریم انجام دهید. از الگوریتم‌های n- 2 استفاده نکنید. از کامپوننت‌های کارآمد برای کارهایی مانند اسکرول کردن یا صفحه‌بندی استفاده کنید - برای مثال، کتابخانه Jetpack Paging .
توسط کامپوننت دیگری مسدود شده است یک کامپوننت متفاوت، مانند یک گیرنده پخش، در حال اجرا است و نخ اصلی را مسدود می‌کند. تا حد امکان کارهای غیر رابط کاربری را از نخ اصلی خارج کنید. گیرنده‌های پخش را روی نخ دیگری اجرا کنید.
هنگ کردن پردازنده گرافیکی (GPU) هنگ کردن پردازنده گرافیکی (GPU) یک مشکل سیستمی یا سخت‌افزاری است که باعث مسدود شدن رندرینگ و در نتیجه، اختلال در ارسال ورودی (ANR) می‌شود. متأسفانه، معمولاً هیچ راه حلی از طرف برنامه وجود ندارد. در صورت امکان، برای عیب یابی با تیم سخت افزار تماس بگیرید.

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

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

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

شکل ۱. نحوه اشکال‌زدایی یک ANR ارسال ورودی.

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

شکل ۲. تشخیص ANR مربوط به علائم حیاتی (Response Vitals)

بدون پنجره متمرکز

در حالی که رویدادهایی مانند لمس بر اساس تست ضربه مستقیماً به پنجره مربوطه ارسال می‌شوند، رویدادهایی مانند کلیدها به یک هدف نیاز دارند. این هدف به عنوان پنجره متمرکز شناخته می‌شود. در هر صفحه نمایش فقط یک پنجره متمرکز وجود دارد و معمولاً پنجره‌ای است که کاربر در حال حاضر با آن در تعامل است. اگر یک پنجره متمرکز پیدا نشود، ورودی یک 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() را فراخوانی نمی‌کنند، timeout به این معنی است که onReceive() به موقع تکمیل نشده است. برای گیرنده‌های ناهمزمان یا گیرنده‌هایی که goAsync() را فراخوانی می‌کنند، timeout به این معنی است که PendingResult.finish() به موقع فراخوانی نشده است.

ANR های گیرنده پخش اغلب در این موضوعات اتفاق می افتند:

  • تاپیک اصلی، اگر مشکل کندی شروع برنامه است.
  • اگر مشکل کندی کد onReceive() باشد، thread گیرنده پخش را اجرا می‌کند.
  • اگر مشکل کندی کد پخش goAsync() باشد، نخ‌های کارگر پخش (Broadcast worker threads)

برای جلوگیری از ANR های گیرنده پخش، این بهترین شیوه‌ها را دنبال کنید:

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

دوره‌های تایم‌اوت

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

نوع قصد اندروید ۱۳ و پایین‌تر اندروید ۱۴ و بالاتر

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

(مجموعه FLAG_RECEIVER_FOREGROUND )

۱۰ ثانیه

۱۰-۲۰ ثانیه، بسته به اینکه آیا فرآیند به CPU نیاز دارد یا خیر

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

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

۶۰ ثانیه

۶۰ تا ۱۲۰ ثانیه، بسته به اینکه آیا فرآیند به CPU نیاز دارد یا خیر

برای تشخیص اینکه آیا پرچم FLAG_RECEIVER_FOREGROUND تنظیم شده است یا خیر، در موضوع ANR به دنبال "flg=" بگردید و وجود 0x10000000 را بررسی کنید. اگر این بیت تنظیم شده باشد، آنگاه intent دارای 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.

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

کنسول گوگل پلی کلاس گیرنده و قصد پخش را در امضای 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 به طور کلی در کجا اتفاق می‌افتد، بقیه پشته‌های نخ اصلی را بررسی کنید تا عملیات کند را پیدا کرده و آن را بهینه کنید یا از مسیر بحرانی خارج کنید.

برای اطلاعات بیشتر در مورد خدمات، به صفحات زیر مراجعه کنید:

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

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

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

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

  • مطمئن شوید که راه‌اندازی برنامه سریع باشد، زیرا اگر برنامه برای اجرای ارائه‌دهنده محتوا شروع به کار کند، در زمان‌بندی ANR محاسبه می‌شود.
  • مطمئن شوید که کوئری‌های ارائه‌دهنده محتوا سریع هستند.
  • تعداد زیادی فراخوانی همزمان binder مسدودکننده انجام ندهید که می‌تواند تمام threadهای binder برنامه را مسدود کند.

علل شایع

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

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

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

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

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

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

قطعه کد زیر نشان می‌دهد که وقتی رشته‌ی اتصال (binder thread) به دلیل کندی کوئریِ ارائه‌دهنده‌ی محتوا مسدود می‌شود، چه شکلی می‌شود. در این حالت، کوئریِ ارائه‌دهنده‌ی محتوا هنگام باز کردن یک پایگاه داده منتظر قفل شدن است.

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() است، بررسی کنید که در thread اصلی چه اتفاقی می‌افتد. اگر مشکل از JobService.setNotification() است، حتماً آن را در اسرع وقت فراخوانی کنید. قبل از ارائه اعلان، کار زیادی انجام ندهید.

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

روش نوشتن را امتحان کنید
Jetpack Compose ابزار رابط کاربری پیشنهادی برای اندروید است.

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

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

این نقض نخ‌بندی یکی از دلایل اصلی ردیابی پشته ANR است که در آن نخ اصلی بیکار به نظر می‌رسد (مثلاً در "nativePollOllOnce" یا "نخ اصلی بیکار" ثبت شده است).

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. یک نسخه قابل اشکال‌زدایی از برنامه خود را روی دستگاهی که اندروید ۱۷ یا بالاتر دارد نصب کنید.
  2. برنامه تنظیمات دستگاه خود را باز کنید و به مسیر System > Advanced > Developer options > App Compatibility Changes بروید.
  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>

فعال کردن این پرچم، برنامه را مجبور می‌کند هر زمان که یک View API از thread اشتباه فراخوانی شود، یک استثنا ایجاد کرده و از کار بیفتد، که به شما کمک می‌کند مشکلات نقض view threading را شناسایی و برطرف کنید.

۲. API فراخوانی‌شده از WrongThreadListener

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

  • تله‌متری و ردیابی : این شنونده به برنامه شما اجازه می‌دهد تا در صورت فراخوانی یک View API از یک thread نادرست، callback دریافت کند و ثبت تله‌متری و ردیابی اشکالات را آسان‌تر کند.
  • اجرای درون‌خطی : شنونده درون‌خطی نامیده می‌شود و به شما امکان می‌دهد رد دقیق پشته را در لحظه وقوع تخلف ثبت و بررسی کنید.
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

اگر فریم "nativePollOllOnce" یا "message queue idle" را در پشته‌های ANR مشاهده کردید، اغلب نشان می‌دهد که رشته مشکوک به عدم پاسخگویی در واقع بیکار بوده و منتظر پیام‌های looper بوده است. در کنسول 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 و تخلیه پشته‌ها بازیابی شده است. تأخیر در Pixels در اندروید ۱۳ حدود ۱۰۰ میلی‌ثانیه است، اما می‌تواند از ۱ ثانیه نیز فراتر رود. تأخیر در Pixels در اندروید ۱۴ معمولاً کمتر از ۱۰ میلی‌ثانیه است.
  • انتساب نادرست رشته. رشته‌ای که برای ساخت امضای ANR استفاده شده، رشته‌ی بی‌پاسخی که باعث ANR شده بود، نبود.
  • نقض نخ‌بندی نما . اگر برنامه شما یک نما را در یک نخ پس‌زمینه تغییر دهد، می‌تواند باعث ایجاد شرایط رقابتی در بخش‌های داخلی نما شود که مانع از اجرای وظایف نخ رابط کاربری می‌شود.

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

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

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

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

بدون فریم‌های پشته‌ای

برخی از گزارش‌های 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ها به درستی کار نکند.