כששרשור ה-UI של אפליקציה ל-Android נחסם למשך זמן רב מדי, מופעלת השגיאה "האפליקציה לא מגיבה" (ANR). אם האפליקציה פועלת בחזית, המערכת מציגה למשתמש תיבת דו-שיח, כמו שמוצג באיור 1. בתיבת הדו-שיח של ה-ANR, המשתמש יכול לבצע יציאה ידנית מהאפליקציה.
שגיאות ANR הן בעיה כי ה-thread הראשי של האפליקציה, שאחראי על עדכון ממשק המשתמש, לא יכול לעבד אירועי קלט של משתמשים או לצייר, וזה גורם לתסכול אצל המשתמשים. מידע נוסף על ה-thread הראשי של האפליקציה זמין במאמר סקירה כללית על תהליכים ו-threads.
שגיאת ANR מופעלת באפליקציה כשאחד מהתנאים הבאים מתקיים:
- הזמן הקצוב לתפוגה של שליחת קלט: אם האפליקציה לא הגיבה לאירוע קלט (כמו לחיצה על מקש או נגיעה במסך) תוך 5 שניות.
- הפעלת שירות: אם שירות שהאפליקציה מצהירה עליו לא יכול לסיים את ההפעלה של
Service.onCreateו-Service.onStartCommand/Service.onBindתוך כמה שניות. - לא בוצעה קריאה ל-
Service.startForeground: אם האפליקציה משתמשת ב-Context.startForegroundServiceכדי להפעיל שירות חדש שפועל בחזית, אבל השירות לא קורא ל-startForegroundתוך 5 שניות. - שידור של Intent: אם
BroadcastReceiverלא סיים לפעול בתוך פרק זמן מוגדר. אם לאפליקציה יש פעילות כלשהי בחזית, זמן הקצוב לתפוגה הוא 5 שניות. - אינטראקציות עם
JobScheduler: אםJobServiceלא חוזר מ-JobService.onStartJobאו מ-JobService.onStopJobתוך כמה שניות, או אם משימה שהמשתמש התחיל מתחילה והאפליקציה לא קוראת ל-JobService.setNotificationתוך כמה שניות אחרי הקריאה ל-JobService.onStartJob. באפליקציות שמטרגטות ל-Android 13 ומטה, שגיאות ה-ANR הן שקטות ולא מדווחות לאפליקציה. באפליקציות שמטרגטות ל-Android 14 ומעלה, שגיאות ה-ANR הן מפורשות ומדווחות לאפליקציה.
אם באפליקציה שלכם יש שגיאות ANR, אתם יכולים להיעזר בהנחיות שבמסמך הזה כדי לאבחן את הבעיה ולפתור אותה.
אבחון של מקרי ANR
יש כמה דפוסים נפוצים שכדאי לחפש כשמנתחים שגיאות ANR:
- האפליקציה מבצעת פעולות איטיות שכוללות קלט/פלט ב-thread הראשי.
- האפליקציה מבצעת חישוב ארוך ב-thread הראשי.
- ה-thread הראשי מבצע קריאת Binder סינכרונית לתהליך אחר, ולתהליך האחר לוקח הרבה זמן להחזיר תשובה.
- ה-thread הראשי חסום. הוא נמצא בהמתנה לבלוק מסונכרן לפעולה ארוכה שמתרחשת ב-thread אחר.
- ה-thread הראשי נמצא במבוי סתום עם thread אחר, בתהליך שלכם או באמצעות קריאת Binder. ה-thread הראשי לא רק ממתין לסיום של פעולה ארוכה, אלא נמצא במצב של מבוי סתום.
השיטות הבאות יכולות לעזור לכם לקבוע מה הגורם לשגיאות ה-ANR.
HealthStats
HealthStats מספק מדדים לגבי תקינות האפליקציה על ידי תיעוד של זמן המשתמש והמערכת הכולל, זמן המעבד (CPU), הרשת, נתוני הרדיו, זמן ההפעלה/הכיבוי של המסך והתראות ההתעוררות. כך אפשר למדוד את השימוש הכולל ב-CPU ואת התרוקנות הסוללה.
ניפוי באגים
Debug עוזר לבדוק אפליקציות ל-Android במהלך הפיתוח, כולל מעקב וספירת הקצאות כדי לזהות תקיעות ועיכובים באפליקציות.
אתם יכולים גם להשתמש ב-Debug כדי לקבל מוני זיכרון מקוריים ומוני זיכרון של זמן ריצה, ומדדי זיכרון שיכולים לעזור לכם לזהות את הזיכרון שבשימוש של תהליך מסוים.
ApplicationExitInfo
המאפיין ApplicationExitInfo זמין ב-Android 11 (רמת API 30) ומעלה, והוא מספק מידע על הסיבה ליציאה מהאפליקציה. האירועים האלה כוללים מקרי ANR, זיכרון נמוך, קריסות של אפליקציות, שימוש מוגזם ב-CPU, הפרעות למשתמשים, הפרעות למערכת ושינויים בהרשאות בתחילת ההפעלה.
מצב קפדני
שימוש ב-StrictMode עוזר לכם למצוא פעולות קלט/פלט מקריות בשרשור הראשי בזמן שאתם מפתחים את האפליקציה. אתם יכולים להשתמש ב-StrictMode ברמת האפליקציה או הפעילות.
הפעלת תיבות דו-שיח של ANR ברקע
ב-Android מוצגות תיבות דו-שיח של ANR לאפליקציות שבהן עיבוד ההודעה לכולם נמשך יותר מדי זמן, רק אם האפשרות הצגת כל השגיאות מסוג ANR מופעלת באפשרויות למפתחים במכשיר. לכן, תיבות דו-שיח של ANR ברקע לא תמיד מוצגות למשתמש, גם כשיש בעיות בביצועים של האפליקציה.
צווארי בקבוק ביצירה מחדש של קומפוזיציה
משתמשים ב-Android Studio Profiler וב-Layout Inspector כדי לאתר צווארי בקבוק בהרכבה מחדש. מידע נוסף זמין במאמר הביצועים של Jetpack Compose.
איך שולפים קובץ מעקב
מערכת Android שומרת מידע על המעקב כשהיא נתקלת בשגיאת ANR. בגרסאות ישנות יותר של מערכת ההפעלה, יש קובץ /data/anr/traces.txt אחד במכשיר. בגרסאות חדשות יותר של מערכת ההפעלה, יש כמה קובצי /data/anr/anr_*. אפשר לגשת לקובצי המעקב של ה-ANR ממכשיר או מאמולטור באמצעות Android Debug Bridge (adb) כמשתמש root:
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
כדי ליצור דוח על באג ממכשיר פיזי, אפשר להשתמש באפשרות למפתחים "יצירת דוח על באג" במכשיר או בפקודה adb bugreport במחשב הפיתוח. מידע נוסף זמין במאמר תיעוד וקריאה של דוחות על באגים.
איך פותרים את הבעיות
אחרי שמזהים את הבעיה, אפשר להשתמש בטיפים שבקטע הזה כדי לפתור בעיות נפוצות.
קוד איטי ב-thread הראשי
מזהים את המקומות בקוד שבהם ה-thread הראשי של האפליקציה עסוק במשך יותר מ-5 שניות. חפשו את תרחישי השימוש החשודים באפליקציה ונסו לשחזר את ה-ANR.
בעיה נפוצה היא משימה שפועלת במשך זמן רב ישירות ב-composable:
@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 בשכבת הנתונים.
מבוי סתום
מבוי סתום מתרחש כש-thread נכנס למצב המתנה כי משאב נדרש מוחזק על ידי thread אחר, שגם הוא ממתין למשאב שמוחזק על ידי ה-thread הראשון. אם ה-thread הראשי של האפליקציה נמצא במצב הזה, סביר להניח שיתרחשו שגיאות ANR.
מבוי סתום הוא תופעה שנחקרה רבות במדעי המחשב, ויש אלגוריתמים למניעת מבוי סתום שבהם אפשר להשתמש כדי להימנע ממצבים כאלה.
למידע נוסף, קראו את המאמרים מבוי סתום ואלגוריתמים למניעת מבוי סתום בוויקיפדיה.
כשמשתמשים ב-Kotlin וב-Compose, אפשר להחליף נעילות פרימיטיביות ב-Mutexes של שגרות המשך (coroutine) ללא חסימה (Mutex.withLock) כדי למנוע חסימה של השרשור על ידי השעיה של הקשר ההרצה במקום הקפאה של שרשור ה-UI. לדוגמה:
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.
האפליקציה צריכה לבצע רק פעולות קצרות ב-method onReceive של BroadcastReceiver. עם זאת, אם האפליקציה דורשת עיבוד מורכב יותר כתוצאה מהודעה לכולם, צריך לדחות את המשימה ל-ViewModel (תוך ניצול היכולות של פונקציות ה-coroutine, הסקופים והדיספצ'רים של Kotlin) אם המשימה צפויה להימשך כמה שניות לכל היותר, לכל סוג של מאחסן מצב, או ל-WorkManager למשימות שצפויות להימשך יותר מכמה שניות.
GameActivity
הספרייה GameActivity הפחיתה את מספר מקרי ה-ANR במקרי בוחן של משחקים ואפליקציות שנכתבו ב-C או ב-++C. אם תחליפו את פעילות ה-Native הקיימת ב-GameActivity, תוכלו להפחית את החסימה של שרשור ה-UI ולמנוע חלק ממקרי ה-ANR.
מידע נוסף על שגיאות ANR זמין במאמר שמירה על תגובתיות האפליקציה. למידע נוסף על שרשורים, ראו שיפור הביצועים באמצעות שרשורים.
מקורות מידע נוספים
צפייה בתוכן
מומלץ בשבילכם
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- יציאות רבות מדי ממצב שינה