تحميل الصور النقطية الكبيرة بكفاءة

ملاحظة: هناك العديد من المكتبات التي تتبع أفضل الممارسات لتحميل الصور. يمكنك استخدام هذه المكتبات في تطبيقك لتحميل الصور بأفضل طريقة محسَّنة. ننصحك بما يلي: التمرير الذي يقوم بتحميل الصور وعرضها في أسرع وقت ممكن وسلاسة. تشمل مكتبات تحميل الصور الشائعة الأخرى مكتبة بيكاسو من Square، وCoil من Instacart، فريسكو من Facebook. تعمل هذه المكتبات على تبسيط معظم المهام المعقدة المرتبطة باستخدام الصور النقطية وأنواع أخرى من الصور على Android.

تأتي الصور بجميع الأشكال والأحجام. وفي كثير من الحالات تكون أكبر مما هو مطلوب واجهة مستخدم التطبيق. على سبيل المثال، يعرض النظام تطبيق المعرض الصور التي تم التقاطها باستخدام كاميرا أجهزة Android التي تكون عادةً أعلى دقة بكثير من الشاشة وكثافة الجهاز.

بما أنّك تعمل باستخدام ذاكرة محدودة، يُفضَّل تحميل درجة دقة أقل نسخة في الذاكرة. يجب أن يتطابق الإصدار ذي الدقة الأقل مع حجم مكوِّن واجهة المستخدم الذي يعرضها. لا تقدّم الصورة ذات الدقة الأعلى أي فائدة مرئية، ولكنها تظلّ صالحة مساحة من الذاكرة الثمينة وتتحمل نفقات إضافية في الأداء بسبب زيادة المساحة سريعًا والتحجيم.

يرشدك هذا الدرس إلى كيفية فك ترميز الصور النقطية الكبيرة بدون تجاوز عدد عناصر كل تطبيق الحد المخصص للذاكرة من خلال تحميل نسخة أصغر حجمًا مستندة إلى عينة فرعية في الذاكرة.

قراءة أبعاد الصورة النقطية ونوعها

توفّر الفئة BitmapFactory عدة طرق فك ترميز (decodeByteArray() أو decodeFile() أو decodeResource() أو غير ذلك) لإنشاء Bitmap من مصادر مختلفة. اختيار أنسب طريقة فك الترميز استنادًا إلى مصدر بيانات الصورة. تحاول هذه الطرق تخصيص ذاكرة للصورة النقطية التي تم إنشاؤها، وبالتالي يمكن أن يؤدي ذلك بسهولة إلى ظهور OutOfMemory . يحتوي كل نوع من طرق فك الترميز على توقيعات إضافية تتيح لك تحديد فك الترميز. الخيارات عبر فئة BitmapFactory.Options. ضبط السمة inJustDecodeBounds على true أثناء فك الترميز ستتجنّب تخصيص الذاكرة، وسيتم عرض null لكائن الصورة النقطية مع ضبط outWidth وoutHeight وoutMimeType. يتيح لك هذا الأسلوب قراءة ونوع بيانات الصورة قبل إنشاء (وتخصيص الذاكرة) صورة نقطية.

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 المستهدفة أو مكوّن واجهة المستخدم الذي تستخدمه الصورة هي التحميل إليه.
  • حجم شاشة الجهاز الحالي وكثافته

على سبيل المثال، لا يستحق الأمر تحميل صورة بحجم 1024×768 بكسل في الذاكرة إذا كانت ستتم في النهاية في صورة مصغّرة بحجم 128x96 بكسل في ImageView.

لتوجيه برنامج فك الترميز إلى إعداد عيّنة فرعية للصورة، وتحميل نسخة أصغر في الذاكرة، اضبط inSampleSize على true في عنصر BitmapFactory.Options. على سبيل المثال، صورة بدقة 2048x1536 فك ترميزه باستخدام inSampleSize من 4 ينتج عنها صورة نقطية بحجم 512×384 تقريبًا. يؤدي تحميل هذا الملف إلى الذاكرة إلى استخدام 0.75 ميغابايت بدلاً من 12 ميغابايت لحجم الملف صورة (بافتراض إعداد الصورة النقطية لـ 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 تعرض صورة مصغّرة بحجم 100×100 بكسل، كما هو موضّح في المثال التالي. الرمز:

Kotlin

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

Java

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

ويمكنك اتباع عملية مشابهة لفك ترميز الصور النقطية من مصادر أخرى، وذلك عن طريق استبدال BitmapFactory.decode* المناسبة حسب الحاجة.