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

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

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

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

הקטנת רזולוציית התמונות

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

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

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

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

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

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

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

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

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

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

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

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

אל תגדירו ריפוד ישירות

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

בחירת הפורמט המתאים של Pixel

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

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

מומלץ להשתמש בווקטורים כשאפשר

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

שחררו מפות סיביות והשתמשו בהן מחדש כשאפשר

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

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

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

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

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

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

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

חיפוש מפות סיביות מיותרות

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

כשמשתמשים ב-ImageBitmap, צריך להפעיל את prepareToDraw לפני השרטוט

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

עדיף להעביר Int DrawableRes או כתובת URL כפרמטרים ל-composable במקום Painter

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

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

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

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

}