بار کردن کارآمد نقشه‌های بیتی بزرگ

توجه: چندین کتابخانه وجود دارد که از روال‌های مطلوب برای بار کردن تصاویر پیروی می‌کنند. می‌توانید از این کتابخانه‌ها در برنامه‌تان استفاده کنید تا تصاویر را به بهینه‌ترین شکل بار کنید. کتابخانه Glide را توصیه می‌کنیم که تصاویر را در سریع‌ترین زمان ممکن و به‌صورت روان بار می‌کند و نمایش می‌دهد. دیگر کتابخانه‌های محبوب بار کردن تصویر عبارتند از Picasso از Square،‏ Coil از Instacart، و Fresco از Facebook. این کتابخانه‌ها اکثر کارهای پیچیده مرتبط با نگاشت‌های بیتی و انواع دیگر تصاویر را در Android ساده می‌کنند.

تصاویر در هر شکل و اندازه‌ای وجود دارند. در بسیاری از موارد، این اهداف بزرگ‌تر از اندازه موردنیاز برای میانای کاربر (UI) برنامه معمولی هستند. برای مثال، برنامه «گالری» سیستم عکس‌های گرفته‌شده با دوربین دستگاه Android شما را نمایش می‌دهد که معمولاً وضوح بسیار بالاتری نسبت به تراکم صفحه‌نمایش دستگاه شما دارند.

با توجه به اینکه با حافظه محدودی کار می‌کنید، در حالت ایده‌آل فقط می‌خواهید نسخه با وضوح پایین‌تر را در حافظه بارگیری کنید. نسخه با وضوح پایین‌تر باید با اندازه مؤلفه میانای کاربری که آن را نمایش می‌دهد مطابقت داشته باشد. تصویر با وضوح بالاتر هیچ مزیت قابل‌مشاهده‌ای ارائه نمی‌دهد، اما همچنان حافظه ارزشمندی را اشغال می‌کند و به‌دلیل مقیاس‌بندی اضافی درحین اجرا، سربار عملکرد اضافی ایجاد می‌کند.

این درس شما را در رمزگشایی نقشه‌های بیتی بزرگ بدون فراتر رفتن از حد مجاز حافظه هر برنامه با بار کردن نسخه کوچک‌تر نمونه‌برداری‌شده در حافظه راهنمایی می‌کند.

خواندن ابعاد و نوع نقشه بیتی

کلاس BitmapFactory چندین روش رمزگشایی (decodeByteArray()،‏ decodeFile()،‏ decodeResource()، و غیره) برای ایجاد Bitmap از منابع مختلف ارائه می‌دهد. مناسب‌ترین روش رمزگشایی را براساس منبع داده تصویرتان انتخاب کنید. این روش‌ها تلاش می‌کنند حافظه را به نقشه بیتی ساخته‌شده اختصاص دهند و بنابراین به‌راحتی می‌توانند منجر به استثنای OutOfMemory شوند. هر نوع روش رمزگشایی دارای امضاهای اضافی است که به شما امکان می‌دهد گزینه‌های رمزگشایی را ازطریق کلاس BitmapFactory.Options مشخص کنید. تنظیم کردن ویژگی inJustDecodeBounds روی true هنگام رمزگشایی از تخصیص حافظه جلوگیری می‌کند، null را برای شیء نقشه بیتی برمی‌گرداند اما outWidth، outHeight، و outMimeType را تنظیم می‌کند. این تکنیک به شما امکان می‌دهد ابعاد و نوع داده‌های تصویر را قبل‌از ساخت (و تخصیص حافظه) نقشه بیتی بخوانید.

کاتلین

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

جاوا

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 یا مؤلفه میانای کاربری که تصویر باید در آن بار شود.
  • اندازه و تراکم صفحه‌نمایش دستگاه فعلی.

برای مثال، اگر تصویری با ابعاد ۱۰۲۴×۷۶۸ پیکسل درنهایت به‌صورت ریزعکس ۱۲۸×۹۶ پیکسلی در ImageView نمایش داده شود، بار کردن آن در حافظه ارزشی ندارد.

برای اینکه به رمزگشا بگویید تصویر را زیرنمونه‌برداری کند و نسخه کوچک‌تری را در حافظه بار کند، inSampleSize را در شیء BitmapFactory.Options روی true تنظیم کنید. برای مثال، تصویری با وضوح ۱۵۳۶×۲۰۴۸ که با inSampleSize ۴ رمزگشایی می‌شود، نگاشت بیتی تقریباً ۳۸۴×۵۱۲ تولید می‌کند. بار کردن این تصویر در حافظه به‌جای ۱۲ مگابایت برای تصویر کامل، ۰٫۷۵ مگابایت مصرف می‌کند (با فرض پیکربندی نقشه بیتی ARGB_8888). در اینجا روشی برای محاسبه مقدار اندازه نمونه که توان دو است براساس عرض و ارتفاع هدف ارائه شده است:

کاتلین

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
}

جاوا

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 رمزگشایی کنید:

کاتلین

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)
    }
}

جاوا

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 که ریزعکس ۱۰۰×۱۰۰ پیکسلی را نمایش می‌دهد آسان می‌کند، همان‌طور که در کد نمونه زیر نشان داده شده است:

کاتلین

imageView.setImageBitmap(
        decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)
)

جاوا

imageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

می‌توانید فرایند مشابهی را برای رمزگشایی نقشه‌های بیتی از منابع دیگر دنبال کنید، با جایگزین کردن روش مناسب BitmapFactory.decode* درصورت نیاز.