אופטימיזציה של תמונות מפת סיביות

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

שימוש בספריות לטעינת תמונות

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

הקטנת דגימה של תמונות

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

דגימת יתר בצד הלקוח

ספריות לטעינת תמונות כמו Coil ו-Glide מטפלות בהורדה של קצב הדגימה באופן אוטומטי. אפשר להגדיר את אסטרטגיות הדגימה שלהם באמצעות ImageLoader (ל-Coil) או DownsampleStrategy (ל-Glide). אם אתם מנהלים מפות סיביות באופן ידני, אתם יכולים להשתמש ב-inSampleSize כדי לפענח גרסה קטנה יותר. כדי לעשות זאת בצורה בטוחה, צריך קודם להגדיר את inJustDecodeBounds ל-true כדי לקרוא את מידות התמונה בלי להקצות זיכרון, לחשב את גודל הדגימה, להגדיר את inSampleSize לערך הזה, להגדיר את inJustDecodeBounds ל-false ואז לפענח את התמונה.

העדפה של שינוי גודל בצד השרת

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

אפשר להגדיר את הספריות כך שיצורף באופן דינמי לכתובת ה-URL של התמונה גודל התצוגה המקדימה של התמונה. לדוגמה, בספריית Coil אפשר לעשות זאת באמצעות interceptors מותאמים אישית, ובספריית Glide יש תמיכה בכך באמצעות טועני מודלים מותאמים אישית (כמו BaseGlideUrlLoader).

הימנעות משימוש בגדלים לא מוגבלים של פריסות

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

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

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

אספקת משאבים חלופיים לגדלים שונים של מסכים

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

לא להחיל מרווח פנימי ישירות

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

בחירת פורמט הפיקסלים הנכון

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

ב-Glide אפשר להגדיר את זה באמצעות DecodeFormat. ב-Coil, אפשר להשתמש בנכס bitmapConfig.

שימוש בווקטורים כשאפשר

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

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

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

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

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

טיפים וטריקים נוספים

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

לא מומלץ לארוז תמונות גדולות עם קובץ ה-AAB או ה-APK

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

איתור מפות סיביות מיותרות

אם יש לכם כמה עותקים של אותה תמונה, הזיכרון מנוצל בצורה לא יעילה. אפשר להשתמש בכלי לניתוח ביצועים (profiler) ב-Android Studio כדי לזהות גרפיקה מיותרת. כדי לצלם תמונת מצב של הזיכרון ולסנן את התוצאות, בוחרים בהגדרה duplicate bitmaps בכלי לניתוח תמונת מצב של הזיכרון.

כשמשתמשים ב-ImageBitmap, מתקשרים אל prepareToDraw לפני שמציירים

כשמשתמשים ב-ImageBitmap, כדי להתחיל את תהליך ההעלאה של הטקסטורה ל-GPU, צריך לקרוא ל-ImageBitmap#prepareToDraw() לפני שמציירים אותה בפועל. כך המעבד הגרפי יכול להכין את המרקם ולשפר את הביצועים של הצגת התמונה על המסך. רוב הספריות לטעינת תמונות כבר מבצעות את האופטימיזציה הזו, אבל אם אתם עובדים עם המחלקה ImageBitmap בעצמכם, כדאי לזכור את זה.

מומלץ להעביר Int DrawableRes או כתובת URL כפרמטרים לרכיב שאפשר להרכיב במקום Painter

בגלל המורכבות של העבודה עם תמונות (לדוגמה, כתיבת פונקציית שוויון עבור Bitmaps תהיה יקרה מבחינת חישובים), ה-API של Painter לא מסומן במפורש כ-API יציב באמצעות ההערה @Stable. מחלקות לא יציבות עלולות להוביל להרכבות מחדש מיותרות, כי קומפיילר לא יכול להסיק בקלות אם הנתונים השתנו.

לכן, מומלץ להעביר כתובת URL או מזהה של משאב drawable כפרמטרים לפונקציה הניתנת להרכבה, במקום להעביר Painter כפרמטר.

// Prefer this:
@Composable
fun MyImage(url: String) {

}
// Over this:
@Composable
fun MyImage(painter: Painter) {

}