האפליקציה צריכה להיות רספונסיבית

איור 1. תיבת דו-שיח של ANR שמוצגת למשתמש.

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

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

טריגרים של ANR

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

לדוגמה, ANR יכולה להתרחש אם אפליקציה מבצעת פעולת קלט/פלט (I/O) חוסמת, כמו גישה לרשת, בשרשור UI. דוגמה נוספת היא כשבאפליקציה מושקע יותר מדי זמן בבניית מבנה מורכב בזיכרון או בחישוב המהלך הבא במשחק בשרשור UI.

ב-Android, שירותי המערכת ActivityManager וWindowManager עוקבים אחרי מהירות התגובה של האפליקציה. מערכת Android מציגה את תיבת הדו-שיח ANR לאפליקציה כשהיא מזהה אחד מהתנאים הבאים:

  • אין תגובה לאירוע קלט – כמו אירועים של לחיצה על מקש או הקשה על המסך – תוך 5 שניות.
  • BroadcastReceiver לא מסיימת את ההפעלה תוך 10 עד 20 שניות, לגבי כוונות בחזית. מידע נוסף זמין במאמר בנושא פסק זמן של מקלט שידורים.

איך להימנע ממקרי ANR

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

  • חשוב לוודא שה-thread הראשי לא חסום בכל רגע נתון, ולהשתמש ב-threads באופן אסטרטגי.

    • אל תבצעו פעולות חסימה או פעולות ארוכות בשרשור הראשי של האפליקציה. במקום זאת, משתמשים בשגרות המשך (coroutines) של Kotlin כדי להעביר את העבודה למפיצים ברקע (כמו Dispatchers.IO או Dispatchers.Default). משתמשים במנגנונים כמו viewModelScope כדי להפעיל בבטחה את המשימות האלה ברקע, או ב-LaunchedEffect כדי להפעיל אותן בתגובה לשינויים במצב של Compose.

    • כדאי לנסות לצמצם את התחרות על נעילות בין ה-thread הראשי לבין threads אחרים.

    • מומלץ לצמצם את כמות העבודה שאינה קשורה לממשק המשתמש בשרשור הראשי, למשל כשמטפלים בשידורים או כשמפעילים שירותים. כל שיטה או פונקציה שפועלות בשרשור של ממשק המשתמש צריכות לבצע כמה שפחות עבודה. בפרט, הפעילויות צריכות לבצע כמה שפחות פעולות כדי להגדיר את עצמן בשיטות מרכזיות במחזור החיים, כמו onCreate ו-onResume. אף פעם אל תבצעו פעולות קלט/פלט או חישובים כבדים שחוסמים ישירות בתוך פונקציה קומפוזבילית. הפעולה הזו חוסמת את שרשור ה-UI במהלך קומפוזיציה ורה-קומפוזיציה. מידע נוסף על פתרונות זמינים לתזמון עבודה בשרשור ברקע ולתקשורת חזרה עם ממשק המשתמש מופיע במאמר סקירה כללית של משימות ברקע.

    • צריך להיזהר כשמשתפים מאגרי שרשורים בין רכיבים. אל תשתמשו באותם שרשורים לפעולות שעלולות לחסום לזמן רב ולמשימות רגישות לזמן, כמו קבלת שידורים.

  • שמירה על הפעלה מהירה של האפליקציה. צריך לצמצם פעולות איטיות או חוסמות בקוד ההפעלה של האפליקציה, כמו מתודות שמופעלות במהלך הגדרת הזרקת תלות (כמו ב-Hilt) או רכיבים שאותחלו באמצעות ספריית ההפעלה של Jetpack. כדי לבצע אופטימיזציה נוספת של הפעלת האפליקציה, אפשר להשתמש בפרופילים של Baseline, בפרופילים להפעלה וב-R8.

  • אם אתם משתמשים ב-BroadcastReceiver, כדאי להריץ את מקלטי השידור בשרשור שאינו השרשור הראשי באמצעות Context.registerReceiver. מידע נוסף זמין במאמר בנושא ANR ב-BroadcastReceiver.

    • אם אתם משתמשים ב-goAsync, חשוב לוודא שהפונקציה PendingResult.finish נקראת במהירות לפני שחלף הזמן הקצוב לתגובה (ANR).

מקרי ANR ב-BroadcastReceiver

זמן הביצוע של BroadcastReceiver מוגבל כי מקלטי שידור נועדו לבצע כמויות קטנות של עבודה בדיסקרטיות ברקע, כמו שמירת הגדרה או רישום של Notification. לכן, כמו במקרים אחרים של קריאות לשיטות בשרשור UI, האפליקציות צריכות להימנע מפעולות או מחישובים שעלולים להימשך זמן רב במקלט שידורים. במקום לבצע משימות ארוכות דרך שרשור UI, כדאי לבצע אותן ברקע כדי להריץ אותן מאוחר יותר. מידע נוסף על פתרונות אפשריים זמין במאמר סקירה כללית של משימות ברקע.

בעיה נפוצה נוספת באובייקטים מסוג BroadcastReceiver מתרחשת כשהם מופעלים בתדירות גבוהה מדי. ביצוע תכופות של פעולות ברקע עלול להקטין את כמות הזיכרון שזמינה לאפליקציות אחרות. מידע נוסף על הפעלה והשבתה יעילות של אובייקטים של BroadcastReceiver זמין במאמר סקירה כללית על שידורים.

שיפור ההיענות

באופן כללי, 100 עד 200 אלפיות השנייה הוא הסף שמעבר לו המשתמשים תופסים את האפליקציה כאיטית. הנה טיפים נוספים שיעזרו לכם לגרום לאפליקציה להיראות מגיבה למשתמשים:

  • אם האפליקציה מבצעת פעולות ברקע בתגובה לקלט של המשתמש, צריך להציג שהפעולה מתבצעת, למשל באמצעות CircularProgressIndicator או LinearProgressIndicator בממשק המשתמש.

  • במשחקים, מבצעים חישובים של מהלכים ב-Coroutine ברקע או ב-Thread עובד.

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

  • כדי לזהות צווארי בקבוק בתגובתיות של האפליקציה, אפשר להשתמש בכלי ביצועים כמו Perfetto ו-CPU Profiler.