כלים וביצועים

אסטרטגיות פרקטיות לצמצום הזיכרון שבשימוש מפת הסיביות (bitmap) של האפליקציה

רמקולים

מידע כללי

מידע על ההרצאה

ניהול הזיכרון של מפת הסיביות (bitmap) חיוני לצמצום הסיכונים של חוסר בזיכרון (OOM) באפליקציות ל-Android עם תוכן חזותי עשיר. מפתחים יודעים שצריך להקטין את גודל התמונות, אבל כדי להשיג יציבות של האפליקציה לטווח ארוך, צריך לשלוט בשני עקרונות חשובים אחרים: שימוש חוזר ומִחזור. בסשן הזה נציג מדריך מעשי לאופטימיזציה של ביצועי הזיכרון באמצעות כלים מודרניים, ספריות קוד פתוח כמו Glide ו-Coil וממשקי API ברמת המערכת.

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

סיכום

  • בפברואר 2027, Google Play תתחיל לאכוף ערכי סף לביצועי הזיכרון. חריגה מהאחוזון ה-90 משפיעה ישירות על הערכת התקינות של האפליקציה, ויכולה לפגוע בחשיפה שלה בחנות Play.
  • מפות סיביות מתרחבות לפי המאפיינים ועומק הצבעים כשהן מפוענחות, ולכן הן צורכות הכי הרבה זיכרון ברוב האפליקציות. כמה תמונות ברזולוציה מלאה יכולות לתפוס מאות מגה-בייט.
  • כדי לצמצם את צריכת הזיכרון, כדאי להקטין את הרזולוציה של התמונות ולפענח רק את האזורים שגלויים. כדי לא לטעון תמונות גדולות מדי ברזולוציה מלאה, אפשר לבקש מידות תואמות לתמונות דרך CDN או להשתמש בממשקי API כמו BitmapRegionDecoder.
  • שימוש בכמה ספריות לטעינת תמונות מכפיל את השימוש בזיכרון המטמון של ערימת הזיכרון שמוגדרת כברירת מחדל, ומשכפל את מפות הסיביות (bitmap) ששמורות. להשתמש בטוען אחד או להגדיר תקציבי זיכרון מפורשים בטוענים הקיימים.
  • כדי לפנות את הזיכרון של התמונות כשהאפליקציה עוברת לרקע, צריך לטפל ב-onTrimMemory. כדי למנוע גידול בזיכרון ברקע, צריך להפסיק את הסקרים והאנימציות ברקע ב-onStop.

משאבים

Gemini Notebook

נושאים

  • bitmap-optimization
  • android-memory
  • android-vitals
  • שמירת תמונות במטמון
  • memory-profiler

תמליל

הורדת התמליל

מבוא וסקירה כללית על הזיכרון של מפת הסיביות (bitmap)

  • Fung Lam

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

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

  • כן, תודה.

  • לפני שנתחיל, אפשר לשאול שאלה?

  • כמה מכם קנו טלפון חדש השנה?

  • וואו!

  • וואו!

  • תגיד את האמת, אני לא אאשים אותך אם לא תקנה, פיקסל.

  • הטלפון החדש יקר יותר, נכון?

  • נכון, וזו הסיבה שחלק מכם לא קנו טלפון חדש.

  • למה המחיר גבוה יותר?

  • האם ידעת?

  • לא.

  • RAM.

  • זיכרון RAM, כן, כן.

  • כן, זיכרון RAM הוא באמת האתגר של Android ושלנו, המפתחים.

  • לכן אני כאן היום כדי לספר איך אפשר להתגבר על האתגר של הזיכרון, של ה-RAM.

  • בכנסים Google I/O ו-Google I/O Connect, היו לנו כמה מפגשים בנושא זיכרון, החל מהאופן שבו מערכת ההפעלה מקצה את הזיכרון, איך אפשר ליצור פרופיל של האפליקציה, איך אפשר לבצע אופטימיזציה של הקוד באמצעות R8, וכמובן, דיברתי קצת על Bitmap באותו זמן.

  • אפשר לראות את התמונות שלי בצילום המסך.

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

  • אתם בטח שואלים את עצמכם, למה? למה Bitmap?

  • למה כדאי להתמקד בזיכרון של מפת הסיביות (bitmap)?

  • כי תמונה אחת שווה יותר מאלף מילים.

  • זה נכון, ואסביר לך למה.

  • במסך של טלפון, נניח בגרסה של Pixel 7,‏ Pixel 9 או Pixel 11 Pro, רזולוציית המסך היא בערך 2,500 על 1,200 או 1,400.

  • לכן, לכל פיקסל יש עומק צבע של 32 ביט.

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

  • אז, אבל, כמו, מילה במערכת ההפעלה היא כמו 64 סיביות, זה אומר שהיא 8 בייט.

  • תמונה אחת שווה אלף מילים, אבל היא שוקלת רק 8MB.

  • כן, 8K.

  • אז בהחלט תמונה אחת שווה יותר מאלף מילים.

  • כן, זה חשוב.

  • תמונה אחת כבר בגודל 15MB.

  • מה אם יש באפליקציה 10 או 100 תמונות?

  • זה מספר גדול.

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

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

  • זה לא משנה מה גודל הקובץ, או אם הוא בפורמט JPEG או PNG.

  • לא, הוא יפענח את הנתונים וישתמש בזיכרון.

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

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

  • זה יגרום לממשק המשתמש להיראות לא תקין.

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

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

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

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

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

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

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

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

  • אחר כך אראה לכם את העיקרון של אופטימיזציה של השימוש בזיכרון של מפת הסיביות (bitmap).

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

תקני האיכות של Google Play ובעיות נפוצות

  • Fung Lam

    קודם כול, דרישת האיכות החדשה של Google Play.

  • באיזו תדירות אתם נכנסים ל-Google Play Console?

  • כל חודש?

  • כל כמה שבועות?

  • אף פעם?

  • הצחוק הזה אומר כן, נכון?

  • לא, נכון?

  • לא.

  • האם ביקרת בקטע "תפקוד האפליקציה"?

  • כן, כן.

  • כן, אז אתה בהחלט יודע משהו לפני שאני מכין את זה.

  • לכן, ב-Google Play יש דרישות איכות חדשות לגבי סף הביצועים.

  • אנחנו ב-Android וב-Play רוצים שהמשתמשים ייהנו מאפליקציות באיכות טובה.

  • אז גם הסף הזה הוא משהו שאנחנו מנסים להביא בצורה הכי טובה למשתמש הקצה.

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

  • אחרת, יכול להיות שהחשיפה של האפליקציה בחנות Play תצטמצם, או משהו כזה".

  • האכיפה תתחיל בפברואר 2027 באפליקציות ובמשחקים.

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

  • לכן, חשוב להיכנס ל-Google Play Console ולבדוק: "מהו האחוזון ה-90 שלי היום?"

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

  • זו האנימציה.

  • "היי, אם נכנסים ל-Google Play Console, אפשר לראות את, כן, הנה "תפקוד האפליקציה".

  • יש חלק בזיכרון.

  • ואפשר לראות את האחוזון ה-90.

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

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

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

  • אבל באמת, אני לא יודע מה הבעיה.

  • רוצה לעזור?"

  • כן, בשביל זה אני כאן.

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

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

  • כן, גם אני זיהיתי היום בעיה נפוצה.

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

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

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

  • ולמען האמת, ככה אני קורא את המדריך של הטלוויזיה.

  • כדי לגלות את הבעיה הנפוצה ביותר בזיכרון מפת הסיביות (bitmap) שקשורה להקצאת מפת סיביות בסביבה העסקית של Android, בדקתי כ-500 אפליקציות פופולריות ב-Android.

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

  • ואני בודק את האפליקציה ב-RAM שונה, גם במכשירים שונים, כמו מכשיר Android Go עם זיכרון של 2GB, מכשיר בסיסי עם 4GB, מכשיר בינוני עם 4GB,‏ 6GB עד 8GB, או מכשיר פרימיום מתקפל עם מסך ממש גדול, איכות מסך טובה מאוד וזיכרון של 16GB.

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

  • ככה הזיכרון השתנה במצבי פעילות שונים.

  • בעזרת הניתוח הזה, אנחנו מזהים יותר מ-15 בעיות נפוצות, החל מתמונות גדולות מדי ועד להגדלת המטמון ברקע.

  • זהו תיאור המקרה העיקרי שנשתף ונבחן היום.

מקרים לדוגמה: שינוי גודל ופענוח של מפות סיביות (bitmap)

  • Fung Lam

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

  • אם אתם כאן, כנראה שכולנו אוהבים את Android, נכון?

  • כולנו אוהבים את Android.

  • נניח שיש מישהו כזה, אחד מאיתנו שאוהב גם את Android, אבל הוא או היא לא מומחים בפיתוח ל-Android.

  • אז הוא יצר אפליקציה ל-Android להזמנת פסלונים של Android.

  • אבל יש כמה טעויות.

  • בואו נדבר על זה. יכול להיות שזו הטעות הראשונה.

  • "היי, בדף המוצר יש לנו פריסה של שתי עמודות שמוצגות בהן דמויות שונות של Android.

  • בכל עמודה, התמונה היא בגודל של כ-400x400 פיקסלים.

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

  • התמונה שמוצגת היא בגודל 3,000 x‏ 3,000 פיקסלים.

  • כן, כי כמו המפתח, "היי, Android כל כך יפה, כל כך מדהים.

  • אני רוצה להציג למשתמש את התוצאות הכי טובות".

  • אבל בעצם לא, כי הוא מוגבל על ידי המימד של תצוגת התמונה.

  • לכן, גם אם תורידו את התמונה הגדולה, היא לא תוצג למשתמש בצורה הטובה ביותר.

  • בנוסף, התמונה הגדולה הזו צרכה יותר זיכרון.

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

  • איך אנחנו יכולים לעזור למפתח הזה לתקן את הבעיה?

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

  • באמצעות בקשת HTTP, רשת CDN יכולה לטפל במשהו.

  • לכן, גם אם בצד השרת מתארחת רק תמונה אחת, אפשר להשתמש ב-CDN כדי לשלוח למכשיר תמונה בגודל שונה.

  • אז דבר ראשון, כשמבקשים את התמונה, כדאי להשתמש ב-CDN, כדי לבקש את התמונה בגודל של ImageView.

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

  • לכן, כשהתמונה נטענת לזיכרון, נטענת תמונה קטנה יותר ולא התמונה ברזולוציה המלאה.

  • הנה דוגמת קוד ל-Coil.

    // 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
    val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"
    
    // 2. Enforce exact downsampling + Hardware Bitmap config
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(sizedUrl)
            .size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
            .precision(Precision.EXACT)      // Downsample to exact card size
            .bitmapConfig(Bitmap.Config.HARDWARE)
            .build(),
        contentDescription = product.title
    )
  • אפשר להעביר את הגודל כניסוח בצהוב.

  • כן, אפשר להעביר את הגודל כ-HTTP בבקשת HTTP.

  • ואז, כאן, בקוד המודגש, אתה רואה את ההדגשה? כן, כן, אל תשתמש ב-Size.ORIGINAL.

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

  • משתמשים בדיוק כדי להקטין את התמונה לגודל המדויק.

  • אם אפשר, כדאי להשתמש ב-Bitmap.Config.HARDWARE, שגם משתמש בפחות זיכרון כי התמונה תאוחסן ישירות ב-VRAM.

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

  • אבל יש כאן עוד בעיה.

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

  • איך אפשר לשפר את זה?

  • כן.

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

  • התמונות שלא מוצגות במסך לא ייטענו, או שהטעינה שלהן תבוטל.

  • כך הוא ישתמש בפחות זיכרון.

  • באופן דומה, במקרה הזה, זה חסך כ-60% מהזיכרון.

  • כמובן שאם חותכים למלבן קטן יותר, חוסכים יותר.

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

  • אבל לא, אתם לא חייבים לעשות את זה.

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

  • לכן, רק האזור הגלוי הזה ייטען לזיכרון.

  • איך עושים את זה?

  • יש API שנקרא BitmapRegionDecoder, שעוזר לטעון אזור ספציפי בתמונה.

  • וכמובן, יש גם כמה כלים וספריות שיכולים לעזור לכם.

  • אפשר לחפש אותו ב-GitHub.

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

  • אתם יכולים להשתמש בהם כחומר עזר.

  • בנוסף, אנחנו בני אדם, ואנחנו עושים טעויות.

  • גם ה-AI עלול לטעות.

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

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

    // Query active window bounds instead of hardcoding 1080 / 1920
    val bounds = WindowMetricsCalculator.getOrCreate()
        .computeCurrentWindowMetrics(activity).bounds
    
    // Clamp target dimensions so Bitmap NEVER exceeds the active window
    val safeWidth  = minOf(requestedWidthPx,  bounds.width())
    val safeHeight = minOf(requestedHeightPx, bounds.height())
    
    val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)
  • אז עם הפונקציה הזו, בכל פעם שאני טוען תמונה, היא אף פעם לא תחרוג מהגבולות.

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

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

ניהול הזיכרון, הכפילויות והמטמון

  • Fung Lam

    ושוב, להגיד, היי, האפליקציה הזו, למעשה, לא מגיעה ממפתח אחד.

  • למעשה, הוא מיועד לכמה שותפי עריכה, ויכול להיות שהם משתמשים בכלים שונים.

  • לדוגמה, חלק מהשימוש הוא, אה, סליחה.

  • אני רוצה להתחיל מחדש.

  • אותו דבר לגבי האפליקציה הזו.

  • היי, אני בדף המוצר, יש לי רשימה של מוצרים וראיתי את התמונה הזו, אני מוסיף אותה לעגלת הקניות.

  • אחרי כמה דקות, אני עובר לתשלום, והיי, אותה תמונה מופיעה שוב.

  • אבל האם זו באמת אותה תמונה או שבעצם מדובר בשני אובייקטים שונים בזיכרון?

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

  • אבל יש גם הרבה תמונות כפולות בזיכרון.

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

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

  • ב-Android Studio, אתם יכולים להשתמש ב-Memory Profiler כדי לבדוק אם יש באפליקציה מפת סיביות כפולה.

  • דיברתי על זה במפגש שלי ב-Google I/O, אז אתם יכולים לחזור ל-YouTube ולצפות בסרטון.

  • אם אתם לא משתמשים ב-Android Studio, יש גם פקודת adb שמאפשרת לצלם את תמונת המצב של הזיכרון, ואז אתם יכולים להשתמש בשפת SQL של תרשים ה-heap של Perfetto כדי לבדוק אם יש באפליקציה שלכם מפת סיביות כפולה.

  • אז זו דוגמה לקוד ב-Coil, איך מוסיפים מפתח לשמירת Bitmap במטמון.

    // Strip volatile query tokens (?from=list / ?sig=...) for cache key
    val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))
    
    // On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
    AsyncImage(
        model = ImageRequest.Builder(context)
            .data(productUrl)
            .placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
            .memoryCacheKey(canonicalKey)
            .diskCacheKey(productUrl.substringBefore("?"))
            .build(),
        contentDescription = product.title
    )
  • ועכשיו, כן.

  • אני חוזר לדוגמה הקודמת.

  • היי, לאפליקציה שלי יש מפתח אחר.

  • בחלק מהאפליקציה נעשה שימוש ב-Views, אז כאן משתמשים ב-Glide ככלי לטעינת תמונות.

  • חלק מהם משתמשים ב-Coil כי הם משתמשים ב-Compose.

  • אז זה מה שהזכרת עכשיו, טוען התמונות עוזר לנו לשמור במטמון, אז יש בעיה אם יש לי יותר מטוען תמונות אחד?

  • ברור, משהו לא בסדר.

  • לכן, כברירת מחדל, טוען התמונות ישתמש ב-X% מגודל הזיכרון של הערימה כגודל ברירת המחדל של המטמון.

  • לכן, אם יש לכם טוען תמונות אחד, הוא משתמש ב-X% מהזיכרון.

  • אם יש לכם שני טועני תמונות, הם יצרכו פי 2 זיכרון.

  • אם יש לכם שלושה טועני תמונות, העלות תהיה פי 3.

  • ואם טוען התמונות לא משתף את אותו מאגר, יקרה אותו דבר.

  • לדוגמה, בדף המוצר אני טוען תמונה באמצעות Glide, ואז בדף התשלום אותה תמונה תיטען באמצעות Coil.

  • בכל זאת, בזיכרון יהיו שתי תמונות.

  • זה לא טוב.

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

  • אבל אם זה דורש הרבה עבודת הנדסה, לא אוכל לעשות את זה.

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

    val totalBudgetBytes = computeAdaptiveCacheBytes(context)
    val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)
    
    // Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
    val loaderABudget = (totalBudgetBytes * 0.70).toInt()
    val loaderBBudget = (totalBudgetBytes * 0.30).toInt()
    
    // Approach B (Simple Fix without stats): Divide by number of loaders!
    val simpleSplitBudget = totalBudgetBytes / numberOfLoaders
    
    MemoryCache.Builder()
        .maxSizeBytes(simpleSplitBudget)
        .build()
  • אפשר להגדיר את loaderABudget ל-70% ואת loaderBBudget ל-30%, אז זו עוד דרך.

  • אם אתם לא יודעים כמה תמונות כל טוען יטען, עדיין עדיף פשוט לחלק.

  • היי, יש לי שני טועני תמונות, חלקי ב-2; שלושה טועני תמונות, חלקי ב-3 במקום להשתמש בערך ברירת המחדל כי זה יגרום לשימוש יתר בזיכרון.

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

  • אז בדרך כלל, אנחנו יכולים להגיד, בואו נשמור במטמון בערך 50 תמונות או 50MB של תמונות.

  • אבל מה קורה אם האפליקציה עוברת לרקע?

  • אם האפליקציה פועלת ברקע, יכול להיות שהמשתמש לא גולש.

  • אם המשתמש לא גולש, האם עדיין צריך לשמור את הכמות הזו של תמונות במטמון בזיכרון?

  • לא, אנחנו צריכים לשחרר אותו.

  • כן, אנחנו צריכים לכבד את הקריאה החוזרת (callback) של onTrimMemory.

  • כן, למעשה, הרבה אפליקציות לא מטמיעות או לא מכבדות את פונקציית ה-callback של onTrimMemory.

  • ב-onTrimMemory callback, יש שני אותות שצריך לטפל בהם.

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

  • ואנחנו יכולים לצפות לזה, "היי, המשתמש לא משתמש באפליקציה שלך.

  • צריך לשחרר את המשאבים למערכת ההפעלה".

  • לכן, כדאי לצמצם את גודל המטמון.

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

  • בשלב הזה, צריך לשחרר את הזיכרון כמה שיותר, כמעט 100%.

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

  • כי זה כמו, "היי, הם רוצים זיכרון, אתה נותן להם, ואז מערכת ההפעלה מטפלת בזה".

  • אם לא, למערכת ההפעלה אין ברירה, היא חייבת לעשות משהו.

מניעת גידול בזיכרון ברקע

  • Fung Lam

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

  • אבל ראיתי אפליקציות שבאמת משתמשות ביותר זיכרון ברקע.

  • למה?

  • בשביל זה אני עושה את המחקר.

  • ניסיתי לברר למה.

  • ואחת מהסיבות היא, "היי, באפליקציה שלהם, יש להם אנימציה או קרוסלה.

  • לדוגמה, הן מורידות תמונה חדשה כל 15 שניות ומציגות אותה למשתמש".

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

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

  • אז זה משהו שהאפליקציה לא אמורה לעשות.

  • בהחלט.

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

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

    override fun onStop() {
        super.onStop()
        // 1. Stop animations so they don't decode next frames in background!
        bannerAnimationView.cancelAnimation()
        animatedImageDrawable?.stop()
    
        // 2. Cancel next-image buffering / prefetch queues!
        nextImagesBufferingJob?.cancel()
        Glide.with(this).pauseRequests()
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        bannerAnimationView.clearAnimation() // Release resources!
    }

מסגרת 3 השלבים לשמירה על בריאות הזיכרון

  • Fung Lam

    אני יודע/ת שדיברתי איתך על הרבה פניות, אבל אמרתי "יש לי יותר מ-15 פניות".

  • אני יודע שאם אשתף איתך את כל המקרים, יכול להיות שתשכח את הכול.

  • אתם פשוט תחזרו הביתה ותחפשו ב-Google, "היי, איך פותרים בעיות בזיכרון?"

  • אז אני עוזר לכם לזכור אותה בקלות.

  • בהחלט.

  • אקשר את זה למסגרת 3R מוכרת מאוד כדי לעזור לך לזכור אותם.

  • אני חושב שכולם צריכים להכיר את מסגרת שלושת ה-R.

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

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

  • אנחנו מבצעים אופטימיזציה של עומק הצבע, או משתמשים במפת סיביות (bitmap) של החומרה אם אפשר.

  • אם אתם לא משתמשים ב-CDN, כדאי לנסות להשתמש בו.

  • כשאנחנו מקבלים את התמונה מהשרת, אנחנו מקבלים את התמונה שהכי מתאימה לגודל.

  • שימוש חוזר – אנחנו משתמשים במטמון כדי שהתמונה שנטענת בזיכרון תהיה בשימוש חוזר, ולא תהיה לנו תמונת מפת סיביות (bitmap) כפולה בזיכרון.

  • וכמובן, נסו להימנע משימוש בכמה טועני תמונות בקוד, כי זה פחות יעיל.

  • צמצום, שימוש חוזר, ומה השלישי?

  • האם ידעת?

  • מיחזור.

  • לנסות שוב?

  • מיחזור, כן, מיחזור.

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

  • כן, למחזר, כן.

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

  • יש שם דברים כמו מידע על מיחזור.

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

  • כן, נסה לטפל בבקשה הזו.

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

  • לכן, צמצמו, השתמשו מחדש ומחזרו.

  • קל לזכור, נכון?

ערכת כלי אבחון למפתחים וניתוח באמצעות AI

  • Fung Lam

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

  • איך משתמשים בערכות כלים שונות כדי לזהות ולמדוד את בעיית הזיכרון הזו ביעילות?

  • הראשון הוא Google Play Console, תפקוד האפליקציה ו-Reporting API.

  • כן, צריך לבדוק את האחוזון ה-90 ב-Google Play Console.

  • איפה אתם נמצאים עכשיו?

  • אם אתם לא נכנסים לדף האינטרנט, אתם יכולים להשתמש ב-Reporting API.

  • עדיין אפשר להשתמש ב-API כדי לקבל את הערך.

  • כן, צריך לוודא שהסף הוא 200MB ו-400MB.

  • חשוב לוודא שלא חרגת מהסף.

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

    adb shell dumpsys meminfo <pkg>  # watch Native Heap!
    adb shell am send-trim-memory <pkg> UI_HIDDEN
  • אם אתם משתמשים ב-Android Studio, מצוין.

  • אבל הוא תמיד חבר טוב בשבילך.

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

  • וכמובן, אנחנו מדברים על AI עם Gemini ב-Android Studio או על מיומנויות אחרות שזמינות ב-Android.

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

  • האם התמונה נטענת באמצעות Glide או באמצעות Coil?

  • האם יש עוד דליפות זיכרון באפליקציה שלי?

  • אני יודע שחלק מכם אומרים, "היי, בעצם אני כבר לא משתמש ב-IDE.

  • אני מהנדס פרומפטים.

  • אני משתמש, כן, אני פשוט שולח הנחיה עם סוכן AI כדי ליצור את הקוד".

  • כן, אני יודע.

  • בזמני הפנוי אני גם מתכנת בשיטת Vibe coding.

  • אז אני משתף אתכם בדוגמה הזו.

  • כן, זה רק חלק מההנחיה שבה אני משתמש למחקר על 500 האפליקציות.

  • כן, רק חלק ממנו.

  • אתם יודעים שהמסך לא גדול.

  • אם הייתי מוסיף עוד, בקושי הייתם רואים את זה.

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

  • ואני יכול להוסיף עוד משפט, "Please also provide me the code fix for my code" (תספק לי גם את התיקון לקוד שלי).

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

  • לא למדתי על מערכות הפעלה או על ארכיטקטורה של מערכות מחשב במסגרת לימודי מדעי המחשב באוניברסיטה.

  • אבל למעשה, זה לא כל כך מפחיד, זה לא כל כך קשה".

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

  • כדאי לנסות.

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

  • לכן כדאי להגיע להרצאה שלי ולצפות בסרטון שלנו ב-YouTube.

נקודות חשובות וסיכום

  • Fung Lam

    לסיכום, יש לנו כמה מסקנות.

  • קודם כל, צמצום.

  • חשוב לוודא שגודל התמונה מתאים.

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

  • כשאפשר, השתמשו ב-bitmapConfig או ב-RGB_565 של החומרה.

  • שימוש חוזר – חשוב לוודא שיש לכם אסטרטגיית מטמון, ולהשתמש בטוען תמונות כדי לצמצם את מספר מפות הסיביות (bitmap) הכפולות בזיכרון.

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

  • ואנחנו לא עושים שימוש חוזר, אנחנו ממחזרים.

  • בהחלט.

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

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

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

  • ולהפסיק את ניצול המשאבים, בין אם מדובר במשאבי Bitmap, במשאבי חשמל או במשאבי מחשוב.

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

  • ולבסוף, חשוב לעקוב אחרי הביצועים.

  • כדאי להיכנס ל-Google Play Console באופן קבוע.

  • חשוב לוודא שזו מרפאה.

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

  • זה גם יעזור לכם לזהות בעיות בשלב מוקדם.

  • בהחלט.

  • כדי ליצור פרופיל באופן מקומי, אפשר להשתמש בכלי Memory Profiler של Android Studio.

  • אנחנו יכולים גם להשתמש בפקודת ADB.

  • יש לנו גם מיומנות ב-Android.

  • אבל אם אתם רוצים לראות את הנתונים הסטטיסטיים של המשתמשים בעולם האמיתי, Google Play Console הוא המקום בשבילכם.

  • יש גם את Google Play Reporting API, שמאפשר לקבל את הנתונים באמצעות API בלי להתחבר ל-Play Console.

  • כמו תמיד, מידע נוסף זמין בכתובת developer.android.com/memory.

  • צמצום, שימוש חוזר ומחזור.

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

  • תודה ובהצלחה.

  • בקרוב אהיה זמין בחוץ, אז אם יש לכם שאלות, אתם מוזמנים לבוא.

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

קוד

קטעי קוד

קטע קוד ב-Kotlin בדקה 11:50 בסרטון "Case studies: Sizing and decoding Bitmaps"
// 1. Ask CDN for the exact UI card dimensions (e.g. 540x675)
val sizedUrl = "$cdnBaseUrl?w=$cardWidthPx&h=$cardHeightPx&fmt=webp"

// 2. Enforce exact downsampling + Hardware Bitmap config
AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data(sizedUrl)
        .size(cardWidthPx, cardHeightPx) // Never use Size.ORIGINAL!
        .precision(Precision.EXACT)      // Downsample to exact card size
        .bitmapConfig(Bitmap.Config.HARDWARE)
        .build(),
    contentDescription = product.title
)
קטע קוד ב-Kotlin בדקה 14:45 בסרטון "Case studies: Sizing and decoding Bitmaps"
// Query active window bounds instead of hardcoding 1080 / 1920
val bounds = WindowMetricsCalculator.getOrCreate()
    .computeCurrentWindowMetrics(activity).bounds

// Clamp target dimensions so Bitmap NEVER exceeds the active window
val safeWidth  = minOf(requestedWidthPx,  bounds.width())
val safeHeight = minOf(requestedHeightPx, bounds.height())

val bitmap = Bitmap.createBitmap(safeWidth, safeHeight, config)
קטע קוד ב-Kotlin בדקה 16:51 בסרטון "ניהול זיכרון, כפילויות ומטמון"
// Strip volatile query tokens (?from=list / ?sig=...) for cache key
val canonicalKey = MemoryCache.Key(productUrl.substringBefore("?"))

// On Checkout Screen: reuse the 200x200 Bitmap already loaded in the List!
AsyncImage(
    model = ImageRequest.Builder(context)
        .data(productUrl)
        .placeholderMemoryCacheKey(canonicalKey) // Instant zero-decode reuse!
        .memoryCacheKey(canonicalKey)
        .diskCacheKey(productUrl.substringBefore("?"))
        .build(),
    contentDescription = product.title
)
קטע קוד ב-Kotlin בדקה 18:17 בסרטון "ניהול זיכרון, כפילויות ומטמון"
val totalBudgetBytes = computeAdaptiveCacheBytes(context)
val numberOfLoaders = 2 // e.g., Team A (Glide) + Team B (Coil)

// Approach A (With Stats): Weight by image traffic (70% Team A / 30% Team B)
val loaderABudget = (totalBudgetBytes * 0.70).toInt()
val loaderBBudget = (totalBudgetBytes * 0.30).toInt()

// Approach B (Simple Fix without stats): Divide by number of loaders!
val simpleSplitBudget = totalBudgetBytes / numberOfLoaders

MemoryCache.Builder()
    .maxSizeBytes(simpleSplitBudget)
    .build()
קטע קוד של Kotlin בדקה 22:05 בסרטון Preventing background memory growth
override fun onStop() {
    super.onStop()
    // 1. Stop animations so they don't decode next frames in background!
    bannerAnimationView.cancelAnimation()
    animatedImageDrawable?.stop()

    // 2. Cancel next-image buffering / prefetch queues!
    nextImagesBufferingJob?.cancel()
    Glide.with(this).pauseRequests()
}

override fun onDestroyView() {
    super.onDestroyView()
    bannerAnimationView.clearAnimation() // Release resources!
}
קטע קוד של Shell בדקה 25:17 בסרטון "Developer diagnostic toolkit & AI analysis"
adb shell dumpsys meminfo <pkg>  # watch Native Heap!
adb shell am send-trim-memory <pkg> UI_HIDDEN