ניפוי באגים באפליקציה ל-Android על סמך תגי ANR במרכז הבקרה של Crashlytics

שגיאות מסוג 'האפליקציה לא מגיבה (ANR)' מתרחשות כששרשור ממשק המשתמש של האפליקציה לא מגיב במשך יותר מ-5 שניות. מידע נוסף על מקרי ANR ועל אבחון שלהם זמין במסמכי התיעוד של Android.

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

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

Triggered ANR

תג Triggered ANR מתווסף לשרשור שנחסם במשך זמן רב מדי וגרם ל-ANR.

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

Deadlocked

כל השרשורים שנמצאו מעורבים בנעילה מרומזת שהובילה ל-ANR מסומנים בתג Deadlocked.

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

אלה שני השרשוריים שמעורבים בנעילה מרובת משתמשים:

main (unknown): tid=1 systid=1568
    com.android.server.pm.PackageManagerService$PackageManagerInternalImpl.getPackage(PackageManagerService.java:22701)
    com.android.server.pm.PackageManagerService$PackageManagerInternalImpl.filterOnlySystemPackages(PackageManagerService.java:22787)

    ...

    com.android.server.SystemServer.main(SystemServer.java:368)
     java.lang.reflect.Method.invoke(Native method)
    com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:517)
    com.android.internal.os.ZygoteInit.main(ZygoteInit.java:934)


ActivityManager (unknown): tid=21 systid=1902
    com.android.server.pm.PackageManagerService.getPackageSetting(PackageManagerService.java:23618)
    com.android.server.pm.PackageManagerService.getPackageUid(PackageManagerService.java:4542)

    ...

    android.os.Handler.handleCallback(Handler.java:907)
    android.os.Handler.dispatchMessage(Handler.java:99)
    android.os.Looper.loop(Looper.java:216)
    android.os.HandlerThread.run(HandlerThread.java:67)
    com.android.server.ServiceThread.run(ServiceThread.java:44)
  

המלצה

בודקים את השרשורים שמעורבים בנעילה ודואגים לבדוק את המשאבים או המנעולים שהשרשורים האלה רכשו. תוכלו לקרוא על פתרונות אפשריים במאמרים נעילה מרובת משתמשים (deadlock) ואלגוריתמים למניעת נעילה מרובת משתמשים (deadlock).

IO Root blocking

כל thread שהפעיל פעולות קלט/פלט איטיות וחסם את ה-thread‏ Triggered ANR יסומן בתג IO Root blocking. אם השרשור Triggered ANR לא נחסם על ידי שרשורים אחרים, השרשור IO Root blocking הוא גם שרשור Root blocking.

Thread main(THREAD_STATE_TIMED_WAITING)
   sun.misc.Unsafe.park( Unsafe.java:0 )
   java.util.concurrent.locks.LockSupport.parkNanos( LockSupport.java:230 )
   android.database.sqlite.SQLiteConnectionPool.waitForConnection( SQLiteConnectionPool.java:756 )

   ...

   android.app.ActivityThread.main( ActivityThread.java:8192 )
  
Thread main(THREAD_STATE_NATIVE_WAITING)
   Syscall
   art::ConditionVariable::WaitHoldingLocks(art::Thread*)
   art::GoToRunnable(art::Thread*)
   art::JniMethodEnd(unsigned int, art::Thread*)
   libcore.io.Linux.fdatasync( Linux.java:0 )
   libcore.io.ForwardingOs.fdatasync( ForwardingOs.java:105 )

...

   java.io.RandomAccessFile.write( RandomAccessFile.java:559 )

...

   android.app.ActivityThread.main( ActivityThread.java:8192 )
  

המלצה

באופן כללי, לא כדאי לבצע באפליקציה פעולות קלט/פלט יקרות ב-thread הראשי. אם ה-thread הראשי הוא IO Root blocking, אפשר גם להשתמש במצב קפדני כדי לזהות פעולות קלט/פלט לא מכוונות שמתרחשות ב-thread הראשי.

Root blocking

כל שרשור שחסמם את השרשור המסומן בתג Triggered ANR יסומן בתג Root blocking. אם שרשור מסומן גם בתג Root blocking וגם בתג Triggered ANR, סימן שאין שרשורים אחרים שחוסמים את אותו שרשור.

אם יש שרשורים של Triggered ANR שהמתינו (אולי באופן טרנזיטיבי) לשרשורים אחרים, הם Root blocking. יכולות להיות סיבות שונות לכך ששרשור הוא הגורם העיקרי ל-ANR.

הנה כמה דוגמאות על סמך מצב השרשור:

Thread main(THREAD_STATE_RUNNABLE)
   android.os.Parcel.createTypedArray( Parcel.java:3086 )
   android.content.pm.PackageInfo.<init>( PackageInfo.java:546 )

...

   android.app.ActivityThread$H.handleMessage( ActivityThread.java:2166 )
   android.os.Handler.dispatchMessage( Handler.java:106 )
   android.os.Looper.loop( Looper.java:246 )
   android.app.ActivityThread.main( ActivityThread.java:8633 )
  
Thread main(THREAD_STATE_BLOCKED)
   DBHelper.runOnDB( DBHelper.java:97 )
   DBHelper.runDb( DBHelper.java:125 )

...

   java.lang.reflect.Method.invoke( Method.java:0 )
   EventBus.invokeSubscriber( EventBus.java:510 )
   postToSubscription( EventBus.java:437 )

...

   android.os.Handler.handleCallback( Handler.java:938 )
   android.os.Handler.dispatchMessage( Handler.java:99 )
   android.os.Looper.loop( Looper.java:268 )
   android.app.ActivityThread.main( ActivityThread.java:7904 )
  

המלצה

צמצום העבודה המאומצת של המעבד בשרשור הראשי. כדאי להשתמש בשרתי עבודה או בשרתי רקע לביצוע משימות שמתבצעות בעזרת מעבדים.

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

Unknown root cause

שרשור מסומן בתג Unknown root cause אם הוא השרשור שהפעיל את שגיאת ה-ANR, אבל לא היה פעיל בתהליך כשהיא התרחשה. למערכת Crashlytics אין מספיק מידע כדי לקבוע את שורש הבעיה. אין סיבה ברורה לכך שה-ANR הזה התרחש.

Thread main(THREAD_STATE_NATIVE_WAITING)    __epoll_pwait
    android::Looper::pollInner(int)
    android::Looper::pollOnce(int, int*, int*, void**)
    android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)
    android.os.MessageQueue.nativePollOnce( MessageQueue.java:0 )
    android.os.MessageQueue.next( MessageQueue.java:335 )
    android.os.Looper.loop( Looper.java:193 )
    android.app.ActivityThread.main( ActivityThread.java:8019 )
  

המלצה

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