כש-thread של ממשק המשתמש של אפליקציה ל-Android חסום למשך זמן רב מדי, מופיעה לא מתקבלת תגובה" (ANR) מופעלת. אם האפליקציה פועלת בחזית, מציגה למשתמש תיבת דו-שיח, כפי שמוצג באיור 1. בתיבת הדו-שיח של ה-ANR מוצגת למשתמש יש הזדמנות לצאת ידנית מהאפליקציה.
איור 1. תיבת דו-שיח בנושא ANR תוצג למשתמש
מקרי ANR מהווים בעיה כי ה-thread הראשי של האפליקציה, שאחראי ל מעדכנים את ממשק המשתמש, לא יכולים לעבד אירועי קלט של משתמשים או לצייר, ולגרום לתסכול למשתמש. למידע נוסף על ה-thread הראשי של האפליקציה, אפשר לעיין במאמר תהליכים ו שרשורים.
שגיאת ANR מופעלת באפליקציה כשמתקיים אחד מהתנאים הבאים:
- תם הזמן הקצוב לתפוגה לשליחת קלט: אם האפליקציה לא הגיבה לקלט אירוע (כמו לחיצה על מקש או מגע במסך) תוך 5 שניות.
- ביצוע השירות: אם שירות שהוצהר על ידי האפליקציה לא יכול לסיים
ביצוע של
Service.onCreate()
וService.onStartCommand()
לService.onBind()
תוך כמה שניות. - לא מתבצעת קריאה ל-Service.startForeground() : אם האפליקציה משתמשת
Context.startForegroundService()
להפעלת שירות חדש בחזית, אבל השירות לא יתקשר ל-startForeground()
תוך 5 שניות. - שידור של כוונה: אם
BroadcastReceiver
לא סיימו להפעיל אותו בפרק זמן מוגדר. אם יש באפליקציה פעילות בחזית. הזמן הקצוב לתפוגה הוא 5 שניות. - אינטראקציות של מתזמן המשימות: אם
JobService
לא מוחזר מ-JobService.onStartJob()
אוJobService.onStopJob()
בטווח של שניות, או אם משימה שבוצעה ביוזמת המשתמש מתחילה והאפליקציה לא מתקשרת אלJobService.setNotification()
תוך כמה זמן שניות אחרי הקריאה ל-JobService.onStartJob()
. לגבי טירגוט לאפליקציות ב-Android מגרסה 13 ומטה, מקרי ה-ANR הם במצב שקט ולא ידווחו לאפליקציה. באפליקציות שמטרגטות ל-Android 14 ואילך, מקרי ה-ANR כוללים במפורש דווחו לאפליקציה.
אם זוהו מקרי ANR באפליקציה, אפשר להיעזר בהנחיות שבמאמר הזה כדי לאבחן ולפתור את הבעיה.
זיהוי הבעיה
אם כבר פרסמת את האפליקציה, אפשר להשתמש ב- תפקוד האפליקציה כדי לראות מידע על מקרי ANR באפליקציה שלך. אפשר להשתמש בהגדרות אחרות כלים לזיהוי מקרי ANR בשטח, אבל שימו לב שכלים של צד שלישי לא יכולים לדווח על מקרי ANR גרסאות ישנות יותר של Android (Android 10 ומטה), בניגוד לתפקוד האפליקציה.
תפקוד האפליקציה
תפקוד האפליקציה יכול לעזור לך לעקוב אחר שיעור מקרי ה-ANR של האפליקציה ולשפר אותו. תפקוד האפליקציה מודד כמה שיעורים של מקרי ANR:
- שיעור ANR: אחוז המשתמשים הפעילים ביום (DAU) למקרי ANR מכל סוג שהוא.
- שיעור מקרי ה-ANR שהשפיעו על המשתמשים: אחוז המשתמשים הפעילים ביום (DAU)
שחוו לפחות מקרה ANR אחד שהשפיעו על המשתמשים. כרגע רק מקרי ANR של
סוגים
Input dispatching timed out
נחשבים להשפיעו על המשתמשים. - שיעור מקרי ה-ANR מרובים: אחוז המשתמשים הפעילים ביום (DAU) חוו לפחות שני מקרי ANR.
משתמש פעיל ביום הוא משתמש ייחודי שמשתמש באפליקציה שלכם ביום אחד במכשיר יחיד, אולי במהלך מספר סשנים. אם משתמש משתמש באפליקציה שלך ביותר ממכשיר אחד ביום, כל מכשיר יתרום למספר המשתמשים הפעילים באותו יום. אם כמה משתמשים משתמשים באותו מכשיר ביום, נספר כמשתמש פעיל אחד.
שיעור מקרי ה-ANR שהשפיעו על המשתמשים הוא תפקוד ליבה, כלומר הוא משפיע על יכולת הגילוי של האפליקציה ב-Google Play. זה חשוב כי במקרי ANR ספירות מתרחשות תמיד כאשר המשתמש יוצר אינטראקציה עם האפליקציה, וכתוצאה מכך שיבוש.
מערכת Play הגדירה שני ערכי סף של התנהגות לא תקינה במדד הזה:
- סף התנהגות לא תקינה: לפחות 0.47% מהמשתמשים הפעילים ביום ומקרי ANR שהשפיעו על המשתמשים בכל דגמי המכשירים.
- סף התנהגות לא תקינה לפי מכשיר: לפחות 8% מהמשתמשים היומיים מקרה ANR שהשפיעו על המשתמשים, בדגם מכשיר אחד.
אם האפליקציה חורגת מסף ההתנהגות הלא תקינה הכולל, סביר להניח שהיא פחות חשיפה בכל המכשירים. אם האפליקציה שלך חורגת מהמגבלה לכל מכשיר את סף ההתנהגות במכשירים מסוימים, סביר להניח שהדבר יפגע ביכולת הגילוי שלו מכשירים כאלה, ויכול להיות שתוצג אזהרה בדף האפליקציה בחנות.
התכונה 'תפקוד האפליקציה' יכולה לשלוח לך התראה דרך Play Console, כשבאפליקציה יש כמות גדולה של מקרי ANR.
כדי לקבל מידע על אופן האיסוף של נתוני תפקוד האפליקציה ב-Google Play, אפשר לעיין במאמר Play Console התיעוד.
אבחון מקרי ANR
יש כמה דפוסים נפוצים שכדאי לחפש בעת אבחון מקרי ANR:
- האפליקציה מבצעת פעולות איטיות שכוללות קלט/פלט (I/O) ב-thread הראשי.
- האפליקציה מבצעת חישוב ארוך ב-thread הראשי.
- ה-thread הראשי מבצע קריאה סינכרונית של קישור (Binder) לתהליך אחר, לוקח הרבה זמן עד שתהליך מסוים חוזר.
- ה-thread הראשי חסום בהמתנה לבלוק מסונכרן למשך זמן ארוך שמתרחשת בשרשור אחר.
- ה-thread הראשי נמצא בקיפאון עם שרשור אחר, או באמצעות קריאה ל-binder. ה-thread הראשי לא רק ממתין למשך זמן כדי לסיים, אבל הוא במצב של מבוי סתום. לקבלת מידע נוסף, אפשר לקרוא את Deadlock בוויקיפדיה.
תוכלו להיעזר בשיטות הבאות כדי לקבוע את הגורם למקרי ה-ANR.
נתונים סטטיסטיים
HealthStats
מספק מדדים לגבי
את התקינות של אפליקציה באמצעות תיעוד הזמן הכולל של המשתמש והמערכת, זמן המעבד (CPU),
הרשת, נתונים סטטיסטיים של הרדיו, זמן ההפעלה/כיבוי של המסך והשעון המעורר. מי יכול
עזרה במדידה של השימוש הכולל במעבד וצריכת הסוללה.
ניפוי באגים
Debug
עוזר לבדוק אפליקציות ל-Android
במהלך הפיתוח, כולל ספירות של מעקב והקצאת הקצאות כדי לזהות
ועיכוב באפליקציות. אפשר גם להשתמש ב-Debug
כדי לקבל זמן ריצה וזיכרון מקורי
מוניים ומדדי זיכרון שיכולים לעזור בזיהוי טביעת הרגל הפחמנית
של תהליך מסוים.
נתוני יציאה של אפליקציה
הדומיין ApplicationExitInfo
זמין
ב-Android 11 (API ברמה 30) ואילך, ומספק מידע על
הסיבה ליציאה מהאפליקציה. דוגמאות למקרי ANR, מחסור בזיכרון, קריסות של אפליקציות
שימוש מוגזם במעבד (CPU), הפרעות למשתמשים, הפרעות במערכת או זמן ריצה
שינויים בהרשאות.
מצב קפדני
השימוש ב-StrictMode
עוזר לך למצוא
פעולות קלט/פלט מקריות ב-thread הראשי בזמן פיתוח האפליקציה.
אפשר להשתמש ב-StrictMode
ברמת האפליקציה או הפעילות.
הפעלת תיבות דו-שיח ברקע בנושא ANR
ב-Android מוצגות תיבות דו-שיח בנושא מקרי ANR באפליקציות שעיבוד השידור נמשך יותר מדי זמן הודעה רק אם האפשרות הצגת כל מקרי ה-ANR מופעלת בקטע מפתח הפרמטר הזה. לכן, תיבות דו-שיח של מקרי ANR ברקע לא תמיד מוצגות כדי המשתמש, אבל ייתכן שיש בעיות בביצועים של האפליקציה.
Traceview
אפשר להשתמש ב-Traceview כדי לקבל תיעוד של האפליקציה הפעילה בזמן שעוברים תרחישים לדוגמה ולזהות את המקומות שבהם ה-thread הראשי עמוס. למידע לקבלת מידע נוסף על השימוש ב-Traceview, אפשר לעיין במאמר יצירת פרופילים באמצעות Traceview dmtracedump.
אחזור של קובץ מעקב
מערכת Android שומרת את פרטי המעקב כשהיא מזהה מקרה ANR. במערכות הפעלה ישנות יותר
בגרסאות השונות, יש קובץ /data/anr/traces.txt
אחד במכשיר.
בגרסאות חדשות יותר של מערכת ההפעלה יש מספר קבצים מסוג /data/anr/anr_*
.
אפשר לגשת למעקבי ANR ממכשיר או מאמולטור באמצעות
Android Debug Bridge (adb) ברמה הבסיסית (root):
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
אפשר לתעד דוח על באג ממכשיר פיזי באמצעות שתי הפעולות הבאות: לדווח על אפשרות למפתחים במכשיר, או על הפקודה adb report report ב- מכונה של פיתוח. מידע נוסף זמין במאמר תיעוד וקריאה של באג .
תיקון הבעיות
לאחר שתזהו את הבעיה, תוכלו להשתמש בטיפים שבקטע הזה כדי ולפתור בעיות נפוצות.
הזנת קוד איטי בשרשור הראשי
צריך לזהות את המקומות בקוד שבהם ה-thread הראשי של האפליקציה עמוס, מ-5 שניות. צריך לחפש את התרחישים החשודים לדוגמה באפליקציה ולנסות לשחזר את שגיאת ה-ANR.
לדוגמה, באיור 2 מוצג ציר זמן של Traceview שבו ה-thread הראשי עמוס. למשך יותר מ-5 שניות.
איור 2. ציר הזמן של Traceview שבו מוצג שרשור ראשי עמוס
איור 2 מראה שרוב הקוד הפוגעני מופיע
onClick(View)
handler, כפי שמוצג בדוגמת הקוד הבאה:
Kotlin
override fun onClick(v: View) { // This task runs on the main thread. BubbleSort.sort(data) }
Java
@Override public void onClick(View view) { // This task runs on the main thread. BubbleSort.sort(data); }
במקרה כזה, צריך להעביר לעובד אחר את העבודה שרצתה ב-thread הראשי של שרשור. Android Framework כולל מחלקות שיכולות לעזור בהעברת המשימה לשרשור של עובדים. הצגת עובד שרשורים למידע נוסף מידע.
IO ב-thread הראשי
ביצוע פעולות IO ב-thread הראשי היא סיבה נפוצה לפעולות איטיות ב-thread הראשי, שעלול לגרום לשגיאות ANR. מומלץ להעביר את כל הקלט (IO) לשרשורים של עובדים, כפי שמתואר בקטע הקודם.
דוגמאות לפעולות IO הן פעולות רשת ואחסון. לקבלת מידע נוסף מידע נוסף, ראו ביצוע רשת פעולות ושמירה .
תחרות על נעילה
בתרחישים מסוימים, העבודה שגורמת ל-ANR לא מבוצעת ישירות בשרשור הראשי של האפליקציה. אם שרשור של Worker שומר נעילה על משאב שעיקר נדרש ל-thread כדי להשלים את העבודה, אז יכול להיות שמתרחש שגיאת ANR.
לדוגמה, איור 3 מציג ציר זמן של Traceview שבו רוב העבודה בוצע בשרשור של עובדים.
איור 3. ציר זמן של Traceview שבו רואים את הביצוע של העבודה על עובד שרשור
אבל אם המשתמשים שלך עדיין חווים מקרי ANR, צריך לבדוק את הסטטוס של
ה-thread הראשי ב-Android Device Monitor. בדרך כלל, ה-thread הראשי נמצא
RUNNABLE
הוא מוכן לעדכון ממשק המשתמש והוא מגיב בדרך כלל.
אבל אם לא ניתן להמשיך את ההפעלה של ה-thread הראשי, הוא נמצא
מצב BLOCKED
ולא יכולים להגיב לאירועים. המצב מופיע ב-Android Device Monitor בתור
עוקבים או ממתינים, כפי שמוצג באיור 5.
איור 4. ה-thread הראשי בסטטוס 'מעקב'
במעקב הבא מוצג ה-thread הראשי של האפליקציה שנחסם בזמן ההמתנה resource:
...
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)
...
כדאי לבדוק את המעקב כדי לאתר את הקוד שחוסם את ה-thread הראשי. הקוד הבא אחראי להחזיק את המנעול שחוסם את שרשור במעקב הקודם:
Kotlin
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]) } }
Java
@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]); } } }
דוגמה נוספת היא ה-thread הראשי של אפליקציה שממתין לתוצאה
בפרוטוקול worker, כפי שמוצג בקוד הבא. הערה: שימוש ב-wait()
וב-
לא מומלץ להשתמש בדפוס notify()
ב-Kotlin, ויש לו מנגנונים משלו
לטיפול בו-זמניות (concurrency). כשמשתמשים ב-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() } } }
Java
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(); } } }
יש מצבים אחרים שבהם אפשר לחסום את ה-thread הראשי, כולל
שרשורים שמשתמשים ב-Lock
,
Semaphore
,
וגם מאגר משאבים (כמו מאגר של חיבורי מסד נתונים)
או מנגנונים אחרים של הדרה (mutex) הדדיים.
באופן כללי, כדאי להעריך את הנעילות שהאפליקציה שלך מחזיקה במשאבים, אבל אם רוצים למנוע מקרי ANR, צריך לבדוק את המנעולים שמוחזקים למשאבים שנדרשת ל-thread הראשי.
ודאו שהנעילות מוחזקות למשך הזמן הקצר ביותר, או אפילו טוב יותר,
להעריך אם האפליקציה צריכה את ההשהיה מלכתחילה. אם משתמשים
כדי לקבוע מתי לעדכן את ממשק המשתמש בהתבסס על עיבוד של שרשור עובד,
משתמשים במנגנונים כמו
onProgressUpdate()
וגם
onPostExecute()
לצורך תקשורת בין העובד לבין השרשורים הראשיים.
מנעולים
מצב קיפאון מתרחש כששרשור נכנס למצב המתנה עקב דרישה משאב מוחזק על ידי שרשור אחר, שממתין גם הוא למשאב בשרשור הראשון. אם במצב הזה נמצא ה-thread הראשי של האפליקציה, סביר להניח שמקרי ANR עד שזה יקרה.
פערים הם תופעה חקרת היטב במדעי המחשב, ויש אלגוריתמים למניעת קיפאון שאפשר להשתמש בהם כדי למנוע פקקים.
אפשר לקרוא מידע נוסף בקטע נעילה וגם מניעת מבוי סתום אלגוריתמים מופעלים ויקיפדיה.
מקלטי שידורים איטיים
אפליקציות יכולות להגיב להודעות לכולם, למשל הפעלה או השבתה של מטוס או שינוי בסטטוס הקישוריות באמצעות מקלטי שידורים. מקרה ANR מתרחש כשלאפליקציה לוקח יותר מדי זמן לעבד את ההודעה המשודרת.
שגיאת ANR מתרחשת במקרים הבאים:
- מקלט השידורים עדיין לא סיים להפעיל
onReceive()
בשיטה הזו בתוך פרק זמן לא מבוטל. - מקלט שידורים מתקשר
goAsync()
ולא ניתן לקרואfinish()
בPendingResult
לאובייקט.
האפליקציה צריכה לבצע פעולות קצרות בלבד
onReceive()
שיטה
BroadcastReceiver
עם זאת, אם האפליקציה מחייבת
כתוצאה מהודעת שידור, עליך לדחות את המשימה
IntentService
אפשר להשתמש בכלים כמו Traceview כדי לזהות אם מקלט השידור פועל פעולות ממושכות ב-thread הראשי של האפליקציה. לדוגמה, איור מספר 6 ציר הזמן של מקלט שידורים שמעבד הודעות בשרשור הראשי למשך כ-100 שניות.
איור 5. ציר הזמן של Traceview שמציג את העבודה של BroadcastReceiver
ה-thread הראשי
ההתנהגות הזו עשויה לנבוע מביצוע פעולות ממושכות
onReceive()
ה-method של
BroadcastReceiver
,
כפי שאפשר לראות בדוגמה הבאה:
Kotlin
override fun onReceive(context: Context, intent: Intent) { // This is a long-running operation BubbleSort.sort(data) }
Java
@Override public void onReceive(Context context, Intent intent) { // This is a long-running operation BubbleSort.sort(data); }
במצבים כאלה, מומלץ להעביר את הפעולה הממושכת
IntentService
כי נעשה בו שימוש
שרשור עובד לביצוע העבודה. הקוד הבא מראה איך להשתמש
IntentService
כדי לעבד
פעולה ממושכת:
Kotlin
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) } }
Java
@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
,
מתבצעת פעולה בשרשור של עובד במקום בשרשור הראשי. איור 7
מציגה את העבודה נדחתה ל-thread של העובד בציר הזמן של Traceview.
איור 6. ציר הזמן של Traceview שמציג את הודעת השידור שעובדה שרשור עובד
מקלט השידור שלך יכול להשתמש
goAsync()
כדי לאותת למערכת שנדרש זמן נוסף לעיבוד ההודעה. אבל, לפעמים
כדאי להתקשר
finish()
ב
PendingResult
לאובייקט. הדוגמה הבאה מראה איך לקרוא ל-Finish() כדי לאפשר למערכת
מיחזור של מקלט השידור ומניעת ANR:
Kotlin
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)
Java
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
צמצמו מקרי ANR ב-
מקרים לדוגמה של משחקים ואפליקציות שנכתבו
ב-C או ב-C++. אם מחליפים את הפעילות המותאמת הקיימת בפעילות GameActivity
,
אפשר לצמצם את החסימה של השרשורים בממשק המשתמש ולמנוע מקרי ANR מסוימים.
מידע נוסף על מקרי ANR זמין במאמר הקפדה על כך שהאפליקציה תהיה רספונסיבית. לקבלת מידע נוסף על שרשורים, ביצועי השרשורים.
מומלץ עבורך
- הערה: טקסט הקישור מוצג כאשר JavaScript מושבת
- כמות מוגזמת של אירועי שינה