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

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

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

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

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

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

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

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

תוכלו להטמיע את הקריאה החוזרת (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) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

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) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

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

כדי לאפשר הפעלה של כמה תהליכי עבודה, מערכת 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 בקוד בצד הלקוח. פרוטו-באבלים רגילים יוצרים קוד מפורט מאוד, שעלול לגרום לבעיות רבות באפליקציה, כמו שימוש מוגבר ב-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 לפני שמשתמשים בה.

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

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

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