כששרשור ה-UI של אפליקציית Android נחסם למשך זמן ארוך מדי, מופעלת שגיאת ANR (האפליקציה לא מגיבה). אם האפליקציה פועלת בחזית, המערכת מציגה למשתמש תיבת דו-שיח, כמו שמוצג באיור 1. בתיבת הדו-שיח של ה-ANR, המשתמש יכול לבצע יציאה ידנית מהאפליקציה.
שגיאות ANR הן בעיה כי השרשור הראשי של האפליקציה, שאחראי לעדכון ממשק המשתמש, לא יכול לעבד אירועי קלט של משתמשים או לצייר, וזה גורם לתסכול בקרב המשתמשים. מידע נוסף על השרשור הראשי של האפליקציה זמין במאמר סקירה כללית על תהליכים ושרשורים.
הודעת ANR מופעלת באפליקציה שלכם כשמתקיים אחד מהתנאים הבאים:
- פסק זמן בשליחת קלט: אם האפליקציה לא הגיבה לאירוע קלט (כמו לחיצה על מקש או נגיעה במסך) תוך 5 שניות.
- הפעלת שירות: אם שירות שהוגדר באפליקציה לא יכול לסיים את ההפעלה
Service.onCreateו-Service.onStartCommand/Service.onBindתוך כמה שניות. Service.startForegroundלא נקרא: אם האפליקציה משתמשת ב-Context.startForegroundServiceכדי להפעיל שירות חדש בחזית, אבל השירות לא קורא ל-startForegroundתוך 5 שניות.- שידור של כוונה: אם
BroadcastReceiverלא סיים את ההפעלה תוך פרק זמן מוגדר. אם לאפליקציה יש פעילות בחזית, הזמן הקצוב לתפוגה הוא 5 שניות. JobSchedulerאינטראקציות: אםJobServiceלא חוזר מ-JobService.onStartJobאו מ-JobService.onStopJobתוך כמה שניות, או אם מתחילה פעולה שהמשתמש יזם והאפליקציה לא קוראת ל-JobService.setNotificationתוך כמה שניות אחרי הקריאה ל-JobService.onStartJob. באפליקציות שמטרגטות ל-Android 13 ומטה, מקרי ה-ANR הם שקופים ולא מדווחים לאפליקציה. באפליקציות שמטרגטות ל-Android 14 ומעלה, מקרי ה-ANR הם גלויים ומדווחים לאפליקציה.
אם האפליקציה נתקלת בבעיות של ANR, אפשר להיעזר בהנחיות שבמסמך הזה כדי לאבחן את הבעיה ולפתור אותה.
זיהוי הבעיה
אם כבר פרסמתם את האפליקציה, אתם יכולים להשתמש בתפקוד האפליקציה כדי לראות מידע על מקרי ANR באפליקציה. אתם יכולים להשתמש בכלים אחרים כדי לזהות מקרי ANR בשטח, אבל חשוב לזכור שכלים של צד שלישי לא יכולים לדווח על מקרי ANR ב-Android 10 ובגרסאות קודמות, בניגוד לתפקוד האפליקציה.
תפקוד האפליקציה
הנתונים של מדד תפקוד האפליקציה יכולים לעזור לכם לעקוב אחרי שיעור מקרי ה-ANR של האפליקציה ולשפר אותו. ב-Android Vitals נמדדים כמה שיעורי ANR:
- שיעור מקרי ה-ANR: אחוז המשתמשים הפעילים ביום (DAU) שחוו מקרה ANR מכל סוג.
- שיעור מקרי ה-ANR שהשפיעו על המשתמשים: אחוז המשתמשים הפעילים ביום שחוו לפחות מקרה ANR אחד שהשפיע על המשתמשים. נכון לעכשיו, רק מקרי ANR מסוג
Input dispatching timed outנחשבים כמקרי ANR שהשפיעו על המשתמשים. - שיעור מקרי ANR מרובים: אחוז המשתמשים הפעילים ביום (DAU) שחוו לפחות שני מקרי ANR.
משתמש פעיל ביום הוא משתמש ייחודי שמשתמש באפליקציה שלכם ביום מסוים במכשיר יחיד, ויכול להיות שהוא יתחבר לאפליקציה כמה פעמים במהלך היום. אם משתמש מפעיל את האפליקציה ביותר ממכשיר אחד ביום מסוים, כל מכשיר יתרום למספר המשתמשים הפעילים באותו יום.
שיעור מקרי ה-ANR שהשפיעו על המשתמשים הוא נתון בסיסי של תפקוד האפליקציה, כלומר הוא משפיע על יכולת הגילוי של האפליקציה ב-Google Play. המדד הזה חשוב כי שגיאות ה-ANR שהוא סופר מתרחשות תמיד כשהמשתמש מקיים אינטראקציה עם האפליקציה, וגורמות להכי הרבה שיבושים.
ב-Play הוגדרו שני ספים להתנהגות לא תקינה למדד הזה:
- סף ההתנהגות הלא תקינה הכולל: לפחות 0.47% מהמשתמשים הפעילים ביום (DAU) חווים מקרה ANR שהשפיע על המשתמשים, בכל דגמי המכשירים.
- סף ההתנהגות הלא תקינה לכל מכשיר: לפחות 8% מהמשתמשים היומיים חווים מקרה ANR שהשפיע על המשתמש, בדגם מכשיר אחד.
אם האפליקציה חורגת מסף ההתנהגות הלא תקינה הכולל, סביר להניח שזה יפגע ביכולת הגילוי שלה בכל המכשירים. אם האפליקציה חורגת מערך הסף של התנהגות לא תקינה במכשיר מסוים, סביר להניח שזה יפגע ביכולת הגילוי שלה במכשיר הזה, ויכול להיות שתוצג אזהרה בדף האפליקציה בחנות.
הנתונים של תפקוד האפליקציה יכולים לעזור לכם לזהות מקרים של "האפליקציה לא מגיבה" (ANR) שמתרחשים בתדירות גבוהה מדי. לשם כך, תקבלו התראות דרך Play Console.
מידע על האופן שבו Google Play אוסף נתונים של תפקוד האפליקציה ב-Android זמין במסמכי התיעוד של Play Console.
אבחון של מקרי ANR
יש כמה דפוסים נפוצים שכדאי לחפש כשמאבחנים שגיאות ANR:
- האפליקציה מבצעת פעולות איטיות שכוללות קלט/פלט ב-thread הראשי.
- האפליקציה מבצעת חישוב ארוך בשרשור הראשי.
- ה-thread הראשי מבצע הפעלה סינכרונית של Binder לתהליך אחר, והתהליך האחר הזה לוקח הרבה זמן לחזור.
- ה-thread הראשי חסום. הוא נמצא בהמתנה לחסימה מסונכרנת של פעולה ארוכה שמתבצעת ב-thread אחר.
- ה-thread הראשי נמצא במצב של deadlock עם thread אחר, בתהליך או באמצעות קריאה ל-binder. ה-thread הראשי לא רק נמצא בהמתנה לסיום של פעולה ארוכה, אלא נמצא במצב של קיפאון.
הטכניקות הבאות יכולות לעזור לכם לקבוע את הגורם ל-ANR.
HealthStats
HealthStats מספק מדדים לגבי תקינות האפליקציה על ידי תיעוד של סך הזמן של המשתמש והמערכת, זמן המעבד, נתוני הרשת והרדיו, זמן המסך (פועל/כבוי) והתראות על התעוררות. כך תוכלו למדוד את השימוש הכולל במעבד ואת קצב ניקוז הסוללה.
ניפוי באגים
Debug עוזר לבדוק אפליקציות ל-Android במהלך הפיתוח, כולל מעקב ומספרי הקצאות, כדי לזהות באגים ופיגור באפליקציות.
אפשר גם להשתמש ב-Debug כדי לקבל מוני זיכרון של זמן ריצה וזיכרון מקורי, ומדדי זיכרון שיכולים לעזור לכם לזהות את הזיכרון שבשימוש של תהליך מסוים.
ApplicationExitInfo
המאפיין ApplicationExitInfo זמין ב-Android 11 (רמת API 30) ומעלה, והוא מספק מידע על הסיבה ליציאה מהאפליקציה. הנתונים האלה כוללים מקרי ANR, זיכרון נמוך, קריסות של אפליקציות, שימוש מוגזם במעבד, הפרעות למשתמשים, הפרעות למערכת ושינויים בהרשאות בתחילת ההפעלה.
מצב קפדני
השימוש ב-StrictMode עוזר לכם למצוא פעולות קלט/פלט מקריות בשרשור הראשי בזמן שאתם מפתחים את האפליקציה. אתם יכולים להשתמש ב-StrictMode ברמת האפליקציה או הפעילות.
הפעלת תיבות דו-שיח של ANR ברקע
ב-Android מוצגים תיבות דו-שיח של ANR לאפליקציות שלוקח להן יותר מדי זמן לעבד את הודעת השידור רק אם האפשרות הצגת כל פריטי ה-ANR מופעלת באפשרויות למפתחים במכשיר. לכן, תיבות דו-שיח של ANR ברקע לא תמיד מוצגות למשתמש, גם כשיש בעיות בביצועים של האפליקציה.
צווארי בקבוק ברה-קומפוזיציה
אפשר להשתמש בכלי לניתוח ביצועים ב-Android Studio ובכלי Layout Inspector כדי לאתר צווארי בקבוק של רה-קומפוזיציה. מידע נוסף זמין במאמר בנושא ביצועים של Jetpack Compose.
שליפת קובץ מעקב
במערכת Android, כשמתרחש מקרה ANR, נשמר מידע על המעקב. בגרסאות ישנות יותר של מערכת ההפעלה, יש קובץ /data/anr/traces.txt אחד במכשיר. בגרסאות חדשות יותר של מערכת ההפעלה, יש כמה קבצים מסוג /data/anr/anr_*. אפשר לגשת לנתוני מעקב של ANR ממכשיר או מאמולטור באמצעות ממשק הגישור של Android (ADB) כמשתמש root:
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
אפשר ליצור דוח על באג ממכשיר פיזי באמצעות האפשרות למילוי דוח על באג למפתחים במכשיר, או באמצעות הפקודה adb bugreport במחשב הפיתוח. מידע נוסף זמין במאמר בנושא איך שולחים דוחות על באגים וקוראים אותם.
פתרון הבעיות
אחרי שתזהו את הבעיה, תוכלו להיעזר בטיפים שבקטע הזה כדי לפתור בעיות נפוצות.
קוד איטי ב-thread הראשי
מזהים את המקומות בקוד שבהם השרשור הראשי של האפליקציה עסוק יותר מ-5 שניות. מחפשים באפליקציה תרחישי שימוש חשודים ומנסים לשחזר את ה-ANR.
בעיה נפוצה היא משימה שפועלת לאורך זמן ישירות ברכיב שאפשר להרכיב:
@Composable
fun BadList(rawStrings: List<String>) {
// Math or sorting inside the composable runs on EVERY recomposition pass!
val heavilyProcessedList = rawStrings
.filter { it.isNotBlank() }
.map { it.uppercase().reversed() }
.map { it.computationallyHeavyFunction() }
.sortedBy { it.length }
LazyColumn { items(sortedList) { Text(it) } }
}
// Modern Compose-first fix
@Composable
fun GoodList(viewModel: MyViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// UI simply renders state; no heavy processing allowed here
LazyColumn { items(uiState.sortedData) { Text(it) } }
}
קלט/פלט (I/O) ב-thread הראשי
ביצוע פעולות קלט/פלט (I/O) ב-thread הראשי הוא גורם נפוץ לפעולות איטיות ב-thread הראשי, שיכולות לגרום לשגיאות ANR. ב-Compose, מפתחים מפעילים בטעות קריאות דיסק (כמו SharedPreferences או קריאות מסד נתונים) בזמן שהם מנסים לגזור את המצב הראשוני.
צריך להריץ פעולות קלט/פלט (I/O) ממושכות מחוץ לשכבת ממשק המשתמש. כדאי להשתמש ב-withContext(Dispatchers.IO) ב-ViewModel או, אפילו טוב יותר, להשתמש ב-Repository בשכבת הנתונים.
קיפאון (deadlock)
מצב של קיפאון מתרחש כש-thread נכנס למצב המתנה כי משאב נדרש מוחזק על ידי thread אחר, שגם הוא נמצא בהמתנה למשאב שמוחזק על ידי ה-thread הראשון. אם ה-thread הראשי של האפליקציה נמצא במצב הזה, סביר להניח שיוצגו שגיאות ANR.
קיפאון הוא תופעה שנחקרה היטב במדעי המחשב, ויש אלגוריתמים למניעת קיפאון שבהם אפשר להשתמש כדי להימנע מקיפאון.
מידע נוסף זמין במאמרים Deadlock ו-Deadlock prevention algorithms בוויקיפדיה.
כשמשתמשים ב-Kotlin וב-Compose, אפשר להחליף נעילות פרימיטיביות ב-Mutexes של קורוטינות לא חוסמות (Mutex.withLock) כדי למנוע חסימת שרשור על ידי השהיה של הקשר הביצועי במקום הקפאה של שרשור ממשק המשתמש. לדוגמה:
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
// Modern non-blocking concurrency state architecture
class SecureDataRepository {
private val mutex = Mutex()
suspend fun safeUIAccess() {
// If locked, the main thread suspends seamlessly, preventing an ANR
mutex.withLock {
performSafeOperation()
}
}
}
מקלט שידורים איטי
אפליקציות יכולות להגיב להודעות שידור, כמו הפעלה או השבתה של מצב טיסה או שינוי בסטטוס הקישוריות, באמצעות מקלטי שידור. שגיאת ANR מתרחשת כשלוקח לאפליקציה יותר מדי זמן לעבד את ההודעה לשידור.
מצב ANR מתרחש במקרים הבאים:
- מקלט שידורים לא סיים להפעיל את השיטה
onReceiveבתוך פרק זמן משמעותי. - מקלט שידור קורא ל-
goAsyncולא מצליח לקרוא ל-finishבאובייקטPendingResult.
האפליקציה צריכה לבצע רק פעולות קצרות בשיטה onReceive של BroadcastReceiver. עם זאת, אם האפליקציה שלכם דורשת עיבוד מורכב יותר כתוצאה מהודעת שידור, כדאי לדחות את המשימה ל-ViewModel (תוך ניצול היכולות של קורוטינות, היקפים ומשגרים של Kotlin) אם המשימה צפויה להימשך כמה שניות לכל היותר, לכל סוג של מחזיק מצב או ל-WorkManager אם המשימה צפויה להימשך יותר מכמה שניות.
GameActivity
במקרי לדוגמה של
משחקים ואפליקציות שנכתבו ב-C או ב-C++, השימוש בספרייה GameActivity הוביל לצמצום של שגיאות ANR. אם תחליפו את הפעילות
Native הקיימת בפעילות GameActivity, תוכלו לצמצם את החסימה של שרשור UI ולמנוע חלק
משגיאות ה-ANR.
מידע נוסף על ANR זמין במאמר שמירה על רספונסיביות האפליקציה. מידע נוסף על שרשורים זמין במאמר שיפור הביצועים באמצעות שרשורים.
מקורות מידע נוספים
צפיות בתוכן
מומלץ בשבילכם
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- יותר מדי אירועי wakeup