ניהול הזיכרון של האפליקציה

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

זיכרון גישה אקראית (RAM) הוא משאב חשוב לכל סביבת פיתוח תוכנה. זהו ערך רב יותר למערכת הפעלה לנייד שבה הזיכרון הפיזי מוגבל בדרך כלל. למרות שגם זמן הריצה של Android (ART) והמכונה הווירטואלית של Dalvik מבצעים אשפה שגרתית זה לא אומר שאפשר להתעלם ממתי ומאיפה האפליקציה מקצה ומשחררת זיכרון. עדיין צריך למנוע דליפות זיכרון – בדרך כלל נגרמות על ידי החזקה של אובייקט הפניות במשתנים של חבר סטטי, ולשחרר Reference אובייקטים ב- הזמן המתאים כפי שמוגדר על ידי קריאות חוזרות (callback) במחזור החיים.

מעקב אחר השימוש בזיכרון ובזיכרון הזמינים

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

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

שחרור הזיכרון בתגובה לאירועים

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

תוכלו להטמיע את הקריאה החוזרת (callback) של onTrimMemory() כדי להגיב למקרים שונים שקשורים לזיכרון אירועים, כפי שמוצג בדוגמה הבאה:

Kotlin

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        // Determine which lifecycle or system event is raised.
        when (level) {

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */
            }

            else -> {
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
            }
        }
    }
}

Java

import android.content.ComponentCallbacks2;
// Other import statements.

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    public void onTrimMemory(int level) {

        // Determine which lifecycle or system event is raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

בדיקת נפח הזיכרון

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

כדי לא להיגמר לכם הזיכרון, אפשר לשלוח שאילתה למערכת כדי לברר מה נפח הערימה. זמין במכשיר הנוכחי. אפשר להריץ שאילתה במערכת לגבי הדמויות האלה getMemoryInfo() הפעולה הזו תחזיר ActivityManager.MemoryInfo אובייקט שמספק מידע על סטטוס הזיכרון הנוכחי של המכשיר, כולל נתונים זמינים הזיכרון, סך הזיכרון וסף הזיכרון - רמת הזיכרון שבה המערכת מתחילה תהליכי עוצרים. האובייקט ActivityManager.MemoryInfo גם חושף lowMemory, זהו ערך בוליאני פשוט שמציין אם אין מספיק זיכרון במכשיר.

קטע הקוד לדוגמה הבא מראה איך להשתמש ב-method getMemoryInfo() באפליקציה שלך.

Kotlin

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

Java

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

להשתמש במבנים קוד חסכוניים יותר בזיכרון

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

שימוש מצומצם בשירותים

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

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

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

שימוש במאגרי נתונים שעברו אופטימיזציה

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

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

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

כדאי להיזהר עם פשטות קוד

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

שימוש ב-lite protobufs לנתונים סריאליים

פרוטוקול מאגרי נתונים זמניים (pretobufs) הם מנגנון ניתן להרחבה ניטרלי-שפה, נייטרלי שפה Google לסידור נתונים מובְנים בהמשכים – דומה ל-XML אבל קטן יותר, מהיר ופשוט יותר. אם המיקום כאשר משתמשים ב-protobufs בנתונים שלכם, השתמשו תמיד ב-litesbufs בקוד בצד הלקוח. רגיל Protobufs יוצרים קוד מפורט מאוד, שעלול לגרום לבעיות רבות באפליקציה, כמו שימוש מוגבר ב-RAM, הגדלה משמעותית בגודל ה-APK והפעלה איטית יותר.

מידע נוסף זמין במאמר פרוטובוף Readme.

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

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

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

לדוגמה, אפשר להקצות מספר אובייקטים זמניים בתוך לולאת for. או אפשר ליצור Paint חדש או Bitmap אובייקטים בתוך onDraw() פונקציה של תצוגה. בשני המקרים, האפליקציה יוצרת אובייקטים רבים במהירות בנפח גבוה. האלה יכולים לצרוך במהירות את כל הזיכרון הזמין בקרב הדור הצעיר, ולאלץ את איסוף האשפה יתרחש.

אפשר להשתמש ב-Memory Profiler כדי למצוא את המקומות ב בקוד שבו רמת הנטישה של הזיכרון גבוהה לפני שתוכלו לתקן אותם.

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

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

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

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

הסרת משאבים וספריות שצורכים הרבה זיכרון

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

הקטנת הגודל הכולל של ה-APK

על ידי הקטנת הגודל הכולל של האפליקציה, אפשר לצמצם משמעותית את השימוש בזיכרון של האפליקציה. גודל מפת סיביות (Bitmap), משאבים, פריימים של אנימציה וספריות של צד שלישי יכולים להשפיע על הגודל של האפליקציה. Android Studio ו-Android SDK מספקים כמה כלים שעוזרים לצמצם את הגודל של את המשאבים ואת יחסי התלות החיצוניים שלכם. הכלים האלה תומכים בשיטות מודרניות לכיווץ קוד, אוסף R8.

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

שימוש ב-Hilt או Dagger 2 להזרקת תלות

frameworks להחדרת תלות יכולות לפשט את הקוד שכותבים ולספק לו שימושית לבדיקה ולשינויים אחרים בתצורה.

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

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

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

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

גם ספריות מסוימות שמותאמות לניידים יכולות לגרום לבעיות עקב הטמעות שונות. עבור לדוגמה, ספרייה אחת עשויה להשתמש ב-{2/} Protobufs וספרייה אחרת משתמשת במיקרו-פרוטופים, וכתוצאה מכך הטמעות שונות של אב-טיפוס באפליקציה. יכול להיות שזה יקרה בהטמעות שונות של רישום ביומן, ניתוח נתונים, frameworks, טעינת תמונות, שמירה במטמון ועוד הרבה דברים שלא ציפיתם להם.

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

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