שגיאות מסוג 'האפליקציה לא מגיבה (ANR)' מופעלות כששרשור ה-UI של האפליקציה לא מגיב במשך יותר מ-5 שניות. מידע נוסף על מקרי ANR ועל אבחון שלהם זמין במאמרי העזרה של Android.
בנוסף, Crashlytics יכול לעזור לזהות שרשורים ספציפיים שבהם יש בעיות. אנחנו מנתחים שגיאות ANR, ואז בלוח הבקרה DevOps & Engagement > Crashlytics, אנחנו מתייגים את ה-Threads הרלוונטיים כדי לספק רמזים לגבי ניפוי הבאגים של שגיאות ה-ANR.
בקטעים הבאים בדף הזה מוסבר מה המשמעות של כל תג ANR, מוצגות דוגמאות ל-ANR עם התג הזה ומוצע פתרון מומלץ לניפוי באגים של ה-ANR.
Triggered ANR
Thread שנחסם למשך זמן ארוך מדי והפעיל את ה-ANR מסומן בתג Triggered ANR.
השרשור הבעייתי יכול להיות השרשור הראשי של האפליקציה, או כל שרשור שלא מגיב. עם זאת, יכול להיות שהשרשור שתויג כ-Triggered ANR הוא הסיבה האמיתית ל-ANR, ויכול להיות שלא. כדי לספק תובנות לניפוי באגים ולתיקון של שגיאות ANR, Crashlytics מתייג גם את כל השרשורים האחרים שקשורים לשגיאת ה-ANR. בקטעים הבאים בדף הזה מוסבר על תגים אחרים שאפשר להוסיף לשרשור.
Deadlocked
כל השרשורים שנמצאו כמעורבים בקיפאון (deadlock) שהוביל ל-ANR מסומנים בתג Deadlocked.
מבוי סתום מתרחש כש-thread נכנס למצב המתנה כי משאב נדרש מוחזק על ידי thread אחר, שגם הוא נמצא בהמתנה למשאב שמוחזק על ידי ה-thread הראשון. אם ה-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)
המלצה
בודקים את השרשורים שמעורבים במבוי סתום ואת המשאבים או הנעילות שהשרשורים האלה קיבלו. כדי לראות פתרונות אפשריים, כדאי לעיין במאמרים בנושא קיפאון ואלגוריתמים למניעת קיפאון.
IO Root blocking
כל thread שביצע פעולות קלט/פלט (I/O) איטיות וחסימה של ה-thread Triggered ANR מסומן בתג IO Root blocking. אם ה-thread Triggered ANR לא נחסם על ידי threads אחרים, אז ה-thread IO Root blocking הוא גם thread 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, סימן שלא קיימים שרשורים אחרים שחוסמים את השרשור הזה.
אם יש threads של Triggered ANR בהמתנה (אולי באופן טרנזיטיבי) ל-threads אחרים, הם 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 הראשי. משתמשים ב-worker או בשרשורים ברקע כדי לבצע משימות שדורשות הרבה משאבי CPU.
כדאי לצמצם את העבודה שדורשת הרבה קלט/פלט, כמו טעינה ממסד נתונים, ב-thread הראשי.
Unknown root cause
שרשור מתויג בתג Unknown root cause אם הוא השרשור שהפעיל את ה-ANR אבל היה במצב המתנה בתהליך כשהתרחש ה-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 שניות.
Waiting for binder call
כל שרשור שהמתין לקריאה של קובץ מאגד וחסימה של השרשור Triggered ANR מסומן בתג Waiting for binder call. אם ה-thread Triggered ANR לא נחסם על ידי threads אחרים, אז ה-thread Waiting for binder call הוא גם thread Root blocking.
לצפייה בדוגמה
Thread main(THREAD_STATE_NATIVE_WAITING)
syscall
android::IPCThreadState::talkWithDriver(bool)
android::IPCThreadState::waitForResponse(android::Parcel*, int*)
android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)
android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)
android_os_BinderProxy_transact(_JNIEnv*, _jobject*, int, _jobject*, _jobject*, int)
android.os.BinderProxy.transactNative( BinderProxy.java:0 )
android.os.BinderProxy.transact( BinderProxy.java:540 )
android.app.IActivityManager$Stub$Proxy.getContentProvider( IActivityManager.java:5945 )
android.app.ActivityThread.acquireProvider( ActivityThread.java:7138 )
android.app.ActivityThread$H.handleMessage( ActivityThread.java:1982 )
android.os.Handler.dispatchMessage( Handler.java:106 )
android.os.Looper.loop( Looper.java:236 )
android.app.ActivityThread.main( ActivityThread.java:8037 )
המלצה
ההשהיה של הפעלת Binder יכולה להיות בלתי צפויה, ולכן צריך להתייחס להפעלות של Binder כמו לפעולות קלט/פלט (I/O), ולהימנע ככל האפשר מהפעלות של Binder ב-thread הראשי. צריך להעביר ל-thread ברקע את הקוד שגורם להפעלות של Binder (כולל קריאות ל-Platform API שגורמות לעסקאות של Binder), או לשמור את התוצאות במטמון אם אי אפשר להימנע מקריאה מה-thread הראשי.