אבחון ותיקון של מקרי ANR

כאשר ה-thread של ממשק המשתמש של אפליקציה ל-Android חסום למשך זמן רב מדי, המערכת שולחת "האפליקציה לא מגיבה" (ANR). בדף הזה מתוארים סוגים של מקרי ANR, איך לאבחן אותם והצעות לפתרון. כל טווחי הזמן שמוגדרים כברירת מחדל לזמן הקצוב לתפוגה הם למכשירי AOSP ו-Pixel. בזמנים האלה עשויים להשתנות בהתאם ל-OEM.

חשוב לזכור שכשקובעים את הגורם למקרי ה-ANR, כדאי להבחין בין בעיות במערכת לבין בעיות באפליקציה.

כאשר המערכת במצב לא טוב, הבעיות הבאות עלולות לגרום לשגיאות ANR:

  • בעיות זמניות בשרת המערכת גורמות בדרך כלל לקריאות מהירות של binder לפעול לאט.
  • בעיות בשרת המערכת ובעומס גבוה על המכשיר גורמות לכך ששרשורי האפליקציות לא פועלים מתוזמן.

דרך טובה להבחין בין בעיות במערכת לבין בעיות באפליקציה היא אם היא זמינה, כדי להשתמש במעקבי Perfetto:

  • כדי לבדוק אם ה-thread הראשי של האפליקציה מתוזמן לשרשורים, צריך לבדוק את מצב ה-thread לעקוב ב-Perfetto כדי לבדוק אם הוא פועל או אפשר להריץ אותו.
  • מומלץ לעיין בשרשורים בsystem_server כדי למצוא בעיות כמו תחרות על נעילה.
  • במקרה של שיחות איטיות ב-Binder, כדאי לבדוק את שרשור התשובה (אם יש כזה) כדי להבין למה לאט.

הזמן הקצוב לתפוגה של שליחת קלט

מקרי ANR בשליחה של קלט מתרחשים כשה-thread הראשי של האפליקציה לא מגיב לקלט אירוע, כמו החלקה או לחיצה על מקש, בזמן. כי האפליקציה פועלת בחזית. כשמסתיים הזמן הקצוב לתפוגה של שליחת קלט, הם כמעט תמיד גלויים למשתמש וחשוב מאוד לצמצם את הסיכון.

ברירת מחדל לזמן קצוב לתפוגה: 5 שניות.

שגיאות ANR בקלט נגרמות בדרך כלל מבעיות ב-thread הראשי. אם העמודה ה-thread נחסם בזמן ההמתנה לנעילה, אפשר גם להעביר את ה-thread של שמירה מעורבות.

כדי להימנע ממקרי ANR בקלט, כדאי לפעול לפי השיטות המומלצות הבאות:

  • אין לבצע פעולות חסימה או ממושכות על ה-thread הראשי. נקודות שכדאי להעלות שימוש ב-StrictMode כדי לזהות בפעילות ב-thread הראשי.
  • צמצום התחרות על נעילה בין ה-thread הראשי לשרשורים אחרים.
  • צמצום העבודה שאינה דרך ממשק המשתמש ב-thread הראשי, למשל בטיפול בשידורים או שירותים פעילים.

סיבות נפוצות

ריכזנו כאן כמה סיבות נפוצות והצעות לתיקונים לשגיאות ANR בשליחה של קלט.

הסיבה מה קורה הצעות לתיקונים
הפעלה איטית של binder ה-thread הראשי מבצע קריאה ארוכה ב-Binder סינכרוני. כדאי להוציא את השיחה מהשרשור הראשי או לנסות לייעל את השיחה הם הבעלים של ה-API.
הרבה קריאות רצופות ב-Binder ה-thread הראשי מבצע הרבה קריאות סינכרוניות ב-Binder. אין לבצע הפעלות ל-Binder בתוך לולאה הדוקה.
חסימת קלט/פלט ה-thread הראשי גורם לחסימה של קריאה לקלט/פלט (I/O), כמו מסד נתונים או רשת גישה. צריך להעביר את כל ה-IO שחוסם מה-thread הראשי.
נעילת תחרות ה-thread הראשי חסום בהמתנה לקבלת נעילה. הפחתת תחרות על נעילה בין ה-thread הראשי לשרשור אחר. ביצוע אופטימיזציה לקוד האיטי בשרשור השני.
מסגרת יקרה מתבצע עיבוד יותר מדי במסגרת אחת, וגורמת לשיבושים חמורים. פחות עבודת סידור המסגרת. לא להשתמש ב-n2 אלגוריתמים. כדאי להשתמש רכיבים יעילים לדברים כמו גלילה או חלוקה לדפים - לדוגמה, תרמיל ריחוף ספריית הדפים.
הדף נחסם על ידי רכיב אחר רכיב אחר, כמו מקלט שידורים, פועל חסימת ה-thread הראשי. חשוב להעביר את העבודה שלא בממשק המשתמש מחוץ ל-thread הראשי עד כמה שאפשר. הפעלת שידור נמענים בשרשור אחר.
קריסת GPU קריסת GPU היא בעיה במערכת או בחומרה שגורמת ייחסם וכתוצאה מכך תתרחש שגיאת ANR בשיגור הקלט. לצערנו, בדרך כלל אין תיקונים בצד האפליקציה. אם המיקום יש לפנות לצוות החומרה כדי לפתור את הבעיה.

איך לנפות באגים

מתחילים בניפוי באגים על ידי בדיקת החתימה של אשכול מקרי ה-ANR ב-Google Play המסוף או Firebase Crashlytics. בדרך כלל האשכול מכיל את החלק העליון פריימים החשודים בגורמים ל-ANR.

תרשים הזרימה הבא מראה איך לקבוע את הסיבה לזמן הקצוב לתפוגה של קלט שליחה של שגיאת ANR.

איור 1. איך לנפות באגים ב-ANR של שליחת קלט.

תפקוד האפליקציה יכול לזהות חלק מהסיבות הנפוצות האלה למקרי ANR ולנפות באגים. עבור לדוגמה, אם מדד תפקוד האפליקציה מזהה שמקרה ANR התרחש בגלל תחרות על נעילה, יכולים לסכם את הבעיה ולקבל המלצות לתיקון בקטע תובנות של מקרי ה-ANR.

איור 2. זיהוי ANR של תפקוד האפליקציה.

אין חלון מודגש

אירועים כמו מגע נשלחים ישירות לחלון הרלוונטי על סמך היט בדיקה, אירועים כמו מפתחות צריכים יעד. יעד זה נקרא חלון מודגש. יש רק חלון מיקוד אחד לכל תצוגה, ובדרך כלל מדובר בחלון החלון שהמשתמש מקיים איתו אינטראקציה. אם בחלון מודגש לא ניתן למצוא אותו, הקלט מעלה שגיאת ANR ללא חלון ממוקד. שגיאת ANR ללא טירגוט לחלון הוא סוג של מקרי ANR בשיגור קלט.

ברירת המחדל לזמן הקצוב לתפוגה: 5 שניות.

סיבות נפוצות

מקרי ANR ללא חלונות ספציפיים נגרמים בדרך כלל מאחת מהבעיות הבאות:

  • האפליקציה עושה יותר מדי עבודה ואיטית מדי לשרטט את הפריים הראשון.
  • אי אפשר להתמקד בחלון הראשי. אם סומן חלון FLAG_NOT_FOCUSABLE, המשתמש לא יכול לשלוח אליו אירועים מרכזיים או לחצנים.

Kotlin

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

Java

@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 של מקלט שידורים מתרחשים לעיתים קרובות בשרשורים הבאים:

  • thread ראשי, אם הבעיה היא הפעלה איטית של האפליקציה.
  • מקלט השידורים של השרשור פועל, אם הבעיה היא קוד onReceive() איטי.
  • שרשורים של עובד שידור, אם הבעיה היא קוד שידור איטי של goAsync().

כדי להימנע ממקרי ANR של מקלט שידורים, כדאי לפעול לפי השיטות המומלצות הבאות:

  • חשוב לוודא שהפעלת האפליקציה מהירה, כי היא נספרת בזמן הקצוב לתפוגה של שגיאת ה-ANR אם האפליקציה מתחילה לטפל בשידור.
  • אם נעשה שימוש ב-goAsync(), חשוב לוודא שהשיחה PendingResult.finish() מהירה. הוא כפוף לאותו זמן קצוב לתפוגה של ANR כמו מקלטי שידור סינכרוניים.
  • אם משתמשים ב-goAsync(), צריך לוודא שהשרשורים של העובדים לא משותפים עם פעולות ממושכות אחרות או פעולות חסימה אחרות.
  • כדאי להשתמש ב-registerReceiver() כדי להפעיל מקלטי שידור כדי למנוע חסימת קוד של ממשק המשתמש שרץ ב-thread הראשי.

תקופות זמן קצוב לתפוגה

הזמן הקצוב לתפוגה של שידור לקבלת הודעות תלוי בשאלה אם דגל Intent בחזית מוגדרת, וגרסת הפלטפורמה.

סוג כוונת רכישה Android מגרסה 13 ומטה Android מגרסה 14 ואילך

הכוונה בעדיפות גבוהה בחזית

(FLAG_RECEIVER_FOREGROUND) הגדרה)

‫10 שניות

10-20 שניות, תלוי אם התהליך מורעב למעבד (CPU)

Intent של העדיפות ברקע

(FLAG_RECEIVER_FOREGROUND לא מוגדר)

60 שניות

60 עד 120 שניות, תלוי אם התהליך סובל מורעב במעבד (CPU)

כדי לדעת אם הדגל FLAG_RECEIVER_FOREGROUND מוגדר, מחפשים את הביטוי 'flg=" ב נושא ה-ANR ולבדוק את הנוכחות של 0x10000000. אם הביט הזה מוגדר, ל-Intent מוגדר FLAG_RECEIVER_FOREGROUND, ולכן הזמן הקצוב לתפוגה קצר יותר.

נושא של שגיאת ANR עם זמן קצר לאחר השידור (10-20 שניות):

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

נושא של שגיאת ANR עם זמן קצוב לתפוגה של שידור ארוך (60-120 שניות):

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

איך נמדדים זמני שידור

המדידה של משך השידור מתחילה כשהשידור נשלח system_server לאפליקציה, ומסתיימת כשהמערכת מסיימת לעבד את שידור. אם תהליך האפליקציה לא פעל כבר, הוא צריך גם לבצע קירור להתחיל במסגרת הזמן הקצוב לתפוגה של שגיאת ה-ANR. לכן, הפעלה איטית של האפליקציה עלולה להוביל ANRs של מקלט השידורים.

האיור הבא ממחיש שציר הזמן של שגיאת ה-ANR של מקלט השידור תואם תהליכים מסוימים של אפליקציות.

איור 3. ציר הזמן של מקרה ה-ANR של מקלט השידור.

המדידה של הזמן הקצוב לתפוגה של ANR מסתיימת כשהמקבל מסיים לעבד את שידור: מתי בדיוק זה מתרחש, תלוי אם הוא סינכרוני או למקלט אסינכרוני.

  • במקלטים סינכרוניים, המדידה נפסקת כשחוזרים onReceive().
  • עבור מקלטים אסינכרוניים, המדידה נפסקת מתבצעת שיחה אל PendingResult.finish().
איור 4. נקודות קצה סינכרוניות למדידת הזמן הקצוב לתפוגה של ANR ומקלטים אסינכרוניים.

סיבות נפוצות

הנה כמה סיבות נפוצות והצעות לתיקונים לשגיאות ANR של מקלט שידורים.

הסיבה חל על מה קרה? הצעה לתיקון
הפעלה איטית של האפליקציה כל המקבלים ההפעלה במצב התחלתי (cold start) של האפליקציה נמשכה יותר מדי זמן. אופטימיזציה של הפעלה איטית של אפליקציה.
לא בוצע תזמון של onReceive() כל המקבלים השרשור של מקלט השידור היה עמוס בביצוע עבודה אחרת ולא ניתן מפעילים את השיטה onReceive(). לא ביצועים משימות ממושכות בשרשור של המקבל (או להעביר את המקבל למרכז ייעודי ).
onReceive() איטי כל המקלטים, אבל בעיקר סינכרוניים ה-method onReceive() התחילה, אבל נחסם או איטי ולכן לא הושלם בזמן. אופטימיזציה איטית קוד המקבל.
המשימות של המקבל האסינכרוני לא תוזמנו goAsync() נמענים השיטה onReceive() ניסתה לבצע עבודה במאגר שרשורי עובדים חסום, כך שהעבודה לא התחילה. אופטימיזציה של שיחות איטיות או חסימה, או שימוש בשרשורים שונים לשידור עובדים לעומת משימות ממושכות אחרות.
פועלים איטיים או חסומים goAsync() מקלטים הייתה פעולה של חסימה או פעולה איטית במקום כלשהו ב-thread של העובד בזמן עיבוד השידור. אז, PendingResult.finish לא נשלחה בזמן. אופטימיזציה של מקלט async איטי
שכחת להתקשר אל PendingResult.finish goAsync() מקלטים הקריאה ל-finish() חסרה בנתיב הקוד. חשוב לוודא שתמיד מתבצעת קריאה אל finish().

איך לנפות באגים

על סמך חתימת האשכול ודוח ה-ANR, אפשר לאתר את השרשור המקבל פועל ולאחר מכן הקוד הספציפי שחסר או פועל לאט.

תרשים הזרימה הבא מראה איך לקבוע את הגורם לשידור שגיאת ANR במקלט.

איור 5. איך לנפות באגים ב-ANR של מקלט שידורים.

איתור הקוד של המקבל

ב-Google Play Console מוצגים סיווג הנמען וכוונת השידור ב-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 כדי לציין handler מותאם אישית, הוא ה-thread שמפעיל את ה-handler. אחרת, הוא ה-thread הראשי.

דוגמה: משימות של מקלט אסינכרוני לא מתוזמנות

בקטע הזה מתוארת דוגמה לניפוי באגים במקרי 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. מכיוון שכל השרשורים של מונחי החיפוש היו עמוסים, לא ניתן היה לעבד את השידור בזמן, דבר שהוביל ל-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 כחיוב שגוי.
    • רכיב אפליקציה אחר פועל, כמו מקלט שידורים. כאן אם ה-thread הראשי כנראה חסום ברכיב הזה, לאחר מכן.
  3. אם מופיעה בקשה להפעלת פונקציות מרכזיות וניתן לקבוע איפה שגיאת ה-ANR באופן כללי, כדאי לבדוק את שאר מקבצי ה-threads העיקריים כדי למצוא לבצע אופטימיזציה שלה או להזיז אותה מהנתיב הקריטי.

  4. כדי לקבל מידע נוסף על שירותים, אפשר לעיין בדפים הבאים:

    ספק התוכן לא מגיב

    שגיאת ANR של ספק תוכן מתרחשת כאשר לספק התוכן המרוחק נמשך יותר זמן פרק הזמן הקצוב לתפוגה כדי להגיב לשאילתה, והוא מושבת.

    תקופת ברירת מחדל לזמן קצוב לתפוגה: צוינה על ידי ספק התוכן באמצעות ContentProviderClient.setDetectNotResponding. פרק הזמן הקצוב לתפוגה של שגיאת ה-ANR כולל את משך הזמן הכולל להרצת שאילתה של ספק תוכן מרוחק, כולל הפעלה במצב התחלתי של האפליקציה המרוחקת, אם היא עדיין לא פעלה.

    כדי להימנע ממקרי ANR אצל ספקי התוכן, כדאי לפעול לפי השיטות המומלצות הבאות:

    • חשוב לוודא שהפעלת האפליקציה מהירה, כי היא נספרת בזמן הקצוב לתפוגה של שגיאת ה-ANR אם האפליקציה מופעלת להפעיל את ספק התוכן.
    • חשוב לוודא שהשאילתות של ספקי התוכן מהירות.
    • לא לבצע הרבה הפעלות בו-זמניות של Binder שחוסמות, שעלולות לחסום את כל של האפליקציה.

    סיבות נפוצות

    בטבלה הבאה מפורטות סיבות נפוצות למקרי ANR של ספק התוכן והצעות לתיקונים.

    הסיבה מה קורה עוצמת האות הצעה לתיקון
    שאילתה של ספק תוכן איטי ההפעלה של ספק התוכן נמשכת יותר מדי זמן או שהוא נחסם. המסגרת android.content.ContentProvider$Transport.query נמצא בשרשור של הקישורים. אופטימיזציה לשאילתה של ספק התוכן. צריך לבדוק מה חוסם את הקישור של שרשור.
    הפעלה איטית של האפליקציה הפעלת האפליקציה של ספק התוכן נמשכת זמן רב מדי. המסגרת ActivityThread.handleBindApplication נמצאת ב ה-thread הראשי. אופטימיזציה של הפעלת האפליקציה.
    מיצוי של ה-thread של Binder – כל ה-threads של קלסרים תפוסים כל ה-threads של קלסרים עסוקים במילוי בקשות סינכרוניות אחרות, הקריאה ל-Binder של ספק התוכן לא יכולה לפעול. האפליקציה לא מופעלת, כל השרשורים של קלסרים עסוקים והתוכן הספק לא פועל. הפחתת העומס על שרשורים של קלסרים. כלומר, יש פחות סנכרון יוצא שיחות binder או לעשות פחות עבודה כאשר מטפלים בשיחות נכנסות.

    איך לנפות באגים

    כדי לנפות באגים ב-ANR של ספק תוכן באמצעות חתימת האשכול ודוח ה-ANR ב- ב-Google Play Console או ב-Firebase Crashlytics, צריך לבדוק מה ה-thread הראשי עושים את זה.

    תרשים הזרימה הבא מתאר איך לנפות באגים ב-ANR של ספק תוכן:

    איור 7. איך לנפות באגים ב-ANR של ספק תוכן.

    קטע הקוד הבא מראה איך נראה ה-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)
    

    בקטע הקוד הבא אפשר לראות איך נראה ה-thread הראשי כשהוא נחסם עקב הפעלה איטית של האפליקציה. במקרה כזה, ההפעלה של האפליקציה איטית עקב תחרות על נעילה במהלך אתחול הפגיונות.

    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(). זה מרמז ה-thread הראשי של האפליקציה חסום לביצוע פעולה אחרת.

    אם מדובר בבעיה עם JobService.onStartJob() או עם JobService.onStopJob(), לבדוק מה קורה בשרשור הראשי. אם מדובר בבעיה עם JobService.setNotification(), חשוב להתקשר אליו בהקדם האפשרי. לא כדאי להשקיע המון מאמץ לפני ששולחים את ההודעה.

    מקרי ANR מסתוריים

    לפעמים לא ברור למה מתרחש ANR, או שאין מספיק כדי לנפות באגים בחתימת האשכול ובדוח ה-ANR. במקרים כאלה, יש עדיין כמה שלבים שאפשר לבצע כדי לקבוע אם שגיאת ה-ANR פרקטי.

    תור ההודעות לא פעיל או NativePollOnce

    אם המסגרת android.os.MessageQueue.nativePollOnce מופיעה זה מצביע על כך שהשרשור החשוד שלא מגיב ללא פעילות ומחכה להודעות אלה. ב-Google Play Console, פרטי שגיאת ה-ANR נראים כך:

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

    לדוגמה, אם ה-thread הראשי לא פעיל, המקבץ ייראה כך:

    "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)
    

    יש כמה סיבות אפשריות לכך שהשרשור שחשוד שלא מגיב עשוי להיות לא פעיל:

    • תמונת מצב של מאוחר יותר במקבץ. ה-thread שוחזר במהלך התקופה הקצרה שבין הפעלת ANR והקריסות של המקבצים. זמן האחזור ב-Pixel ב-Android 13 הוא באורך של כ-100 אלפיות השנייה, אבל יכול להימשך יותר משנייה אחת. זמן האחזור ב-Pixel 14 ב-Android הוא בדרך כלל פחות מ-10 אלפיות השנייה.
    • שיוך שגוי של שרשורים ה-thread ששימש ליצירה של חתימת ה-ANR לא ה-thread שלא מגיב בפועל שגרם ל-ANR. במקרה כזה, נסו כדי לקבוע אם שגיאת ה-ANR היא אחד מהסוגים הבאים:
    • בעיה ברמת המערכת. התהליך לא תוזמן בגלל עומס רב על המערכת או בעיה בשרת המערכת.

    אין סטאק פריימים

    חלק מדוחות ה-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.