تحميل الصور النقطية الكبيرة بكفاءة
تنظيم صفحاتك في مجموعات
يمكنك حفظ المحتوى وتصنيفه حسب إعداداتك المفضّلة.
ملاحظة: هناك العديد من المكتبات التي تتبع
أفضل الممارسات لتحميل الصور. يمكنك استخدام هذه المكتبات في تطبيقك
لتحميل الصور بأفضل طريقة محسَّنة. ننصحك بما يلي:
التمرير
الذي يقوم بتحميل الصور وعرضها في أسرع وقت ممكن وسلاسة.
تشمل مكتبات تحميل الصور الشائعة الأخرى مكتبة بيكاسو من 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*
المناسبة حسب الحاجة.
يخضع كل من المحتوى وعيّنات التعليمات البرمجية في هذه الصفحة للتراخيص الموضحّة في ترخيص استخدام المحتوى. إنّ Java وOpenJDK هما علامتان تجاريتان مسجَّلتان لشركة Oracle و/أو الشركات التابعة لها.
تاريخ التعديل الأخير: 2025-07-27 (حسب التوقيت العالمي المتفَّق عليه)
[[["يسهُل فهم المحتوى.","easyToUnderstand","thumb-up"],["ساعَدني المحتوى في حلّ مشكلتي.","solvedMyProblem","thumb-up"],["غير ذلك","otherUp","thumb-up"]],[["لا يحتوي على المعلومات التي أحتاج إليها.","missingTheInformationINeed","thumb-down"],["الخطوات معقدة للغاية / كثيرة جدًا.","tooComplicatedTooManySteps","thumb-down"],["المحتوى قديم.","outOfDate","thumb-down"],["ثمة مشكلة في الترجمة.","translationIssue","thumb-down"],["مشكلة في العيّنات / التعليمات البرمجية","samplesCodeIssue","thumb-down"],["غير ذلك","otherDown","thumb-down"]],["تاريخ التعديل الأخير: 2025-07-27 (حسب التوقيت العالمي المتفَّق عليه)"],[],[],null,["# Loading Large Bitmaps Efficiently\n\n**Note:** There are several libraries that follow\nbest practices for loading images. You can use these libraries in your app to\nload images in the most optimized manner. We recommend the\n[Glide](https://github.com/bumptech/glide)\nlibrary, which loads and displays images as quickly and smoothly as possible.\nOther popular image loading libraries include [Picasso](http://square.github.io/picasso/) from Square, [Coil](https://github.com/coil-kt/coil) from Instacart, and\n[Fresco](https://github.com/facebook/fresco)\nfrom Facebook. These libraries simplify most of the complex tasks associated\nwith bitmaps and other types of images on Android.\n\nImages come in all shapes and sizes. In many cases they are larger than required for a typical\napplication user interface (UI). For example, the system Gallery application displays photos taken\nusing your Android devices's camera which are typically much higher resolution than the screen\ndensity of your device.\n\nGiven that you are working with limited memory, ideally you only want to load a lower resolution\nversion in memory. The lower resolution version should match the size of the UI component that\ndisplays it. An image with a higher resolution does not provide any visible benefit, but still takes\nup precious memory and incurs additional performance overhead due to additional on the fly\nscaling.\n\nThis lesson walks you through decoding large bitmaps without exceeding the per application\nmemory limit by loading a smaller subsampled version in memory.\n\nRead Bitmap Dimensions and Type\n-------------------------------\n\nThe [BitmapFactory](/reference/android/graphics/BitmapFactory) class provides several decoding methods ([decodeByteArray()](/reference/android/graphics/BitmapFactory#decodeByteArray(byte[], int, int, android.graphics.BitmapFactory.Options)), [decodeFile()](/reference/android/graphics/BitmapFactory#decodeFile(java.lang.String, android.graphics.BitmapFactory.Options)), [decodeResource()](/reference/android/graphics/BitmapFactory#decodeResource(android.content.res.Resources, int, android.graphics.BitmapFactory.Options)), etc.) for creating a [Bitmap](/reference/android/graphics/Bitmap) from various sources. Choose\nthe most appropriate decode method based on your image data source. These methods attempt to\nallocate memory for the constructed bitmap and therefore can easily result in an `OutOfMemory`\nexception. Each type of decode method has additional signatures that let you specify decoding\noptions via the [BitmapFactory.Options](/reference/android/graphics/BitmapFactory.Options) class. Setting the [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) property to `true` while decoding\navoids memory allocation, returning `null` for the bitmap object but setting [outWidth](/reference/android/graphics/BitmapFactory.Options#outWidth), [outHeight](/reference/android/graphics/BitmapFactory.Options#outHeight) and [outMimeType](/reference/android/graphics/BitmapFactory.Options#outMimeType). This technique allows you to read the\ndimensions and type of the image data prior to construction (and memory allocation) of the\nbitmap. \n\n### Kotlin\n\n```kotlin\nval options = BitmapFactory.Options().apply {\n inJustDecodeBounds = true\n}\nBitmapFactory.decodeResource(resources, R.id.myimage, options)\nval imageHeight: Int = options.outHeight\nval imageWidth: Int = options.outWidth\nval imageType: String = options.outMimeType\n```\n\n### Java\n\n```java\nBitmapFactory.Options options = new BitmapFactory.Options();\noptions.inJustDecodeBounds = true;\nBitmapFactory.decodeResource(getResources(), R.id.myimage, options);\nint imageHeight = options.outHeight;\nint imageWidth = options.outWidth;\nString imageType = options.outMimeType;\n```\n\nTo avoid `java.lang.OutOfMemory` exceptions, check the dimensions of a bitmap before\ndecoding it, unless you absolutely trust the source to provide you with predictably sized image data\nthat comfortably fits within the available memory.\n\nLoad a Scaled Down Version into Memory\n--------------------------------------\n\nNow that the image dimensions are known, they can be used to decide if the full image should be\nloaded into memory or if a subsampled version should be loaded instead. Here are some factors to\nconsider:\n\n- Estimated memory usage of loading the full image in memory.\n- Amount of memory you are willing to commit to loading this image given any other memory requirements of your application.\n- Dimensions of the target [ImageView](/reference/android/widget/ImageView) or UI component that the image is to be loaded into.\n- Screen size and density of the current device.\n\nFor example, it's not worth loading a 1024x768 pixel image into memory if it will eventually be\ndisplayed in a 128x96 pixel thumbnail in an [ImageView](/reference/android/widget/ImageView).\n\nTo tell the decoder to subsample the image, loading a smaller version into memory, set [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) to `true` in your [BitmapFactory.Options](/reference/android/graphics/BitmapFactory.Options) object. For example, an image with resolution 2048x1536 that\nis decoded with an [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) of 4 produces a\nbitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full\nimage (assuming a bitmap configuration of [ARGB_8888](/reference/android/graphics/Bitmap.Config)). Here's\na method to calculate a sample size value that is a power of two based on a target width and\nheight: \n\n### Kotlin\n\n```kotlin\nfun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {\n // Raw height and width of image\n val (height: Int, width: Int) = options.run { outHeight to outWidth }\n var inSampleSize = 1\n\n if (height \u003e reqHeight || width \u003e reqWidth) {\n\n val halfHeight: Int = height / 2\n val halfWidth: Int = width / 2\n\n // Calculate the largest inSampleSize value that is a power of 2 and keeps both\n // height and width larger than the requested height and width.\n while (halfHeight / inSampleSize \u003e= reqHeight && halfWidth / inSampleSize \u003e= reqWidth) {\n inSampleSize *= 2\n }\n }\n\n return inSampleSize\n}\n```\n\n### Java\n\n```java\npublic static int calculateInSampleSize(\n BitmapFactory.Options options, int reqWidth, int reqHeight) {\n // Raw height and width of image\n final int height = options.outHeight;\n final int width = options.outWidth;\n int inSampleSize = 1;\n\n if (height \u003e reqHeight || width \u003e reqWidth) {\n\n final int halfHeight = height / 2;\n final int halfWidth = width / 2;\n\n // Calculate the largest inSampleSize value that is a power of 2 and keeps both\n // height and width larger than the requested height and width.\n while ((halfHeight / inSampleSize) \u003e= reqHeight\n && (halfWidth / inSampleSize) \u003e= reqWidth) {\n inSampleSize *= 2;\n }\n }\n\n return inSampleSize;\n}\n```\n\n**Note:** A power of two value is calculated because the decoder uses\na final value by rounding down to the nearest power of two, as per the [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) documentation.\n\nTo use this method, first decode with [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) set to `true`, pass the options\nthrough and then decode again using the new [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) value and [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) set to `false`: \n\n### Kotlin\n\n```kotlin\nfun decodeSampledBitmapFromResource(\n res: Resources,\n resId: Int,\n reqWidth: Int,\n reqHeight: Int\n): Bitmap {\n // First decode with inJustDecodeBounds=true to check dimensions\n return BitmapFactory.Options().run {\n inJustDecodeBounds = true\n BitmapFactory.decodeResource(res, resId, this)\n\n // Calculate inSampleSize\n inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)\n\n // Decode bitmap with inSampleSize set\n inJustDecodeBounds = false\n\n BitmapFactory.decodeResource(res, resId, this)\n }\n}\n```\n\n### Java\n\n```java\npublic static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,\n int reqWidth, int reqHeight) {\n\n // First decode with inJustDecodeBounds=true to check dimensions\n final BitmapFactory.Options options = new BitmapFactory.Options();\n options.inJustDecodeBounds = true;\n BitmapFactory.decodeResource(res, resId, options);\n\n // Calculate inSampleSize\n options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);\n\n // Decode bitmap with inSampleSize set\n options.inJustDecodeBounds = false;\n return BitmapFactory.decodeResource(res, resId, options);\n}\n```\n\nThis method makes it easy to load a bitmap of arbitrarily large size into an [ImageView](/reference/android/widget/ImageView) that displays a 100x100 pixel thumbnail, as shown in the following example\ncode: \n\n### Kotlin\n\n```kotlin\nimageView.setImageBitmap(\n decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)\n)\n```\n\n### Java\n\n```java\nimageView.setImageBitmap(\n decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));\n```\n\nYou can follow a similar process to decode bitmaps from other sources, by substituting the\nappropriate [BitmapFactory.decode*](/reference/android/graphics/BitmapFactory#decodeByteArray(byte[], int, int, android.graphics.BitmapFactory.Options)) method as needed."]]