טעינה יעילה של מפות סיביות גדולות

הערה: יש כמה ספריות שנמצאות במעקב שיטות מומלצות לטעינת תמונות. אפשר להשתמש בספריות האלה באפליקציה כדי לטעון תמונות באופן האופטימלי. מומלץ החלקה שטוענת ומציגה תמונות במהירות ובצורה חלקה ככל האפשר. ספריות פופולריות אחרות לטעינת תמונות כוללות את 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* המתאימה לפי הצורך.