שיטות מומלצות לניהול זיכרון

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

מבוא

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

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

לפני שפונים לתמיכה

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

מניעת דליפות זיכרון

כדי להימנע מכמה מהגורמים הנפוצים ביותר לדליפות זיכרון בקוד שמשתמש בערכות SDK של Google, כדאי לפעול לפי השיטות המומלצות הבאות.

שיטות מומלצות לאפליקציות ל-Android

בודקים שביצעתם את כל הפעולות הבאות באפליקציה ל-Android:

  1. שחרור משאבים שלא נמצאים בשימוש.
  2. ביטול הרישום של מאזינים כשאין בהם יותר צורך.
  3. ביטול משימות כשלא צריך אותן.
  4. העברת שיטות מחזור חיים כדי לשחרר משאבים
  5. משתמשים בגרסאות העדכניות ביותר של ערכות ה-SDK.
  6. כדי למנוע ANR, צריך להימנע מחסימת ה-thread הראשי במהלך האתחול.

בסעיפים הבאים מפורטים פרטים ספציפיים לגבי כל אחת מהשיטות האלה.

שחרור משאבים שלא בשימוש

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

שחרור הפניות לא עדכניות ל-GoogleMap ב-GeoSDKs

טעות נפוצה היא ש-GoogleMap עלול לגרום לדליפת זיכרון אם הוא נשמר במטמון באמצעות NavigationView או MapView. ל-GoogleMap יש קשר של אחד לאחד עם NavigationView או MapView שממנו הוא מאוחזר. צריך לוודא ש-GoogleMap לא נשמר במטמון, או שההפניה משוחררת כשקוראים ל-NavigationView#onDestroy או ל-MapView#onDestroy. אם משתמשים ב-NavigationSupportFragment, ב-MapSupportFragment או בקטע משלכם שעוטף את התצוגות האלה, צריך לשחרר את ההפניה ב-Fragment#onDestroyView.

class NavFragment : SupportNavigationFragment() {

  var googleMap: GoogleMap?

  override fun onCreateView(
    inflater: LayoutInflater,
    parent: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View  {
    super.onCreateView(inflater,parent,savedInstanceState)
    getMapAsync{map -> googleMap = map}
  }

  override fun onDestroyView() {
    googleMap = null
  }
}

ביטול הרישום של מאזינים כשכבר לא צריך אותם

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

לדוגמה, נניח שהאפליקציה שלכם משתמשת ב-Navigation SDK והיא קוראת ל-listener הבא כדי להאזין לאירועי הגעה: addArrivalListener כדי להאזין לאירועי הגעה, היא צריכה גם לקרוא ל-removeArrivalListener כשהיא כבר לא צריכה לעקוב אחרי אירועי ההגעה.

var arrivalListener: Navigator.ArrivalListener? = null

fun registerNavigationListeners() {
  arrivalListener =
    Navigator.ArrivalListener {
      ...
    }
  navigator.addArrivalListener(arrivalListener)
}

override fun onDestroy() {
  navView.onDestroy()
  if (arrivalListener != null) {
    navigator.removeArrivalListener(arrivalListener)
  }

  ...
  super.onDestroy()
}

ביטול משימות כשאין בהן צורך

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

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

העברה של שיטות מחזור חיים כדי לשחרר משאבים

אם האפליקציה משתמשת ב-Navigation SDK או ב-Maps SDK, צריך לוודא שמשחררים את המשאבים על ידי העברת שיטות מחזור החיים (מוצגות בהדגשה) אל navView. אפשר לעשות את זה באמצעות NavigationView ב-Navigation SDK או MapView ב-Maps SDK או ב-Navigation SDK. אפשר גם להשתמש ב-SupportNavigationFragment או ב-SupportMapFragment במקום להשתמש ישירות ב-NavigationView וב-MapView. קטעי התמיכה מטפלים בהעברה של שיטות מחזור החיים.

class NavViewActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    navView = ...
    navView.onCreate(savedInstanceState)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    navView.onSaveInstanceState(savedInstanceState)
  }

  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    navView.onTrimMemory(level)
  }

  /* Same with
    override fun onStart()
    override fun onResume()
    override fun onPause()
    override fun onConfigurationChanged(...)
    override fun onStop()
    override fun onDestroy()
  */
}

שימוש בגרסאות העדכניות של ה-SDK

ערכות ה-SDK של Google מתעדכנות כל הזמן עם תכונות חדשות, תיקוני באגים ושיפורים בביצועים. כדי לקבל את התיקונים האלה, חשוב לעדכן את ערכות ה-SDK באפליקציה.

כדי למנוע שגיאות ANR, צריך להימנע מחסימה של ה-thread הראשי במהלך האתחול

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

כדי להימנע משגיאות ANR שקשורות לאתחול של SDK:

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

ניפוי באגים של דליפות זיכרון

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

לפני שמתחילים, חשוב להבין איך מערכת Android מנהלת את הזיכרון. מידע נוסף זמין במאמר סקירה כללית על ניהול הזיכרון ב-Android.

כדי לנפות באגים של דליפות זיכרון, פועלים לפי התהליך הבא:

  1. משחזרים את הבעיה. השלב הזה חיוני לניפוי הבאגים.
  2. בודקים אם השימוש בזיכרון הוא צפוי. בודקים שהשימוש המוגבר שנראה כמו דליפה הוא לא הזיכרון שנדרש להפעלת האפליקציה.
  3. ניפוי באגים ברמה גבוהה. יש כמה כלי עזר שאפשר להשתמש בהם כדי לאתר באגים. יש שלושה סטים שונים של כלים סטנדרטיים שעוזרים לנפות באגים בבעיות זיכרון ב-Android: ‏ Android Studio,‏ Perfetto וכלי שורת הפקודה של Android Debug Bridge‏ (adb).
  4. בודקים את השימוש בזיכרון של האפליקציה. קבלת תמונת מצב של הזיכרון ומעקב אחר הקצאת הזיכרון, ואז ניתוח שלהם.
  5. תיקון דליפות זיכרון

בקטעים הבאים מוסבר בפירוט על השלבים האלה.

שלב 1: משחזרים את הבעיה

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

  • אילו תכונות מופעלות?

  • מהו רצף הפעולות הספציפי של המשתמש שגורם לדליפה?

    • ניסית להפעיל את הרצף הזה כמה פעמים?
  • באילו מצבים במחזור החיים האפליקציה הייתה?

    • האם ניסיתם כמה איטרציות במצבי מחזור חיים שונים?

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

שלב 2: בדיקה אם השימוש בזיכרון של האפליקציה הוא צפוי

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

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

  • השימוש הצפוי בזיכרון: הזיכרון משוחרר אחרי שהתרחיש מופסק.

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

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

שלב 3: ניפוי באגים ברמה גבוהה

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

Android Studio Memory Profiler

הכלי הזה מציג היסטוגרמה חזותית של הזיכרון שנצרך. אפשר להפעיל מאותו ממשק גם תמונות מצב של הזיכרון ומעקב אחר הקצאת הזיכרון. הכלי הזה הוא ההמלצה שמוגדרת כברירת מחדל. מידע נוסף זמין במאמר בנושא Memory Profiler ב-Android Studio.

Perfetto Memory Counters

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

ממשק המשתמש של Perfetto

כלי שורת הפקודה של ממשק הגישור של Android‏ (ADB)

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

  • ‫Meminfo מאפשר לכם לראות מידע מפורט על הזיכרון בנקודת זמן מסוימת.

  • Procstats מספקת נתונים סטטיסטיים מצטברים חשובים לאורך זמן.

נתון סטטיסטי חשוב שכדאי לבדוק כאן הוא הזיכרון שבשימוש המקסימלי של הזיכרון הפיזי (maxRSS) שהאפליקציה דורשת לאורך זמן. יכול להיות שהערך של MaxPSS לא יהיה מדויק. כדי לשפר את רמת הדיוק, אפשר להשתמש בדגל adb shell dumpsys procstats --help –start-testing.

מעקב אחר הקצאות

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

שלב 4: בדיקת השימוש בזיכרון של האפליקציה באמצעות תמונת מצב של הזיכרון

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

‫Android Studio יכול לזהות דליפות זיכרון שלא ניתן לתקן באמצעות GC. כשמבצעים צילום מצב של ה-heap, ‏ Android Studio בודק אם יש פעילות או fragment שאפשר עדיין להגיע אליהם אבל הם כבר הושמדו.

  1. יצירת תמונת מצב של הזיכרון
  2. ניתוח של תמונת מצב של הזיכרון כדי למצוא דליפות זיכרון
  3. תיקון דליפות זיכרון.

פרטים נוספים מופיעים בסעיפים הבאים.

יצירת תמונת מצב של הזיכרון

כדי ללכוד תמונת מצב של הזיכרון, אפשר להשתמש בממשק הגישור של Android‏ (ADB‏)‎ או בכלי Memory Profiler של Android Studio.

שימוש ב-adb כדי לצלם תמונת מצב של הזיכרון

כדי לתעד תמונת מצב של הזיכרון באמצעות adb:

  1. מחברים את מכשיר Android למחשב.
  2. פותחים שורת פקודה ועוברים לספרייה שבה נמצאים כלי ה-adb.
  3. כדי לצלם תמונת מצב של הזיכרון, מריצים את הפקודה הבאה :

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

  4. כדי לאחזר את תמונת מצב של הזיכרון, מריצים את הפקודה הבאה:

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

שימוש ב-Android Studio כדי לצלם תמונת מצב של הזיכרון

כדי לתעד תמונת מצב של הזיכרון באמצעות הכלי Memory Profiler ב-Android Studio, פועלים לפי השלבים שבקטע Capture a heapdump ב-Android.

ניתוח של תמונת המצב של הזיכרון כדי למצוא דליפות זיכרון

אחרי שיוצרים תמונת מצב של הזיכרון, אפשר להשתמש בכלי Memory Profiler של Android Studio כדי לנתח אותה. לשם כך, בצע את הצעדים הבאים:

  1. פותחים את פרויקט Android ב-Android Studio.

  2. בוחרים באפשרות הרצה ואז בוחרים בהגדרת ניפוי הבאגים.

  3. פותחים את הכרטיסייה Android Profiler.

  4. בוחרים באפשרות זיכרון.

  5. בוחרים באפשרות Open heap dump ובוחרים את קובץ ה-heap dump שיצרתם. בכלי ליצירת פרופיל של הזיכרון מוצג תרשים של השימוש בזיכרון של האפליקציה.

  6. משתמשים בתרשים כדי לנתח את ה-heap dump:

    • לזהות אובייקטים שכבר לא בשימוש.

    • זיהוי אובייקטים שצורכים הרבה זיכרון.

    • בודקים כמה זיכרון כל אובייקט צורך.

  7. המידע הזה יכול לעזור לכם לצמצם את החיפוש או למצוא את המקור של דליפת הזיכרון ולתקן אותה.

שלב 5: פותרים בעיות של דליפות זיכרון

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

כלים אחרים לניפוי באגים

אם אחרי השלמת השלבים האלה עדיין לא מצאתם ותיקנתם את דליפת הזיכרון, נסו להשתמש בכלים הבאים:

ניפוי באגים בזיכרון בקוד Native באמצעות מעקב אחר הקצאות

גם אם אתם לא משתמשים ישירות בקוד Native, כמה ספריות נפוצות של Android כן משתמשות בקוד כזה, כולל ערכות SDK של Google. אם אתם חושבים שיש דליפת זיכרון בקוד Native, תוכלו להשתמש בכמה כלים כדי לנפות באגים. מעקב אחר הקצאת הזיכרון באמצעות Android Studio או heapprofd (שמתאים גם ל-Perfetto) הוא דרך מצוינת לזהות את הגורמים האפשריים לדליפת זיכרון, ולרוב גם הדרך המהירה ביותר לניפוי באגים.

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

זיהוי דליפות באמצעות LeakCanary

‫LeakCanary הוא כלי יעיל לזיהוי דליפות זיכרון באפליקציות ל-Android. מידע נוסף על השימוש ב-LeakCanary באפליקציה זמין בכתובת LeakCanary.

איך מדווחים על בעיות בערכות SDK של Google

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

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

  • קובצי Heap dumps שצולמו מהאפליקציה אחרי שיצרת מחדש את הבעיה. מצלמים שני קובצי dump של ה-heap בשתי נקודות זמן שונות, שבהן אפשר לראות שהשימוש בזיכרון גדל באופן משמעותי.

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

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

  • דוחות קריסות של קריסות שקשורות לזיכרון.

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