מידע על ניהול זיכרון

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

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

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

  • מניעת הפעלה קרה (חוויית משתמש ושימור): כששחקן עובר באופן זמני מהמשחק שלכם (לדוגמה, כדי לענות להתראה או לבדוק הודעה), מערכת ההפעלה מעבירה את תהליך המשחק לרקע. אם הזיכרון שבשימוש ברקע של המשחק גבוה מדי, מנגנון ה-Low Memory Killer‏ (LMK) של המערכת נותן עדיפות לסיום תהליך המשחק כדי לפנות זיכרון RAM למשימות בחזית. בפעם הבאה שהמשתמש יחזור למשחק, במקום חזרה חלקה ומיידית, המשחק יצטרך לעבור הפעלה קרה ארוכה – טעינה מחדש של נכסים גרפיים כבדים, אודיו וקבצים בינאריים של מנוע המשחק מהאחסון. כדי למנוע את הסגירות השקטות האלה ברקע, לשמור על מצב המשתמש ולוודא שהשחקנים יוכלו להמשיך את הסשן שלהם באופן מיידי, חשוב לשמור על שימוש נמוך בזיכרון ברקע. לפרטים נוספים על ההתנהגות של LMK במערכת, אפשר לעיין במדריך תפקוד האפליקציה – מנגנוני LMK.
  • יציבות המכשיר והסביבה: שימוש לא יעיל בזיכרון ודליפות זיכרון פוגעים בתקינות המערכת באופן כללי. כשיש מחסור בזיכרון המערכת, המערכת נתונה ללחץ כבד, מה שגורם לירידה בקצב הפריימים, לבעיות בממשק המשתמש ולתקלות באודיו. אם העומס על הזיכרון חמור מדי, מנגנון ה-Low Memory Killer‏ (LMK) של המערכת מפסיק באופן אגרסיבי תהליכים ברקע, מה שגורם לאפליקציות אחרות לחוות הפעלות קרות איטיות ולאובדן מצב המשתמש כשהשחקנים עוברים בין משימות.
  • סגירת תהליכים ברמת הפלטפורמה: החל מ-Android 17 (רמת API‏ 37), המערכת סוגרת באופן יזום יותר תהליכים שמשתמשים ביותר מדי זיכרון. אם המשחק תופס יותר מדי מקום, מערכת ההפעלה יכולה לסיים את התהליך שלו באופן פתאומי בלי ליצור דוח קריסות רגיל.
  • תאימות למכשירים: מכשירי הדגל כוללים זיכרון RAM בנפח 12GB עד 16GB, אבל חלק גדול מהגיימרים בעולם משתמשים במכשירים עם זיכרון RAM בנפח 4GB או 6GB. ניהול זיכרון נכון מבטיח שהמשחק יישאר נגיש ורספונסיבי בכל רמות החומרה, בלי צורך בחבילות נכסים מורכבות ונפרדות.

הסבר על הזיכרון ב-Android

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

מושגי ליבה בזיכרון של Android

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

  • סקירה כללית של הזיכרון: מערכת Android משתמשת בדפדוף ובמיפוי זיכרון (mmap) כדי לנהל את זיכרון ה-RAM. הוא לא תומך בקובץ החלפה מסורתי בדיסק, אלא מסתמך על דחיסת דפים (באמצעות zRAM) ועל החזרת דפים כדי לפנות זיכרון פיזי.
  • הקצאת זיכרון בין תהליכים: מערכת Android משתפת את ה-RAM בכל המערכת. הוא מקצה ערימות ספציפיות להרצה של מכונות וירטואליות של Dalvik או ART, ומאפשר לסביבות פיתוח מקוריות (כמו מנועי משחקים ב-C++‎) לבקש זיכרון מערימת המערכת המקורית.
  • ניהול הזיכרון של האפליקציה: מערכת Android פועלת לפי מודל של ריבוי תהליכים, ולכן האפליקציות צריכות לעקוב באופן דינמי אחרי מצב מחזור החיים שלהן ולשחרר באופן יזום משאבים מיותרים (כמו גרפיקה ומפות סיביות שלא נשמרו במטמון) כדי לתמוך בתקינות המערכת.
  • סקירה כללית של תהליכים ושרשורים: המערכת מסווגת תהליכים להיררכיה על סמך החשיבות והניראות שלהם כרגע בעיני המשתמש, וקובעת אילו תהליכים יישארו פעילים ואילו יופסקו קודם כשהזיכרון נמוך.

המדד "הזיכרון הכולל שבשימוש"

התכונה "מגבלת הזיכרון" ברמת הפלטפורמה ב-Android 17 מעריכה את צריכת התהליכים באמצעות הזיכרון הכולל שבשימוש, ולא באמצעות גודל הזיכרון הפיזי הכולל (RSS) או גודל הזיכרון הווירטואלי.

הזיכרון הכולל שבשימוש = RSS אנונימי (RssAnon) + החלפה לא דחוסה (VmSwap)

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

מגבלות זיכרון

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

מגביל הזיכרון ב-Android מגרסה 17 ואילך

ב-Android מגרסה 17 (רמת API‏ 37) ואילך, המערכת מנהלת מגבלות זיכרון מחמירות לכל אפליקציה באמצעות Linux cgroup v2, כדי למנוע מצב שבו אפליקציות ספציפיות גורמות לחוסר יציבות בכל המערכת. פרטים נוספים על ההטמעה הטכנית זמינים במדריך של AOSP בנושא הגבלת הזיכרון ובפוסט בבלוג Prioritizing Memory Efficiency: Essential Steps for Android 17.

  • מנגנון: הכלי להגבלת הזיכרון עוקב אחרי כל התהליכים של האפליקציה ומקצה מגבלות באופן דינמי על סמך מצב מחזור החיים של התהליך:
    • תהליכים גלויים (בחזית): תהליכי אפליקציה שמציגים כרגע ממשק משתמש צפויים להפעיל קבוצת עבודה גדולה יותר של משאבים, ומוקצית להם מגבלה נדיבה יותר.
    • תהליכים לא גלויים (ברקע או בשירותים): תהליכי אפליקציה שמבצעים עבודה פעילה בלי להציג ממשק משתמש מוגבלים לתקציב הדוק ומגביל יותר.
  • מאפייני ליבה: השירות מסתמך על שני מאפיינים עיקריים:
    • ‫memory.high: מגבלה גמישה. אם חורגים מהמגבלה, הליבה מגבילה את התהליך ומנסה לפנות זיכרון באופן אגרסיבי. ההחזרה הזו עלולה לגרום לירידה בביצועים של המשחק.
    • ‫memory.swap.max: מגדיר מגבלה קשיחה על שטח ההחלפה או ה-zRAM שהתהליך יכול להשתמש בו.
  • התנהגות הסיום: אם תהליך ממשיך להקצות זיכרון אנונימי מעבר ל-memory.high וממצה את קיבולת ההחלפה שלו, ההקצאות נכשלות ומערכת ההפעלה מסיימת את התהליך באופן שקט. הסגירה הזו מתועדת ביומן באמצעות ApplicationExitInfo עם סיבת היציאה Memory Limiter (זמינה החל מ-Android 17, ‏ 26Q4).

מגבלות זיכרון חדשות בתפקוד האפליקציה ב-Play Console

כדי לעזור למפתחים לזהות באופן יזום בעיות בזיכרון, Google Play מציגה מדדים חדשים בתפקוד האפליקציה ב-Play Console. מערכת Play Console עוקבת אחרי האחוזון ה-90 (P90) של הזיכרון שבשימוש (RSS אנונימי + החלפה) בסשנים של המשחק כדי לזהות חריגות קיצוניות.

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

הנחיות מפורטות ומגבלות זיכרון זמינות במאמר תפקוד האפליקציה – מהם ספי ההתנהגות הלא תקינה?.

שירותים שהשפיעו על המשתמשים

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

  • שירותים שפועלים בחזית (FGS)
  • משימות בעדיפות גבוהה
  • משימות של העברת נתונים ביוזמת המשתמשים
  • שירותים שקשורים למערכת או שירותים שקשורים לאפליקציות אחרות

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

דרישות R8

כדי לצמצם את גודל ה-bytecode ולהפחית את התקורה של תהליך Java בסיסי, Google Play Console מעריך את האופטימיזציה של הקוד כחלק מהנחיות האיכות של האפליקציה. מידע נוסף על הדרישות והמגבלות של אופטימיזציה שחלות על משחקים זמין במאמר אופטימיזציה של קוד DEX במסמכי התיעוד של "תפקוד האפליקציה".

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

אם אתם משתמשים במנוע משחק, התנהגות ברירת המחדל של R8 משתנה בהתאם למנוע ולגרסה:

דרישות לגבי מפת הסיביות

מפות סיביות מייצגות חלק עצום מהשימוש בזיכרון במשחקים מודרניים באיכות גבוהה. נתוני הפיקסלים של Bitmap מאוחסנים ישירות בערימה המקורית הלא מנוהלת ב-Android 8.0 (רמת API ‏26) ואילך, ולכן טעינת תמונות לא אופטימלית עלולה לגרום לתהליכים לחרוג מסף הזיכרון של הפלטפורמה. במאמר אופטימיזציה של השימוש בתמונות מפורטות שיטות מומלצות לשינוי קנה מידה של תמונות ולשמירה שלהן במטמון.

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

כדי לבצע אופטימיזציה יעילה של הזיכרון של המשחק, קודם צריך להבין איך פלטפורמת Android מודדת את טביעת הרגל שלו. ב-Android 17, מדד הזיכרון מעודכן כדי לעקוב אחרי הסכום של RSS אנונימי (RssAnon) ושטח החלפה לא דחוס (VmSwap), לא כולל זיכרון מגובה בקבצים או זיכרון פרטי של GPU. במדריך הזה מוסבר איך להשתמש בכלים ברמת המערכת כמו Perfetto ו-meminfo, להטמיע ממשקי API לאבחון כמו ProfilingManager ו-onTrimMemory, ולחלץ הקצאות זיכרון מדויקות ב-Unity וב-Unreal Engine. כדאי להבין איך ליצור פרופיל מדויק של המשחק ולהימנע מהפרעות בביצועים שקשורות לדגימה מסורתית של זיכרון בזמן ריצה.

מידע נוסף זמין במאמר מעקב אחרי השימוש בזיכרון.

אסטרטגיות להפחתת השימוש בזיכרון

מנועי משחקים מפשטים את הפיתוח בפלטפורמות שונות, אבל הטיפול שלהם בזיכרון כברירת מחדל יכול להפעיל מגבלות זיכרון ברמת מערכת ההפעלה. בדף הזה מפורטים שלבים מעשיים לאופטימיזציה, שמותאמים במיוחד ל-Unity ול-Unreal Engine. להבין למה הסתמכות על onTrimMemory שמבוסס על Java יכולה לגרום לקיפאון (deadlock) ב-Unity, ואיך להשתמש במקום זאת בקריאות חוזרות במחזור חיים מקוריות. בנוסף, תגלו אופטימיזציות חשובות ברמת הנכס, כמו שימוש בדחיסת טקסטורה של ASTC 8x8 והגדרת פריקות של נכסים, כדי שהמשחק יפעל בצורה חלקה בכל רמות החומרה.

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