هنگامی که رشته رابط کاربری یک برنامه Android برای مدت طولانی مسدود می شود، خطای "Application Not Responsing" (ANR) ایجاد می شود. اگر برنامه در پیش زمینه باشد، سیستم یک گفتگو را به کاربر نشان می دهد، همانطور که در شکل 1 نشان داده شده است. گفتگوی ANR به کاربر این فرصت را می دهد که برنامه را اجباری ترک کند.
ANR ها یک مشکل هستند زیرا رشته اصلی برنامه که مسئول به روز رسانی رابط کاربری است، نمی تواند رویدادهای ورودی کاربر را پردازش کند یا ترسیم کند و باعث ناامیدی کاربر می شود. برای اطلاعات بیشتر در مورد رشته اصلی برنامه، به فرآیندها و رشتهها مراجعه کنید.
هنگامی که یکی از شرایط زیر رخ می دهد، یک ANR برای برنامه شما فعال می شود:
- زمان ارسال ورودی به پایان رسیده است: اگر برنامه شما در عرض 5 ثانیه به یک رویداد ورودی (مانند فشار دادن کلید یا لمس صفحه) پاسخ نداده باشد.
- اجرای سرویس: اگر سرویسی که توسط برنامه شما اعلام شده است نتواند اجرای
Service.onCreate()
وService.onStartCommand()
/Service.onBind()
را در عرض چند ثانیه به پایان برساند. - Service.startForeground() فراخوانی نشده است: اگر برنامه شما از
Context.startForegroundService()
برای راه اندازی یک سرویس جدید در پیش زمینه استفاده می کند، اما سرویس پس از 5 ثانیهstartForeground()
فراخوانی نمی کند. - Broadcast of intent: اگر اجرای
BroadcastReceiver
در مدت زمان معینی به پایان نرسیده باشد. اگر برنامه فعالیتی در پیش زمینه داشته باشد، این زمان 5 ثانیه است. - تعاملات JobScheduler: اگر
JobService
ازJobService.onStartJob()
یاJobService.onStopJob()
در عرض چند ثانیه برنگردد، یا اگر یک کار آغاز شده توسط کاربر شروع شود و برنامه شما در عرض چند ثانیهJobService.setNotification()
را فرا نگیرد. پس از فراخوانیJobService.onStartJob()
. برای برنامههایی که Android 13 و پایینتر را هدف قرار میدهند، ANRها بیصدا هستند و به برنامه گزارش نمیشوند. برای برنامههایی که Android 14 و بالاتر را هدف قرار میدهند، ANRها صریح هستند و به برنامه گزارش میشوند.
اگر برنامه شما دارای ANR است، میتوانید از راهنمایی در این مقاله برای تشخیص و رفع مشکل استفاده کنید.
مشکل را تشخیص دهید
اگر قبلاً برنامه خود را منتشر کردهاید، میتوانید از Android vitals برای مشاهده اطلاعات مربوط به ANR برای برنامه خود استفاده کنید. میتوانید از ابزارهای دیگری برای شناسایی ANR در میدان استفاده کنید، اما توجه داشته باشید که ابزارهای 3P نمیتوانند ANR را در نسخههای قدیمیتر Android (اندروید 10 و پایینتر) گزارش کنند، برخلاف Android vitals.
حیاتی اندروید
Android vitals می تواند به شما در نظارت و بهبود نرخ ANR برنامه کمک کند. Android vitals چندین نرخ ANR را اندازه گیری می کند:
- نرخ ANR: درصد کاربران فعال روزانه شما که هر نوع ANR را تجربه کرده اند.
- نرخ ANR درک شده توسط کاربر: درصدی از کاربران فعال روزانه شما که حداقل یک ANR درک شده توسط کاربر را تجربه کردهاند. در حال حاضر فقط ANR هایی از نوع
Input dispatching timed out
است که توسط کاربر درک می شوند. - نرخ ANR چندگانه: درصد کاربران فعال روزانه شما که حداقل دو ANR را تجربه کردهاند.
کاربر فعال روزانه یک کاربر منحصر به فرد است که از برنامه شما در یک روز در یک دستگاه واحد استفاده می کند، به طور بالقوه در چندین جلسه. اگر کاربر در یک روز از برنامه شما در بیش از یک دستگاه استفاده کند، هر دستگاه به تعداد کاربران فعال آن روز کمک خواهد کرد. اگر چند کاربر در یک روز از یک دستگاه استفاده کنند، این به عنوان یک کاربر فعال حساب می شود.
نرخ ANR درک شده توسط کاربر یک امر حیاتی اصلی است که بر قابلیت کشف برنامه شما در Google Play تأثیر می گذارد. این مهم است زیرا ANR هایی که شمارش می کند همیشه زمانی اتفاق می افتد که کاربر با برنامه درگیر است و بیشترین اختلال را ایجاد می کند.
Play دو آستانه رفتار بد در این معیار تعریف کرده است:
- آستانه رفتار بد کلی: حداقل 0.47٪ از کاربران فعال روزانه یک ANR درک شده توسط کاربر را در همه مدلهای دستگاه تجربه میکنند.
- آستانه رفتار بد برای هر دستگاه: حداقل 8٪ از کاربران روزانه یک ANR درک شده توسط کاربر را برای یک مدل دستگاه تجربه می کنند.
اگر برنامه شما از آستانه کلی رفتار بد فراتر رود، احتمالاً در همه دستگاهها کمتر قابل شناسایی است. اگر برنامه شما از آستانه رفتار بد برای هر دستگاه در برخی دستگاهها فراتر رود، احتمالاً در آن دستگاهها کمتر قابل شناسایی است و ممکن است هشداری در فهرست فروشگاه شما نشان داده شود.
Android vitals میتواند از طریق کنسول Play به شما هشدار دهد که برنامهتان ANR بیش از حد نشان میدهد.
برای اطلاعات در مورد نحوه جمعآوری دادههای حیاتی Android توسط Google Play، به مستندات کنسول Play مراجعه کنید.
ANR ها را تشخیص دهید
الگوهای رایجی وجود دارد که باید هنگام تشخیص ANR به دنبال آنها باشید:
- برنامه در حال انجام عملیات آهسته شامل I/O در رشته اصلی است.
- برنامه در حال انجام یک محاسبه طولانی در موضوع اصلی است.
- موضوع اصلی در حال انجام یک فراخوانی همزمان بایندر به یک فرآیند دیگر است، و آن فرآیند دیگر زمان زیادی را برای بازگشت میبرد.
- رشته اصلی در انتظار یک بلوک همگامسازی شده برای یک عملیات طولانی که در رشته دیگری در حال انجام است مسدود میشود.
- thread اصلی یا در فرآیند شما یا از طریق تماس بایندر در بن بست با رشته دیگری قرار دارد. موضوع اصلی فقط منتظر پایان یک عملیات طولانی نیست، بلکه در وضعیت بن بست قرار دارد. برای اطلاعات بیشتر، بن بست در ویکی پدیا را ببینید.
تکنیکهای زیر میتوانند به شما در تعیین علت ANR کمک کنند.
HealthStats
HealthStats
با ثبت کل زمان کاربر و سیستم، زمان CPU، شبکه، آمارهای رادیویی، زمان روشن/خاموش صفحه و هشدارهای بیدار شدن، معیارهایی درباره سلامت یک برنامه ارائه میکند. این می تواند به اندازه گیری مصرف کلی CPU و تخلیه باتری کمک کند.
اشکال زدایی
Debug
به بررسی برنامه های Android در حین توسعه کمک می کند، از جمله تعداد ردیابی و تخصیص برای شناسایی jank و تاخیر در برنامه ها. همچنین میتوانید از Debug
برای دریافت شمارشگرهای زمان اجرا و حافظه بومی و معیارهای حافظه استفاده کنید که میتواند به شناسایی ردپای حافظه یک فرآیند خاص کمک کند.
ApplicationExitInfo
ApplicationExitInfo
در Android 11 (سطح API 30) یا بالاتر موجود است و اطلاعاتی درباره دلیل خروج از برنامه ارائه می دهد. این شامل ANR، حافظه کم، خرابی برنامه، استفاده بیش از حد از CPU، وقفه های کاربر، وقفه های سیستم یا تغییرات مجوز زمان اجرا می شود.
حالت سختگیرانه
استفاده از StrictMode
به شما کمک می کند تا در حین توسعه برنامه، عملیات ورودی/خروجی تصادفی را در رشته اصلی پیدا کنید. می توانید از StrictMode
در سطح برنامه یا فعالیت استفاده کنید.
گفتگوهای ANR پس زمینه را فعال کنید
Android تنها در صورتی گفتگوهای ANR را برای برنامههایی که پردازش پیام پخش طولانی طول میکشد نشان میدهد که نمایش همه ANR در گزینههای برنامهنویس دستگاه فعال باشد. به همین دلیل، گفتگوهای ANR پسزمینه همیشه به کاربر نمایش داده نمیشوند، اما برنامه همچنان ممکن است مشکلات عملکردی داشته باشد.
Traceview
شما می توانید از Traceview برای دریافت ردی از برنامه در حال اجرا خود در حین مرور موارد استفاده و شناسایی مکان هایی که رشته اصلی در آن مشغول است استفاده کنید. برای کسب اطلاعات در مورد نحوه استفاده از Traceview، به نمایه سازی با Traceview و dmtracedump مراجعه کنید.
یک فایل ردیابی را بکشید
وقتی Android با 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 در دستگاه توسعه خود، گزارش اشکال را از یک دستگاه فیزیکی بگیرید. برای اطلاعات بیشتر، به ضبط و خواندن گزارشهای اشکال مراجعه کنید.
مشکلات را برطرف کنید
پس از شناسایی مشکل، می توانید از نکات این بخش برای رفع مشکلات رایج استفاده کنید.
کد آهسته در موضوع اصلی
مکان هایی را در کد خود شناسایی کنید که رشته اصلی برنامه بیش از 5 ثانیه در آنها مشغول است. به دنبال موارد استفاده مشکوک در برنامه خود بگردید و سعی کنید ANR را بازتولید کنید.
به عنوان مثال، شکل 2 یک جدول زمانی Traceview را نشان می دهد که در آن موضوع اصلی بیش از 5 ثانیه مشغول است.
شکل 2 نشان می دهد که بیشتر کدهای توهین آمیز در کنترل کننده onClick(View)
اتفاق می افتد، همانطور که در مثال کد زیر نشان داده شده است:
کاتلین
override fun onClick(v: View) { // This task runs on the main thread. BubbleSort.sort(data) }
جاوا
@Override public void onClick(View view) { // This task runs on the main thread. BubbleSort.sort(data); }
در این صورت باید کاری را که در نخ اصلی اجرا می شود به نخ کارگر منتقل کنید. چارچوب Android شامل کلاسهایی است که میتوانند به انتقال کار به یک موضوع کارگر کمک کنند. برای اطلاعات بیشتر به موضوعات کارگر مراجعه کنید.
IO در موضوع اصلی
اجرای عملیات IO روی رشته اصلی یکی از دلایل رایج کندی عملیات روی رشته اصلی است که می تواند باعث ANR شود. همانطور که در بخش قبل نشان داده شده است، توصیه می شود که تمام عملیات IO را به یک موضوع کارگر منتقل کنید.
برخی از نمونههای عملیات IO، عملیات شبکه و ذخیرهسازی هستند. برای اطلاعات بیشتر، به انجام عملیات شبکه و ذخیره داده مراجعه کنید.
قفل جدال
در برخی از سناریوها، کاری که باعث ANR میشود مستقیماً در رشته اصلی برنامه اجرا نمیشود. اگر یک نخ کارگر قفلی را روی منبعی نگه دارد که رشته اصلی برای تکمیل کار خود به آن نیاز دارد، ممکن است یک ANR اتفاق بیفتد.
به عنوان مثال، شکل 3 جدول زمانی Traceview را نشان می دهد که در آن بیشتر کار بر روی یک نخ کارگر انجام می شود.
شکل 3. جدول زمانی Traceview که کار در حال اجرا روی یک موضوع کارگر را نشان می دهد.
اما اگر کاربران شما هنوز ANR را تجربه میکنند، باید به وضعیت رشته اصلی در مانیتور دستگاه Android نگاه کنید. معمولاً اگر رشته اصلی برای به روز رسانی رابط کاربری آماده باشد و به طور کلی پاسخگو باشد، در حالت RUNNABLE
است.
اما اگر thread اصلی نتواند اجرا را از سر بگیرد، در حالت BLOCKED
است و نمی تواند به رویدادها پاسخ دهد. همانطور که در شکل 5 نشان داده شده است، وضعیت در مانیتور دستگاه Android به صورت مانیتور یا صبر نشان داده می شود.
ردیابی زیر رشته اصلی برنامه را نشان می دهد که در انتظار منبع مسدود شده است:
...
AsyncTask #2" prio=5 tid=18 Runnable
| group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
| sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
| state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
| stack=0x94a7e000-0x94a80000 stackSize=1038KB
| held mutexes= "mutator lock"(shared held)
at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
- locked <0x083105ee> (a java.lang.Boolean)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
at android.os.AsyncTask$2.call(AsyncTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
...
بررسی ردیابی می تواند به شما کمک کند تا کدی را که رشته اصلی را مسدود می کند، پیدا کنید. کد زیر مسئول نگه داشتن قفلی است که نخ اصلی را در ردیابی قبلی مسدود می کند:
کاتلین
override fun onClick(v: View) { // The worker thread holds a lock on lockedResource LockTask().execute(data) synchronized(lockedResource) { // The main thread requires lockedResource here // but it has to wait until LockTask finishes using it. } } class LockTask : AsyncTask<Array<Int>, Int, Long>() { override fun doInBackground(vararg params: Array<Int>): Long? = synchronized(lockedResource) { // This is a long-running operation, which makes // the lock last for a long time BubbleSort.sort(params[0]) } }
جاوا
@Override public void onClick(View v) { // The worker thread holds a lock on lockedResource new LockTask().execute(data); synchronized (lockedResource) { // The main thread requires lockedResource here // but it has to wait until LockTask finishes using it. } } public class LockTask extends AsyncTask<Integer[], Integer, Long> { @Override protected Long doInBackground(Integer[]... params) { synchronized (lockedResource) { // This is a long-running operation, which makes // the lock last for a long time BubbleSort.sort(params[0]); } } }
مثال دیگر رشته اصلی یک برنامه است که در انتظار نتیجه از یک موضوع کارگر است، همانطور که در کد زیر نشان داده شده است. توجه داشته باشید که استفاده از wait()
و notify()
یک الگوی توصیه شده در Kotlin نیست، که مکانیسم های خاص خود را برای مدیریت همزمانی دارد. هنگام استفاده از Kotlin، در صورت امکان باید از مکانیسم های مخصوص Kotlin استفاده کنید.
کاتلین
fun onClick(v: View) { val lock = java.lang.Object() val waitTask = WaitTask(lock) synchronized(lock) { try { waitTask.execute(data) // Wait for this worker thread’s notification lock.wait() } catch (e: InterruptedException) { } } } internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() { override fun doInBackground(vararg params: Array<Int>): Long? { synchronized(lock) { BubbleSort.sort(params[0]) // Finished, notify the main thread lock.notify() } } }
جاوا
public void onClick(View v) { WaitTask waitTask = new WaitTask(); synchronized (waitTask) { try { waitTask.execute(data); // Wait for this worker thread’s notification waitTask.wait(); } catch (InterruptedException e) {} } } class WaitTask extends AsyncTask<Integer[], Integer, Long> { @Override protected Long doInBackground(Integer[]... params) { synchronized (this) { BubbleSort.sort(params[0]); // Finished, notify the main thread notify(); } } }
موقعیتهای دیگری نیز وجود دارند که میتوانند رشته اصلی را مسدود کنند، از جمله رشتههایی که از Lock
، Semaphore
و همچنین یک منبع منبع (مانند یک مخزن اتصالات پایگاه داده) یا سایر مکانیسمهای حذف متقابل (mutex) استفاده میکنند.
شما باید قفل هایی را که برنامه شما روی منابع نگه می دارد به طور کلی ارزیابی کنید، اما اگر می خواهید از ANR جلوگیری کنید، باید به قفل هایی که برای منابع مورد نیاز رشته اصلی نگه داشته شده است نگاه کنید.
مطمئن شوید که قفل ها برای کمترین زمان نگه داشته می شوند، یا حتی بهتر، ارزیابی کنید که آیا برنامه در وهله اول به نگه داشتن نیاز دارد یا خیر. اگر از قفل برای تعیین زمان به روز رسانی UI بر اساس پردازش یک thread کارگر استفاده می کنید، از مکانیسم هایی مانند onProgressUpdate()
و onPostExecute()
برای برقراری ارتباط بین worker و thread های اصلی استفاده کنید.
بن بست ها
بن بست زمانی رخ می دهد که یک رشته وارد حالت انتظار می شود زیرا یک منبع مورد نیاز توسط رشته دیگری نگه داشته می شود، که همچنین منتظر منبعی است که توسط نخ اول نگهداری می شود. اگر رشته اصلی برنامه در این وضعیت باشد، احتمال وقوع ANR وجود دارد.
بن بست ها پدیده ای است که به خوبی در علوم کامپیوتر مطالعه شده است و الگوریتم های پیشگیری از بن بست وجود دارد که می توانید از آنها برای جلوگیری از بن بست استفاده کنید.
برای اطلاعات بیشتر، الگوریتمهای پیشگیری از بنبست و بنبست را در ویکیپدیا ببینید.
گیرنده های پخش کند
برنامهها میتوانند به پیامهای پخششده، مانند فعال یا غیرفعال کردن حالت هواپیما یا تغییر وضعیت اتصال، از طریق گیرندههای پخش پاسخ دهند. ANR زمانی اتفاق میافتد که پردازش پیام پخششده توسط برنامه خیلی طول بکشد.
ANR در موارد زیر رخ می دهد:
- یک گیرنده پخش، اجرای متد
onReceive()
خود را در مدت زمان قابل توجهی به پایان نرسانده است. - گیرنده پخش
goAsync()
را فراخوانی می کند وfinish()
در شیPendingResult
فراخوانی نمی کند.
برنامه شما فقط باید عملیات کوتاهی را در روش onReceive()
BroadcastReceiver
انجام دهد. با این حال، اگر برنامه شما در نتیجه یک پیام پخش به پردازش پیچیده تری نیاز دارد، باید کار را به IntentService
موکول کنید.
میتوانید از ابزارهایی مانند Traceview برای تشخیص اینکه گیرنده پخش شما عملیات طولانیمدت را روی رشته اصلی برنامه اجرا میکند یا خیر، استفاده کنید. به عنوان مثال، شکل 6 جدول زمانی یک گیرنده پخش را نشان می دهد که یک پیام را در رشته اصلی برای تقریبا 100 ثانیه پردازش می کند.
این رفتار می تواند با اجرای عملیات طولانی مدت روی متد onReceive()
BroadcastReceiver
ایجاد شود، همانطور که در مثال زیر نشان داده شده است:
کاتلین
override fun onReceive(context: Context, intent: Intent) { // This is a long-running operation BubbleSort.sort(data) }
جاوا
@Override public void onReceive(Context context, Intent intent) { // This is a long-running operation BubbleSort.sort(data); }
در شرایطی مانند این، توصیه میشود عملیات طولانیمدت را به IntentService
منتقل کنید زیرا از یک thread کارگر برای اجرای کار خود استفاده میکند. کد زیر نحوه استفاده از IntentService
برای پردازش یک عملیات طولانی مدت را نشان می دهد:
کاتلین
override fun onReceive(context: Context, intent: Intent) { Intent(context, MyIntentService::class.java).also { intentService -> // The task now runs on a worker thread. context.startService(intentService) } } class MyIntentService : IntentService("MyIntentService") { override fun onHandleIntent(intent: Intent?) { BubbleSort.sort(data) } }
جاوا
@Override public void onReceive(Context context, Intent intent) { // The task now runs on a worker thread. Intent intentService = new Intent(context, MyIntentService.class); context.startService(intentService); } public class MyIntentService extends IntentService { @Override protected void onHandleIntent(@Nullable Intent intent) { BubbleSort.sort(data); } }
در نتیجه استفاده از IntentService
، عملیات طولانیمدت به جای نخ اصلی، روی یک thread کارگر اجرا میشود. شکل 7 کار موکول شده به thread کارگر را در جدول زمانی Traceview نشان می دهد.
گیرنده پخش شما می تواند از goAsync()
برای سیگنال دادن به سیستم استفاده کند که برای پردازش پیام به زمان بیشتری نیاز دارد. با این حال، باید finish()
در شی PendingResult
فراخوانی کنید. مثال زیر نحوه فراخوانی () finish را نشان می دهد تا به سیستم اجازه دهد گیرنده پخش را بازیافت کند و از ANR جلوگیری کند:
کاتلین
val pendingResult = goAsync() object : AsyncTask<Array<Int>, Int, Long>() { override fun doInBackground(vararg params: Array<Int>): Long? { // This is a long-running operation BubbleSort.sort(params[0]) pendingResult.finish() return 0L } }.execute(data)
جاوا
final PendingResult pendingResult = goAsync(); new AsyncTask<Integer[], Integer, Long>() { @Override protected Long doInBackground(Integer[]... params) { // This is a long-running operation BubbleSort.sort(params[0]); pendingResult.finish(); } }.execute(data);
با این حال، انتقال کد از یک گیرنده پخش کند به رشته دیگر و استفاده از goAsync()
اگر پخش در پسزمینه باشد، ANR را برطرف نمیکند. مهلت زمانی ANR همچنان اعمال می شود.
GameActivity
کتابخانه GameActivity
ANR را در مطالعات موردی بازیها و برنامههایی که به زبان C یا C++ نوشته شدهاند کاهش داده است. اگر فعالیت بومی موجود خود را با GameActivity
جایگزین کنید، می توانید مسدود کردن رشته رابط کاربری را کاهش دهید و از وقوع برخی ANR ها جلوگیری کنید.
برای اطلاعات بیشتر درباره ANR، به پاسخگویی برنامه خود نگاه کنید. برای اطلاعات بیشتر درباره رشتهها، به عملکرد Threading مراجعه کنید.
{% کلمه به کلمه %}برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- بیدار شدن بیش از حد