ملاحظة: هناك العديد من المكتبات التي تتّبع أفضل الممارسات لتحميل الصور. يمكنك استخدام هذه المكتبات في تطبيقك لتحميل الصور بأكثر الطرق تحسينًا. ونقترح استخدام مكتبة Glide التي تُحمّل الصور وتعرضها في أسرع وقت ممكن. وتشمل مكتبات تحميل الصور الشائعة الأخرى Picasso من Square وCoil من Instacart وFresco من Facebook. تبسّط هذه المكتبات معظم المهام المعقدة المرتبطة بالصور النقطية وأنواع أخرى من الصور في Android.
تظهر الصور بجميع الأشكال والأحجام. وفي كثير من الحالات، تكون هذه العناوين أكبر مما هو مطلوب في واجهة المستخدم النموذجية (UI). على سبيل المثال، يعرض تطبيق "معرض الصور" الخاص بالنظام صورًا تم التقاطها باستخدام كاميرا أجهزة 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
المستهدف أو مكوِّن واجهة المستخدم الذي سيتم تحميل الصورة إليه. - حجم شاشة الجهاز الحالي وكثافته
لا يستحق مثلاً تحميل صورة بحجم 1024x768 بكسل في الذاكرة إذا تم عرضها في النهاية ضمن صورة مصغّرة بحجم 128x96 بكسل في ImageView
.
لتوجيه برنامج فك الترميز إلى إنشاء عيّنة فرعية من الصورة، وتحميل نسخة أصغر في الذاكرة، اضبط inSampleSize
على true
في عنصر BitmapFactory.Options
. على سبيل المثال، إنّ الصورة ذات الدقة 2048×1536 والتي تم فك ترميزها باستخدام 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
تعرض صورة مصغّرة بحجم 100x100 بكسل، كما هو موضّح في المثال التالي:
Kotlin
imageView.setImageBitmap( decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100) )
Java
imageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
يمكنك اتّباع عملية مشابهة لفك ترميز الصور النقطية من مصادر أخرى من خلال استبدال طريقة BitmapFactory.decode*
المناسبة حسب الحاجة.