כששרשור ה-UI של אפליקציית Android נחסם למשך זמן רב מדי, המערכת שולחת שגיאה מסוג ANR (האפליקציה לא מגיבה). בדף הזה מתוארים הסוגים השונים של שגיאות ANR, איך לאבחן אותן ואיך לפתור אותן. כל טווחי הזמן הקצוב לתפוגה שמופיעים כאן הם למכשירי AOSP ולמכשירי Pixel. טווחי הזמן האלה עשויים להשתנות בהתאם ליצרן הציוד המקורי (OEM).
חשוב לזכור שכדי לקבוע את הסיבה למקרי ANR, כדאי להבדיל בין בעיות במערכת לבין בעיות באפליקציה.
כשהמערכת במצב לא תקין, הבעיות הבאות עלולות לגרום למקרי ANR:
- בעיות זמניות בשרת המערכת גורמות בדרך כלל להאטה בקריאות מהירות של binder.
- בעיות בשרת המערכת ועומס גבוה על המכשיר גורמים לכך שלא מתבצע תזמון של השרשורים באפליקציה.
אם האפשרות הזו זמינה לכם, דרך טובה להבחין בין בעיות במערכת לבין בעיות באפליקציה היא להשתמש בעקבות של Perfetto:
- כדי לבדוק אם השרשור הראשי של האפליקציה מתוזמן, אפשר להסתכל על השרשור במעקב ב-Perfetto ולראות אם הוא פועל או ניתן להפעלה.
- כדאי לבדוק את השרשורים
system_serverכדי לאתר בעיות כמו תחרות על נעילה. - אם קריאות ל-binder איטיות, כדאי לבדוק את שרשור התשובות, אם הוא קיים, כדי להבין למה הן איטיות.
הזמן הקצוב לתזמון של קלט
שגיאות ANR של שליחת קלט מתרחשות כשהשרשור הראשי של האפליקציה לא מגיב בזמן לאירוע קלט, כמו החלקה או לחיצה על מקש. מכיוון שהאפליקציה נמצאת בחזית כשההמתנה להעברת קלט מסתיימת, כמעט תמיד המשתמש רואה את זה, ולכן חשוב מאוד לצמצם את התופעה.
תקופת ברירת המחדל לתפוגת זמן קצוב: 5 שניות.
בדרך כלל, שגיאות ANR של שליחת קלט נגרמות מבעיות ב-thread הראשי. אם ה-thread הראשי נחסם בהמתנה להוספת נעילה, יכול להיות שגם ה-thread המחזיק מעורב.
כדי להימנע מ-ANR של שליחת קלט, כדאי לפעול לפי השיטות המומלצות הבאות:
- אל תבצעו פעולות חסימה או פעולות ארוכות בשרשור הראשי. כדאי להשתמש ב-
StrictModeכדי לזהות פעילות לא מכוונת בשרשור הראשי. - צריך לצמצם את התחרות על נעילה בין ה-thread הראשי לבין threads אחרים.
- צריך לצמצם את העבודה שאינה קשורה לממשק המשתמש ב-thread הראשי, למשל כשמטפלים בשידורים או כשמפעילים שירותים.
גורמים נפוצים
ריכזנו כאן כמה סיבות נפוצות ל-ANR של שליחת קלט, ופתרונות אפשריים.
| הסיבה | מה קורה | הצעות לתיקון |
|---|---|---|
| הפעלה איטית של Binder | ה-thread הראשי מבצע הפעלה ארוכה של Binder באופן סינכרוני. | אם אתם הבעלים של ה-API, נסו להעביר את הקריאה מה-thread הראשי או לבצע אופטימיזציה של הקריאה. |
| הפעלות רבות של Binder ברצף | ה-thread הראשי מבצע הרבה הפעלות רצופות של Binder באופן סינכרוני. | אל תבצעו קריאות ל-binder בלולאה צפופה. |
| חסימת קלט/פלט | ה-thread הראשי מבצע קריאת קלט/פלט חוסמת, כמו גישה למסד נתונים או לרשת. | צריך להעביר את כל פעולות הקלט/פלט החוסמות מה-thread הראשי. |
| תחרות על נעילה | ה-thread הראשי חסום וממתין להוספת נעילה. | צמצום התחרות על נעילה בין ה-thread הראשי לבין threads אחרים. אופטימיזציה של קוד איטי בשרשור השני. |
| פריימים יקרים | רינדור יתר במסגרת אחת, מה שגורם לבעיות חמורות בממשק (jank). | מבצעים פחות עבודה בעיבוד המסגרת. לא להשתמש באלגוריתמים מסוג n2. כדאי להשתמש ברכיבים יעילים לפעולות כמו גלילה או החלפת דפים – למשל, ספריית החלפת הדפים של Jetpack. |
| הבקשה נחסמה על ידי רכיב אחר | רכיב אחר, כמו מקלט שידורים, פועל וחוסם את ה-thread הראשי. | כדאי להעביר את העבודה שלא קשורה לממשק המשתמש מה-thread הראשי ככל האפשר. הפעלת מקלטי שידורים בשרשור אחר. |
| קריסת GPU | קריסת GPU היא בעיה במערכת או בחומרה שגורמת לחסימת העיבוד, ולכן ל-ANR של שליחת קלט. | לצערנו, בדרך כלל אין תיקונים בצד האפליקציה. אם אפשר, פנו לצוות החומרה כדי לפתור את הבעיה. |
איך מנפים באגים
כדי להתחיל בניפוי באגים, בודקים את חתימת האשכול של שגיאת ה-ANR במסוף Google Play או ב-Firebase Crashlytics. האשכול בדרך כלל מכיל את המסגרות העליונות שיש חשד שהן גורמות ל-ANR.
בתרשים הזרימה הבא מוצג תהליך לקביעת הסיבה ל-ANR של שליחת פסק זמן לקלט.
התכונה 'תפקוד האפליקציה' יכולה לזהות חלק מהסיבות הנפוצות האלה ל-ANR ולעזור בניפוי הבאגים. לדוגמה, אם נתוני התפקוד מזהים שגיאת ANR שנגרמה בגלל מחלוקת על נעילה, הם יכולים לסכם את הבעיה ולהמליץ על תיקון בקטע תובנות לגבי שגיאות ANR.
אין חלון מודגש
אירועים כמו מגע נשלחים ישירות לחלון הרלוונטי על סמך בדיקת פגיעה, אבל אירועים כמו מקשים צריכים יעד. היעד הזה נקרא החלון הממוקד. בכל מסך יש רק חלון אחד ממוקד, ובדרך כלל זה החלון שהמשתמש מקיים איתו אינטראקציה כרגע. אם לא נמצא חלון ממוקד, הקלט מעלה ANR של no-focused-window. שגיאת 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()איטי. - שרשורי worker של שידור, אם הבעיה היא קוד שידור איטי של
goAsync().
כדי להימנע מ-ANR של מקלט שידורים, מומלץ לפעול לפי השיטות המומלצות הבאות:
- חשוב לוודא שהאפליקציה נפתחת במהירות, כי הזמן הזה נספר כחלק מהזמן הקצוב לתפוגה של שגיאת ANR אם האפליקציה מופעלת כדי לטפל בשידור.
- אם משתמשים ב-
goAsync(), חשוב לוודא שהפונקציהPendingResult.finish()נקראת במהירות. ההגדרה הזו כפופה לאותו זמן קצוב לתפוגה של ANR כמו במקרה של מקלטי שידורים סינכרוניים. - אם משתמשים ב-
goAsync(), צריך לוודא שה-worker thread לא משותף עם פעולות אחרות ארוכות טווח או חוסמות. - כדאי להשתמש ב-
registerReceiver()כדי להריץ מקלטי שידורים ב-thread שאינו ה-thread הראשי, וכך להימנע מחסימה של קוד ממשק המשתמש שפועל ב-thread הראשי.
תקופות של זמן קצוב לתפוגה
תקופות הזמן הקצובות לתפוגה של שידורים תלויות בשאלה אם דגל הכוונה של החזית מוגדר, ובגרסת הפלטפורמה.
| סוג הכוונה | Android מגרסה 13 ומטה | Android מגרסה 14 ואילך |
|---|---|---|
Intent עם עדיפות של תהליך בחזית ( |
10 שניות |
10-20 שניות, בהתאם לשאלה אם התהליך הוא CPU-starved |
Intent של עדיפות ברקע ( |
60 שניות |
60-120 שניות, בהתאם לשאלה אם התהליך הוא CPU-starved |
כדי לדעת אם הדגל FLAG_RECEIVER_FOREGROUND מוגדר, מחפשים את המחרוזת flg= בנושא של ה-ANR ובודקים אם מופיע 0x10000000. אם הביט הזה מוגדר, אז הערך של FLAG_RECEIVER_FOREGROUND ב-Intent מוגדר, ולכן פסק הזמן קצר יותר.
דוגמה לנושא 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. לכן, הפעלה איטית של האפליקציה יכולה לגרום לשגיאות ANR של מקלט שידורים.
באיור הבא מוצג ציר הזמן של ANR של מקלט שידורים, שמתאים לתהליכים מסוימים באפליקציה.
מדידת הזמן הקצוב לתפוגה של ANR מסתיימת כשהמקלט מסיים לעבד את השידור: הזמן המדויק שבו זה קורה תלוי בסוג המקלט – סינכרוני או אסינכרוני.
- לנמענים סינכרוניים, המדידה נפסקת כש-
onReceive()מוחזר. - לנמענים אסינכרוניים, המדידה נפסקת כשמתבצעת קריאה ל-
PendingResult.finish().
גורמים נפוצים
ריכזנו כאן כמה סיבות נפוצות ל-ANR ב-BroadcastReceiver והצעות לפתרונות.
| הסיבה | חל על | מה קרה? | הצעה לתיקון |
|---|---|---|---|
| הפעלה איטית של האפליקציה | כל המקבלים | האפליקציה נמשכה יותר מדי זמן כדי לבצע הפעלה במצב התחלתי (cold start). | אופטימיזציה של הפעלה איטית של האפליקציה. |
onReceive() לא מתוזמן | כל המקבלים | השרשור של מקלט השידור היה עסוק בעבודה אחרת ולא הצליח להפעיל את השיטה onReceive(). | לא לבצע משימות ארוכות בשרשור של המקלט (או להעביר את המקלט לשרשור ייעודי). |
onReceive() איטי | כל המקבלים, אבל בעיקר מקבלים סינכרוניים | השיטה onReceive() התחילה אבל נחסמה או שהייתה איטית ולכן לא הושלמה בזמן. | אופטימיזציה של קוד המקלט האיטי. |
| משימות אסינכרוניות של מקלט לא מתוזמנות | goAsync()
מקבלים | השיטה onReceive() ניסתה לבצע עבודה במאגר של שרשורי עובדים חסומים, ולכן העבודה לא התחילה. |
כדאי לבצע אופטימיזציה לשיחות איטיות או לשיחות שחוסמות, או להשתמש בשרשורים שונים לעובדי שידור לעומת משימות אחרות שנמשכות זמן רב. |
| העובדים איטיים או חסומים | goAsync() מקלטים |
הייתה פעולה חוסמת או איטית איפשהו במאגר של שרשורי העובדים במהלך עיבוד השידור. לכן, לא התקשרנו אל PendingResult.finish
בזמן. | אופטימיזציה של קוד async של מקלט איטי. |
שכחת להתקשר אל PendingResult.finish |
goAsync() מקלטים |
הקריאה ל-finish() חסרה בנתיב הקוד. |
חשוב לוודא שהפונקציה finish() תמיד נקראת. |
איך מנפים באגים
על סמך חתימת האשכול ודוח ה-ANR, אפשר לאתר את השרשור שבו הרסיבר פועל, ואז את הקוד הספציפי שחסר או פועל לאט.
בתרשים הזרימה הבא מוצג תהליך לקביעת הסיבה ל-ANR של מקלט שידור.
איתור קוד המקלט
ב-Google Play Console מוצגים מחלקת המקלט וה-intent של השידור בחתימת ה-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 הזה. אחרת, זה השרשור הראשי.
דוגמה: משימות של מקלט אסינכרוני שלא מתוזמנות
בקטע הזה מוסבר איך לנפות באגים ב-ANR של מקלט שידור.
נניח שחתימת ה-ANR נראית כך:
com.example.app.MyClass.myMethod
Broadcast of Intent {
act=android.accounts.LOG_ACCOUNTS_CHANGED cmp=com.example.app/com.example.app.MyReceiver }
על סמך החתימה, נראה שה-broadcast intent הוא
android.accounts.LOG_ACCOUNTS_CHANGED והמחלקה של המקלט היא
com.example.app.MyReceiver.
מתוך קוד המקלט, אפשר לראות שמאגר ה-threads BG Thread
[0,1,2,3] מבצע את העבודה העיקרית לעיבוד השידור הזה. כשבודקים את קובצי ה-stack dump, אפשר לראות שלכל ארבעת השרשורים ברקע (BG) יש את אותו הדפוס: הם מריצים קריאה חוסמת, getDataSync. מכיוון שכל השרשורים של BG היו עסוקים, לא היה אפשר לעבד את השידור בזמן, מה שהוביל ל-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
getDataSyncis slow and optimize. - Don't run
getDataSyncon 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
goAsyncworker 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(), andonBind()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.
If you've determined that the execute service ANR is actionable, follow these steps to help resolve the issue:
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.MyServiceDetermine 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.handleBindApplicationApp 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 theMyServiceclass 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 הראשי חסום ברכיב הזה, ולכן השירות לא יכול להתחיל.
אם אתם רואים קריאה לפונקציית מפתח ויכולים לקבוע איפה מתרחש ה-ANR באופן כללי, כדאי לבדוק את שאר הערימות של ה-thread הראשי כדי למצוא את הפעולה האיטית, ולבצע אופטימיזציה שלה או להעביר אותה מחוץ לנתיב הקריטי.
- חשוב לוודא שהאפליקציה נפתחת במהירות, כי הזמן הזה נכלל בטיימאוט של ה-ANR אם האפליקציה נפתחת כדי להריץ את ספק התוכן.
- חשוב לוודא שהשאילתות של ספק התוכן מהירות.
- לא כדאי לבצע הרבה קריאות בו-זמניות של binder לחסימה, כי הן עלולות לחסום את כל השרשורים של binder באפליקציה.
- קפיאות שקטות של ממשק המשתמש: ממשק המשתמש של האפליקציה מפסיק להגיב לקלט של המשתמש.
- קריסות: יכול להיות שהאפליקציה תקרוס עם
illegalStateException. - שגיאות ANR: יכול להיות שהמערכת תפעיל זמן קצוב לתפוגה של ANR.
- תזמון מעברים:
ViewRootImplמתזמן מעבר – פריסה ומעבר ציור – על ידי פרסום מחסום סנכרון ב-MessageQueueשל שרשור ממשק המשתמש. ההגבלה הזו גורמת להשהיית העיבוד הרגיל של ההודעות, כדי שפריסת ממשק המשתמש תקבל עדיפות. - מצב מירוץ: כשכמה שרשורים מנסים לבטל תוקף של תצוגה בו-זמנית, הם מתחרים על תזמון המעבר הזה. יכול להיות ששני השרשורים יצליחו להוסיף מחסום סנכרון, אבל ה-framework ישמור את האסימון רק לאחד מהם.
- הקפאת ממשק המשתמש: כשהמעבר מתבצע, רק המחסום השמור היחיד מוסר. המחסומים המשניים, שנוצרו בגלל הדליפה, נשארים בתור ללא הגבלת זמן, וחוסמים את שרשור UI באופן קבוע, כך שהוא לא יכול לעבד הודעות סינכרוניות.
- אפשר לבצע אינטראקציה רק עם אובייקטים של
View– כולל קריאת מאפיינים כמוwidthאוheightאו הגדרת מאפיינים כמוTextView's text– בשרשור שבו יצרתם את היררכיית התצוגה. כמעט תמיד זה יהיה ה-thread הראשי או ה-thread של ממשק המשתמש. - אם אתם לא יכולים להבטיח שאתם פועלים בשרשור UI כשאתם ניגשים לתצוגות, אל תניחו שזה בטוח לעשות זאת. לדוגמה, אם אתם משתמשים ב-
Coroutinesלעבודה ברקע, אתם צריכים לעבור למפיץ הראשי לפני שאתם משנים אובייקטים שלView. ContextCompat#getMainExecutor(android.content.Context)View.post(Runnable)Activity.runOnUiThread(Runnable)- מתקינים גרסה של האפליקציה שאפשר לבצע בה ניפוי באגים במכשיר עם Android מגרסה 17 ואילך.
- פותחים את אפליקציית ההגדרות במכשיר ועוברים אל מערכת > מתקדם > אפשרויות למפתחים > שינויים בתאימות האפליקציה.
- בוחרים את האפליקציה מהרשימה.
- ברשימת השינויים, מאתרים את המתג
ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APISומעבירים אותו למצב מופעל. - טלמטריה ומעקב: מאזין זה מאפשר לאפליקציה לקבל קריאות חוזרות (callback) כשמפעילים View API משרשור שגוי, וכך קל יותר לרשום טלמטריה ולעקוב אחרי באגים.
- הרצה מוטבעת: הפונקציה listener נקראת מוטבעת, כך שאפשר לתעד ולבדוק את דוח הקריסות המדויק ברגע שמתרחשת ההפרה.
- בעיה כללית במערכת. התהליך לא תוכנן בגלל עומס כבד על המערכת או בעיה בשרת המערכת.
- Late stack dump. השרשור שוחזר במהלך התקופה הקצרה שבין הפעלת ה-ANR לבין יצירת dump של המחסניות. ההשהיה בטלפונים מדגם Pixel עם Android מגרסה 13 היא בערך 100 אלפיות השנייה, אבל יכולה להיות יותר משנייה אחת. במכשירי Pixel עם Android 14, זמן האחזור הוא בדרך כלל פחות מ-10 אלפיות השנייה.
- שיוך שגוי של הודעות לשרשור. ה-thread ששימש ליצירת חתימת ה-ANR לא היה ה-thread בפועל שלא הגיב וגרם ל-ANR.
- לפרטים על הפרת כללי השרשור אם האפליקציה משנה תצוגה בשרשור ברקע, היא יכולה להפעיל מרוץ תהליכים בתוך התצוגה, שחוסם את ההרצה של משימות בשרשור UI
- עומס רב על המערכת: בודקים את העומס הכולל על משאבי המכשיר, כמו מחסור במעבד (CPU), בזיכרון או בקלט/פלט (I/O) בכל המערכת, כגורם העיקרי לחוסר התגובה.
- שיוך שגוי של שרשור: בודקים את השרשורים של העובד והקשר כדי לזהות מצבי קיפאון, מחלוקות על נעילה שמשפיעות על השרשור הראשי או שרשורים ברקע שנתקעים בזמן עיבוד רכיבים אסינכרוניים (למשל,
goAsync()). - View Threading Violations: Scan background threads for unlawful UI view hierarchy modifications, which can orphan a sync barrier in the MessageQueue and permanently block all synchronous messages.
- העברת הערימה נמשכת יותר מדי זמן ופג הזמן הקצוב לתפוגה.
- התהליך הופסק או הושבת לפני שהמערכת אספה את נתוני ה-stack.
מידע נוסף על שירותים זמין בדפים הבאים:
ספק התוכן לא מגיב
שגיאת ANR של ספק תוכן מתרחשת כשספק תוכן מרוחק לא מגיב לשאילתה לפני שפג הזמן הקצוב לתפוגה, והמערכת מפסיקה את הפעולה שלו.
תקופת ברירת המחדל להמתנה עד שהבקשה תיפסק: מוגדרת על ידי ספק התוכן באמצעות התג ContentProviderClient.setDetectNotResponding. תקופת הזמן הקצובה לתפוגה של ANR כוללת את הזמן הכולל שנדרש להרצת שאילתה של ספק תוכן מרוחק, כולל הפעלה קרה של האפליקציה המרוחקת אם היא לא פעלה כבר.
כדי להימנע ממקרי ANR של ספקי תוכן, כדאי לפעול לפי השיטות המומלצות הבאות:
גורמים נפוצים
בטבלה הבאה מפורטות הסיבות הנפוצות ל-ANR של ספקי תוכן והתיקונים המוצעים.
| הסיבה | מה קורה | עוצמת אות | הצעה לתיקון |
|---|---|---|---|
| שאילתה איטית של ספק תוכן | ספק התוכן לוקח יותר מדי זמן להפעלה או שהוא חסום. | המסגרת android.content.ContentProvider$Transport.query נמצאת בשרשור של ה-binder. |
אופטימיזציה של שאילתות של ספקי תוכן. לגלות מה חוסם את השרשור של ה-binder. |
| הפעלה איטית של האפליקציה | האפליקציה של ספק התוכן נפתחת לאט מדי. | המסגרת ActivityThread.handleBindApplication נמצאת ב-thread הראשי. |
אופטימיזציה של הפעלת האפליקציה. |
| מיצוי של שרשור Binder – כל שרשורי ה-Binder עסוקים | כל השרשורים של ה-binder עסוקים בטיפול בבקשות סינכרוניות אחרות, ולכן לא ניתן להפעיל את הקריאה ל-binder של ספק התוכן. | האפליקציה לא מופעלת, כל השרשורים של ה-binder עסוקים וספק התוכן לא פועל. | הפחתת העומס על השרשורים של ה-binder. כלומר, להוציא פחות שיחות סינכרוניות של binder או לבצע פחות עבודה כשמטפלים בשיחות נכנסות. |
איך מנפים באגים
כדי לנפות באגים ב-ANR של ספק תוכן באמצעות חתימת האשכול ודוח ה-ANR ב-Google Play Console או ב-Firebase Crashlytics, צריך לבדוק מה קורה בשרשור הראשי ובשרשורי ה-Binder.
בתרשים הזרימה הבא מתואר תהליך ניפוי הבאגים של ANR בספק תוכן:
בקטע הקוד הבא אפשר לראות איך נראה השרשור של ה-Binder כשהוא נחסם בגלל שאילתה איטית של ספק תוכן. במקרה הזה, השאילתה של ספק התוכן ממתינה לנעילה כשפותחים מסד נתונים.
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)
בקטע הקוד הבא אפשר לראות איך נראה השרשור הראשי כשהוא חסום בגלל הפעלה איטית של האפליקציה. במקרה כזה, הפעלת האפליקציה איטית בגלל מחלוקת על נעילה במהלך אתחול Dagger.
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(), חשוב להתקשר אליהם בהקדם האפשרי.
לא לבצע הרבה פעולות לפני הצגת ההתראה.
צפייה בהפרה של כללי השרשור
אם האפליקציה משנה תצוגה בשרשור ברקע, היא עלולה להפעיל מצב מירוץ במצב הפנימי של היררכיית התצוגה. הפרה כזו עלולה לחסום את השרשור הראשי (UI) מביצוע הודעות סינכרוניות, ולגרום לבעיות יציבות קריטיות:
הפרה של ה-threading היא אחת משורשי הבעיה של עקבות מחסנית (stack traces) של ANR שבהם ה-thread הראשי לא פעיל (למשל, נלכד ב-nativePollOnce או ב-main thread idle).
דליפה של מחסום סנכרון
אפשר לבטל את התוקף של תצוגות באופן עקיף על ידי שינוי המצב שלהן (לדוגמה, קריאה ל-TextView.setText או ל-View.setVisibility) או באופן ישיר על ידי קריאה ל-View.invalidate או ל-View.requestLayout. כשמבטלים את התוקף של תצוגה,
ViewRootImpl מתזמן מעבר כדי לבצע מדידה, פריסה וציור
כדי לעדכן את מצב ממשק המשתמש.
כתוצאה מכך, ממשק המשתמש קופא. מכיוון ש-MessageQueue לא יכול לעבד הודעות סינכרוניות אחרי המחסומים שנפרצו, השרשור הראשי עובר למצב המתנה. הבעיה הזו בדרך כלל מופיעה כ-ANR עם nativePollOnce ב-stack trace.
כדי להימנע מהפרות של שרשור תצוגות, כדאי לפעול לפי השיטות המומלצות הבאות:
lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { val data = myRepository.getData() withContext(Dispatchers.Main) { // Switch context to Main myTextView.text = data.title } }
כדי לעזור לכם לפעול לפי שיטות מומלצות, מערכת Android מציעה כמה דרכים לגשת לשרשור ממשק המשתמש משרשורים אחרים:
בדרך כלל, ספריות פופולריות לטעינת תמונות, מסגרות של תוספים ריאקטיביים, ספריות של אוטובוס אירועים ופתרונות אחרים לשרשור מציעים דרכים לצפייה באירועים מסוימים בשרשור הראשי. המידע הזה שימושי לצופים ולפונקציות event listener שצריכות לקבל ולהגדיר את מצב התצוגות.
איך מנפים באגים
ב-Android 17 הוספנו כלים חדשים שיעזרו לכם לזהות, לעקוב ולפתור הפרות של שרשור תצוגות במהלך הפיתוח.
1. כלים של מסגרת התאימות
כלים של מסגרת התאימות מאפשרים למפתחי אפליקציות להפעיל ולהשבית שינויים בהתנהגות באופן פרטני באמצעות אפשרויות למפתחים או ADB. כדי להחליף את התצוגה של השינויים בהתנהגות של הפרות שרשור, פועלים לפי השלבים הבאים:
אפשר גם להפעיל או להשבית את הדגל באמצעות ADB:
$ adb shell am compat enable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
$ adb shell am compat disable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
הפעלת הדגל הזה גורמת לאפליקציה להפעיל חריגה ולקרוס בכל פעם שמתבצעת קריאה ל-View API מהשרשור הלא נכון. כך אפשר לזהות ולתקן בעיות שקשורות להפרות של שרשור תצוגה.
2. CalledFromWrongThreadListener API
אפשר גם להטמיע את API הגלובלי View#registerCalledFromWrongThreadListener כדי לזהות באופן פרוגרמטי גישה לא תקינה לשרשור.
android {
//...
compileSdk = 37
}
val listener = object : View.CalledFromWrongThreadListener { override fun onCalledFromWrongThread() { // Handle the issue, e.g. crash if this is a dev build, or log an event // e.g. Log.d(TAG, "CalledFromWrongThread: ${Exception().stackTraceToString()}") // Unregister the listener to avoid redundant notifications for the same issue View.unregisterCalledFromWrongThreadListener(this) } } View.registerCalledFromWrongThreadListener(listener)
nativePollOnce
אם אתם רואים את המסגרת nativePollOnce או message queue idle במצבורי הנתונים של ה-ANR, זה לרוב מצביע על כך שהשרשור שחשוד כלא מגיב היה למעשה במצב המתנה להודעות של לולאת ההודעות. ב-Google Play Console, פרטי ה-ANR נראים כך:
Native method - android.os.MessageQueue.nativePollOnce
Executing service com.example.app/com.example.app.MyService
לדוגמה, אם השרשור הראשי לא פעיל, המחסניות נראות כך:
"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)
יכולות להיות כמה סיבות לכך שהשרשור החשוד לא מגיב:
הטריגרים הבסיסיים של ANR מסוג nativePollOnce משתנים בהתאם לסוג ה-ANR, ולכן אבחון של קטגוריית ה-ANR הספציפית יכול לעזור לזהות פעולות שאפשר לבצע כדי לפתור את הבעיה באפליקציה.
| קטגוריה | הסיבה | הצעה לתיקון |
|---|---|---|
| הזמן הקצוב לתזמון של קלט | בעיה בכל המערכת dump ערימה מאוחר |
לא נדרשת כל פעולה. |
| אין חלון מודגש | בעיה כללית במערכת Late stack dump View Threading Violation |
בודקים את בסיס הקוד כדי לראות אם יש הפרות של שרשור תצוגות. |
| הזמן הקצוב לתפוגה של מקלט השידורים הסתיים | Late stack dump System-wide issue Thread misattribution View Threading Violation |
בודקים את השרשורים הרלוונטיים ב-stack dump. בודקים את בסיס הקוד כדי לראות אם יש הפרות של שרשור תצוגות. |
| זמן קצוב לתפוגה של ביצוע שירות | Late stack dump System-wide issue View Threading Violation |
בודקים את בסיס הקוד כדי לראות אם יש הפרות של שרשור תצוגות. |
| ספק התוכן לא מגיב | Late stack dump System-wide issue Thread misattribution |
בודקים את השרשורים הרלוונטיים ב-stack dump. |
אלה השלבים המומלצים לניתוח של ANR מסוג nativePollOnce.
אין מסגרות מחסנית
חלק מדוחות ה-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.