איך מוצאים את השרשור שלא מגיב

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

סוג שגיאת ANR השרשור לא מגיב
ניתוב קלט Thread ראשי
אין חלון ממוקד ה-thread הראשי. סוג ה-ANR הזה לא נגרם בדרך כלל על ידי משתמש חסום של שרשור.
מקלט שידורים (סינכרוני) השרשור פועל onReceive() זהו ה-thread הראשי, אלא אם ה-handler המותאם אישית ב-thread שאינו ראשי צוין באמצעות Context.registerReceiver
מקלט שידורים (אסינכרוני) צריך לבדוק בקוד איזה שרשור או מאגר שרשורים אחראים לבצע את העבודה לעיבוד השידור אחרי goAsync נקראת.
תם הזמן הקצוב לתפוגה של השירות Thread ראשי
התחלת שירות שפועל בחזית Thread ראשי
ספק התוכן לא מגיב אחת משתי האפשרויות:
  • צריך לקשר את השרשור אם שגיאת ה-ANR נגרמת על ידי ספק תוכן איטי שאילתה.
  • ה-thread הראשי אם שגיאת ה-ANR נגרמת מהפעלה ארוכה של האפליקציה.
אין תגובה ל- onStartJob או onStopJob Thread ראשי

לפעמים השרשור לא מגיב בגלל בעיה בשרשור אחר או לעבד אותו. יכול להיות שלא תהיה תגובה מהשרשור הזה בגלל ההמתנה הבאה:

  • נעילה שמופעלת על ידי שרשור אחר.
  • קריאה איטית ב-Binder לתהליך שונה.

סיבות נפוצות לשרשורים שלא מגיבים

ריכזנו כאן סיבות נפוצות לשרשורים שלא מגיבים.

הפעלה איטית של Binder

למרות שרוב ההפעלות של קלסרים הן מהירות, "זנב ארוך" יכול להיות איטי מאוד. הדבר גדול יותר אם המכשיר נטען או ששרשור התשובה של קלסר איטיות, למשל עקב תחרות על נעילה, הרבה שיחות נכנסות מ-Binder או חומרה הזמן הקצוב לתפוגה של שכבת הפשטה (HAL).

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

צריך לחפש את BinderProxy.transactNative או את Binderproxy.transact במקבצים. המשמעות היא שמתקיימת שיחה ב-Binder. אחרי שתי השורות האלה, אפשר לראות את ה-API של binder שנקרא. בדוגמה הבאה, הקריאה היא IAccessibilityManager.addClient

main tid=123

...
android.os.BinderProxy.transactNative (Native method)
android.os.BinderProxy.transact (BinderProxy.java:568)
android.view.accessibility.IAccessibilityManager$Stub$Proxy.addClient (IAccessibilityManager.java:599)
...

הרבה קריאות רצופות ב-Binder

ביצוע קריאות רבות ב-Binder רצופות בלולאה צרות יכול לחסום שרשור עבור לתקופה ארוכה.

קלט/פלט (I/O) חוסם

אף פעם לא לחסום קלט/פלט (I/O) ב-thread הראשי. זהו אנטי-דפוס.

תחרות על נעילה

אם שרשור נחסם במהלך הגדרה של נעילה, זה עלול לגרום ל-ANR.

בדוגמה הבאה אפשר לראות שה-thread הראשי חסום כשמנסים להשיג lock:

main (tid=1) Blocked

Waiting for com.example.android.apps.foo.BarCache (0x07d657b7) held by
ptz-rcs-28-EDITOR_REMOTE_VIDEO_DOWNLOAD
[...]
at android.app.ActivityThread.handleStopActivity(ActivityThread.java:5412)
[...]

השרשור החסום שולח בקשת HTTP להורדת סרטון:

ptz-rcs-28-EDITOR_REMOTE_VIDEO_DOWNLOAD (tid=110) Waiting

at jdk.internal.misc.Unsafe.park(Native method:0)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:715)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1047)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:230)
at com.example.android.apps.foo.HttpRequest.execute(HttpRequest:136)
at com.example.android.apps.foo$Task$VideoLoadTask.downloadVideoToFile(RequestExecutor:711)
[...]

מסגרת יקרה

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

  • רינדור עם הרבה פריטים מיותרים שלא מופיעים במסך.
  • שימוש באלגוריתם לא יעיל, כמו O(n^2), במהלך רינדור של הרבה ממשקי משתמש רכיבים.

נחסם על ידי רכיב אחר

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

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