אם לא תפעלו בזהירות, עבודה עם תמונות עלולה לגרום לבעיות בביצועים. אפילו גרפיקה קטנה בפורמט דחוס כמו JPG או PNG יכולה להפוך למפת סיביות גדולה כשהיא מפוענחת לצורך הצגה. אם לא תשתמשו בגרפיקה בצורה יעילה, עלולות להיווצר בעיות בזיכרון שיפגעו בביצועים של האפליקציה ושל אפליקציות אחרות במכשיר. כדי להבטיח שהאפליקציה תפעל בצורה אופטימלית, מומלץ לפעול לפי השיטות המומלצות הבאות.
שימוש בספריות לטעינת תמונות
כדי לשפר את היעילות של האפליקציה, אפשר להשתמש בספריות לטעינת תמונות כמו Coil (לפרויקטים שמתבססים על Kotlin) או Glide (לפרויקטים של Java). הספריות האלה מצמצמות את השימוש בזיכרון של האפליקציה באמצעות פעולות כמו שמירת תמונות במטמון, דגימת גרפיקה כשצריך ושימוש חוזר באובייקטים גרפיים.
הקטנת הדגימה של תמונות
חשוב להשתמש בגודל התמונה המתאים לצרכים שלכם. מומלץ להימנע מטעינת תמונה גדולה ברזולוציה גבוהה לתוך מאגר קטן (כמו תמונה ממוזערת). במקום זאת, כדאי להשתמש בדגימת יתר כדי להקטין את התמונה לפני שמפענחים אותה בזיכרון.
דגימת יתר בצד הלקוח
ספריות לטעינת תמונות כמו Coil ו-Glide מטפלות בשבירת דגימה בשבילכם באופן אוטומטי. אפשר להגדיר את אסטרטגיות הדגימה שלהם באמצעות ImageLoader (ל-Coil) או DownsampleStrategy (ל-Glide). אם אתם מנהלים מפות סיביות (bitmap) באופן ידני, אתם יכולים להשתמש ב-inSampleSize כדי לפענח קוד גרסה קטנה יותר. כדי לעשות זאת בצורה בטוחה, צריך קודם להגדיר את inJustDecodeBounds ל-true כדי לקרוא את מידות התמונה בלי להקצות זיכרון, לחשב את גודל הדגימה, להגדיר את inSampleSize לערך הזה, להגדיר את inJustDecodeBounds ל-false ואז לפענח את התמונה.
העדפה של שינוי גודל בצד השרת
במקרים שבהם אפשר, כדאי לבקש את הממדים המדויקים של התמונה שאתם צריכים ישירות מהשרת העורפי. כך מצמצמים את השימוש ברשת ואת נפח המטמון בדיסק, וגם את השימוש בזיכרון, כי לא צריך להקצות זיכרון לשינוי הגודל של התמונות במכשיר.
אפשר להגדיר ספריות כך שיצורפו באופן דינמי לכתובת ה-URL של התמונה את גודל התצוגה של היעד. לדוגמה, בספריית Coil אפשר לעשות את זה באמצעות interceptors בהתאמה אישית, ובספריית Glide אפשר לעשות את זה באמצעות טועני מודלים בהתאמה אישית (כמו BaseGlideUrlLoader).
הימנעות משימוש בגדלים לא מוגבלים של פריסות
כדי שמטעני תמונות יבצעו דגימת יתר (בצד הלקוח או בצד השרת) בצורה יעילה, הם צריכים לדעת את גודל היעד לפני ביצוע הבקשה.
מומלץ להימנע משימוש ב-wrapContentSize או להשאיר את המימדים ללא הגבלה ברכיבים הניתנים להרכבה שמעלים תמונות מרחוק. אם הספריות האלה לא יכולות להסיק את הגבולות של היעד, הן חוזרות להעלאה של התמונה המקורית בגודל מלא. זה עלול להוביל להעלאה של תמונה גדולה בהרבה מהנדרש, ולהגדיל את השימוש בזיכרון ואת זמן האחזור.
במקום זאת, צריך להגדיר מימדים מפורשים לרכיב התמונה (לדוגמה, באמצעות 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) {
}
מומלץ עבורך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- ImageBitmap לעומת ImageVector {:#bitmap-vs-vector}
- שמירת מצב ממשק המשתמש בפיתוח נייטיב
- שלבים ב-Jetpack Compose