הערה: יש כמה ספריות שנמצאות במעקב שיטות מומלצות לטעינת תמונות. אפשר להשתמש בספריות האלה באפליקציה כדי לטעון תמונות באופן האופטימלי. מומלץ החלקה שטוענת ומציגה תמונות במהירות ובצורה חלקה ככל האפשר. ספריות פופולריות אחרות לטעינת תמונות כוללות את Picasso מ-Square, Coil מ-Instacart, וגם פרסקו מ-Facebook. הספריות האלה מפשטות את רוב המשימות המורכבות עם מפות סיביות וסוגים אחרים של תמונות ב-Android.
יש תמונות בכל הצורות והגדלים. במקרים רבים הם גדולים יותר מהנדרש לחיפוש ממשק משתמש של אפליקציה. לדוגמה, אפליקציית המערכת 'גלריה' מציגה תמונות שצולמו באמצעות המצלמה של מכשירי Android, שבדרך כלל ברזולוציה גבוהה בהרבה מהמסך צפיפות של המכשיר.
מאחר שהזיכרון שלך מוגבל, רצוי לטעון רק רזולוציה נמוכה יותר שבזיכרון. הגרסה ברזולוציה נמוכה יותר צריכה להתאים לגודל הרכיב בממשק המשתמש מציג אותה. תמונה ברזולוציה גבוהה יותר לא מספקת כל תועלת, אבל עדיין לוקחת כתוצאה מכך לבצע התאמה לעומס (scaling).
השיעור הזה ילמד אתכם איך מפענחים מפות סיביות גדולות בלי לחרוג מהמגבלות לכל אפליקציה מגבלת זיכרון על ידי טעינת גרסה קטנה של תת-דגימה בזיכרון.
קריאת המאפיינים והסוג של מפת סיביות (bitmap)
המחלקה BitmapFactory
מספקת כמה שיטות פענוח (decodeByteArray()
, decodeFile()
, decodeResource()
וכו') ליצירת Bitmap
ממקורות שונים. בחירה
שיטת הפענוח המתאימה ביותר על סמך מקור הנתונים של התמונה. השיטות האלה מנסות
להקצות זיכרון למפת הביטים המורכבת של הפרויקט, כך שהוא יכול להוביל בקלותOutOfMemory
חריג. לכל סוג של שיטת פענוח יש חתימות נוספות שמאפשרות לך לציין פענוח
אפשרויות אחרות דרך הכיתה BitmapFactory.Options
. הגדרת המאפיין inJustDecodeBounds
לערך true
במהלך הפענוח
הפונקציה מונעת את הקצאת הזיכרון, ומחזירה את הערך null
לאובייקט מפת הסיביות, אבל מגדירה outWidth
, outHeight
ו-outMimeType
. הטכניקה הזו מאפשרת לקרוא
והסוג של נתוני התמונה לפני הבנייה (והקצאת הזיכרון) של
מפת סיביות (bitmap).
Kotlin
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeResource(resources, R.id.myimage, options) val imageHeight: Int = options.outHeight val imageWidth: Int = options.outWidth val imageType: String = options.outMimeType
Java
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;
כדי להימנע מחריגים מ-java.lang.OutOfMemory
, צריך לבדוק את המידות של מפת סיביות לפני
אלא אם כן סומכים על המקור שיספק לך נתוני תמונה בגודל שניתן לחזות
שמתאים בצורה נוחה לזיכרון הזמין.
טעינה בזיכרון של גרסה מותאמת לעומס
לאחר שהמידות של התמונה ידועות, ניתן להשתמש בהן כדי להחליט אם להציג את התמונה המלאה לזיכרון או לטעון גרסת משנה לדוגמה במקום זאת. הנה כמה גורמים: לשקול:
- השימוש המשוער בזיכרון של טעינת התמונה המלאה בזיכרון.
- כמות הזיכרון שאתה מוכן להשקיע כדי לטעון את התמונה הזו בהינתן כל זיכרון אחר בדרישות של האפליקציה שלך.
- המידות של היעד
ImageView
או רכיב ממשק המשתמש של התמונה שצריך לטעון אליו. - גודל המסך והצפיפות של המכשיר הנוכחי.
לדוגמה, לא כדאי לטעון לזיכרון תמונה בגודל 1024x768 פיקסלים אם בסופו של דבר
מוצגת בתמונה ממוזערת של 128x96 פיקסלים ב-ImageView
.
כדי להורות למפענח לבצע תת-דגימה של התמונה, תוך טעינת גרסה מוקטנת לזיכרון, צריך להגדיר את inSampleSize
לערך true
באובייקט BitmapFactory.Options
. לדוגמה, תמונה ברזולוציה של 2,048x1,536
מפוענח בעזרת inSampleSize
מתוך 4,
מפת סיביות של כ-512x384. הטעינה הזו לזיכרון משתמשת ב-0.75MB, במקום ב-12MB
תמונה (בהנחה שהתצורה של מפת סיביות (bitmap) של ARGB_8888
). הנה
שיטה לחישוב ערך של גודל מדגם שהוא חזקה של שתיים על בסיס רוחב יעד
גובה:
Kotlin
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { // Raw height and width of image val (height: Int, width: Int) = options.run { outHeight to outWidth } var inSampleSize = 1 if (height > reqHeight || width > reqWidth) { val halfHeight: Int = height / 2 val halfWidth: Int = width / 2 // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { inSampleSize *= 2 } } return inSampleSize }
Java
public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
הערה:המערכת מחשבת חזקה של שני ערכים כי המפענח משתמש
ערך סופי על ידי עיגול למטה אל החזקה הקרובה ביותר של שתיים, לפי המידע במסמכי התיעוד inSampleSize
.
כדי להשתמש בשיטה הזו, קודם צריך לפענח את הקוד כאשר inJustDecodeBounds
מוגדר לערך true
, ומעבירים את האפשרויות
ואז מפענחים שוב באמצעות הערך החדש של inSampleSize
ו-inJustDecodeBounds
מוגדר ל-false
:
Kotlin
fun decodeSampledBitmapFromResource( res: Resources, resId: Int, reqWidth: Int, reqHeight: Int ): Bitmap { // First decode with inJustDecodeBounds=true to check dimensions return BitmapFactory.Options().run { inJustDecodeBounds = true BitmapFactory.decodeResource(res, resId, this) // Calculate inSampleSize inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) // Decode bitmap with inSampleSize set inJustDecodeBounds = false BitmapFactory.decodeResource(res, resId, this) } }
Java
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
בעזרת השיטה הזו קל לטעון מפת סיביות בגודל גדול באופן שרירותי לתוך ImageView
שמציגה תמונה ממוזערת בגודל 100x100 פיקסלים, כמו בדוגמה הבאה
קוד:
Kotlin
imageView.setImageBitmap( decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100) )
Java
imageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
אפשר לבצע תהליך דומה כדי לפענח מפות סיביות ממקורות אחרים, על ידי החלפת הרכיב
שיטת BitmapFactory.decode*
המתאימה לפי הצורך.