غريب من الذاكرة مشكلة أثناء تحميل الصورة إلى كائن صورة نقطية

StackOverflow https://stackoverflow.com/questions/477572

سؤال

لدي قائمة عرض مع اثنين من الأزرار الصورة في كل صف.عند النقر فوق قائمة صف انها تطلق نشاط جديد.لقد كان لبناء بلدي علامات التبويب بسبب مشكلة مع الكاميرا تخطيط.النشاط الذي يحصل إطلاق والنتيجة هي الخريطة.إذا كنت انقر على زر لبدء معاينة الصورة (صورة من بطاقة الذاكرة الرقمية المؤمنة) تطبيق العوائد من النشاط مرة أخرى إلى listview النشاط نتيجة معالج لاستئناف بلدي نشاط جديد وهو ليس أكثر من صورة القطعة.

صورة معاينة في القائمة عرض يجري مع المؤشر ، ListAdapter.هذا يجعل من بسيطة جدا, ولكن أنا لست متأكدا كيف يمكن أن أضع تغيير حجم الصورة (أولا-هاء.أصغر قليلا من حجم بكسل كما src على زر صورة على الطاير.لذلك أنا فقط تغيير حجم الصورة التي جاءت من كاميرا الهاتف.

القضية هي أن أحصل على خطأ من الذاكرة عندما كان يحاول العودة و إعادة إطلاق 2nd النشاط.

  • هل هناك طريقة أستطيع بناء قائمة محول بسهولة من صف ، حيث يمكنني تغيير على الطاير (بت الحكمة)?

هذا سيكون من الأفضل كما أنا أيضا بحاجة إلى إجراء بعض التغييرات على خصائص الحاجيات/عناصر في كل صف كما أنا غير قادر على تحديد صف واحد مع شاشة تعمل باللمس بسبب التركيز المسألة.(يمكنني استخدام الكرة الدوارة.)

  • وأنا أعلم أنني يمكن أن تفعل للخروج من الفرقة تغيير حجم و حفظ صورة بلدي ، لكن ليس هذا هو حقا ما أريد القيام به ، ولكن بعض التعليمات البرمجية التي من شأنها أن تكون لطيفة.

في أقرب وقت كما كنت المعوقين الصورة على عرض قائمة انها عملت على ما يرام مرة أخرى.

لمعلوماتك:هذه هي الطريقة التي كنت تفعل ذلك:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

حيث R.id.imagefilename هو ButtonImage.

هنا هو بلدي LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

أنا أيضا جديد خطأ عند عرض صورة:

01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
هل كانت مفيدة؟

المحلول

على تدريب الروبوت كلاس" ، عرض الصور النقطية بكفاءة"،ويقدم بعض المعلومات لفهم والتعامل مع استثناء java.lang.OutOfMemoryError: bitmap size exceeds VM budget عند تحميل الصور النقطية.


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

على BitmapFactory الدرجة يوفر عدة طرق فك (decodeByteArray(), decodeFile(), decodeResource(), إلخ.) من أجل خلق Bitmap من مصادر مختلفة.اختيار أنسب فك شفرة طريقة على صورة مصدر البيانات.هذه الأساليب محاولة تخصيص الذاكرة التي شيدت نقطية وبالتالي يمكن أن يؤدي بسهولة في OutOfMemory الاستثناء.كل نوع من فك شفرة طريقة إضافية التوقيعات التي تمكنك من تحديد فك الخيارات عبر BitmapFactory.Options فئة.وضع inJustDecodeBounds الملكية true في حين فك يتجنب تخصيص الذاكرة ، والعودة null عن كائن صورة نقطية ولكن الإعداد outWidth, outHeight و 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 الاستثناءات ، تحقق أبعاد صورة نقطية قبل فك ذلك ، إلا إذا كنت تثق تماما المصدر أن توفر لك مع متوقعا الحجم بيانات الصورة التي مريح يناسب داخل الذاكرة المتوفرة.


تحميل نسخة مصغرة في الذاكرة

الآن أن أبعاد الصورة معروفة أنها يمكن أن تستخدم ل تقرر ما إذا كانت صورة كاملة يجب أن يتم تحميل في الذاكرة أو إذا subsampled النسخة ينبغي تحميلها بدلا من ذلك.وهنا بعض العوامل في الاعتبار:

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

على سبيل المثال, أنه لا يستحق تحميل 1024x768 بكسل صورة في الذاكرة إذا كان في نهاية المطاف سوف يتم عرضها في 128x96 بكسل المصغرة في ImageView.

اقول فك فرعية الصورة, تحميل نسخة مصغرة في الذاكرة ، مجموعة inSampleSize إلى true في BitmapFactory.Options الكائن.على سبيل المثال, صورة مع القرار 2048 x 1536 أن يتم فك الشفرة مع inSampleSize 4 تنتج صورة نقطية من حوالي 512x384.تحميل هذا في الذاكرة يستخدم 0.75 ميغابايت بدلا من 12 ميغابايت للحصول على صورة كاملة (على افتراض صورة نقطية التكوين ARGB_8888).وهنا طريقة لحساب حجم العينة قيمة الطاقة من اثنين على أساس الهدف العرض والارتفاع:

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:

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 بكسل المصغرة ، كما هو موضح في المثال التالي كود:

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

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

نصائح أخرى

لإصلاح خطأ OutOfMemory، يجب عليك القيام بشيء مثل هذا:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

هذا inSampleSize الخيار يقلل من استهلاك الذاكرة.

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

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

        // The new size we want to scale to
        final int REQUIRED_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            scale *= 2;
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}

لقد قمت بإجراء تحسين بسيط على كود فيدور.إنها تفعل الشيء نفسه بشكل أساسي، ولكن بدون حلقة while القبيحة (في رأيي)، وتؤدي دائمًا إلى قوة اثنين.مجد لفيدور لأنه قام بالحل الأصلي، لقد كنت عالقًا حتى وجدت حله، وبعد ذلك تمكنت من إنشاء هذا :)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}

لقد جئت من نظام التشغيل iOS وشعرت بالإحباط عندما اكتشفت مشكلة تتعلق بشيء أساسي مثل تحميل الصورة وعرضها.بعد كل شيء، يحاول كل من يواجه هذه المشكلة عرض صور ذات حجم معقول.على أي حال، إليك التغييرين اللذين أصلحا مشكلتي (وجعلا تطبيقي سريع الاستجابة).

1) في كل مرة تفعل ذلك BitmapFactory.decodeXYZ(), ، تأكد من اجتياز أ BitmapFactory.Options مع inPurgeable ضبط ل true (ويفضل مع inInputShareable تعيين أيضا ل true).

2) لا تستخدم أبدًا Bitmap.createBitmap(width, height, Config.ARGB_8888).أعني أبدا!لم يسبق لي أن حدث هذا الشيء ولم يثير خطأ في الذاكرة بعد تمريرات قليلة.لا يوجد مبلغ recycle(), System.gc(), ، مهما ساعد.لقد أثار الاستثناء دائمًا.الطريقة الأخرى التي تعمل فعليًا هي الحصول على صورة وهمية في رسوماتك (أو صورة نقطية أخرى قمت بفك تشفيرها باستخدام الخطوة 1 أعلاه)، وإعادة قياسها إلى ما تريد، ثم معالجة الصورة النقطية الناتجة (مثل تمريرها إلى لوحة قماشية) لمزيد من المتعة).لذا، ما يجب عليك استخدامه بدلاً من ذلك هو: Bitmap.createScaledBitmap(srcBitmap, width, height, false).إذا كان عليك، لأي سبب من الأسباب، استخدام طريقة إنشاء القوة الغاشمة، فانجح على الأقل Config.ARGB_4444.

يكاد يكون هذا مضمونًا لتوفير ساعات إن لم يكن أيامًا.كل ما يتحدث عن تحجيم الصورة وما إلى ذلك.لا يعمل حقًا (إلا إذا كنت تفكر في الحصول على حجم خاطئ أو صورة متدهورة كحل).

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

حل:انقل الصور إلى مجلد "الأصول" واستخدم الوظيفة التالية للحصول على BitmapDrawable:

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}

لقد واجهت نفس المشكلة وقمت بحلها عن طريق تجنب وظائف BitmapFactory.decodeStream أو decodeFile واستخدمتها بدلاً من ذلك BitmapFactory.decodeFileDescriptor

decodeFileDescriptor يبدو أنه يستدعي أساليب أصلية مختلفة عن decodeStream/decodeFile.

على أي حال، ما نجح هو هذا (لاحظ أنني أضفت بعض الخيارات كما فعل البعض أعلاه، ولكن هذا ليس ما أحدث الفرق.ما هو حاسم هو الدعوة إلى BitmapFactory.decodeFileDescriptor بدلاً من com.decodeStream أو com.decodeFile):

private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
    bfOptions.inTempStorage=new byte[32 * 1024]; 

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

أعتقد أن هناك مشكلة في الوظيفة الأصلية المستخدمة في decodeStream/decodeFile.لقد أكدت أنه يتم استدعاء طريقة أصلية مختلفة عند استخدام decodeFileDescriptor.ما قرأته أيضًا هو "أن الصور (الصور النقطية) لا يتم تخصيصها بطريقة Java القياسية ولكن عبر المكالمات الأصلية؛تتم التخصيصات خارج الكومة الافتراضية، ولكنهايحسب ضدها!"

أعتقد أن أفضل طريقة لتجنب OutOfMemoryError هو مواجهتها وفهمها.

لقد صنعت برنامج للتسبب عمدا OutOfMemoryError, ومراقبة استخدام الذاكرة.

بعد أن قمت بالكثير من التجارب مع هذا التطبيق، توصلت إلى الاستنتاجات التالية:

سأتحدث عن إصدارات SDK قبل Honey Comb أولاً.

  1. يتم تخزين الصورة النقطية في الكومة الأصلية، ولكنها ستجمع البيانات المهملة تلقائيًا، ولا داعي لاستدعاء recycle().

  2. إذا كان {VM Heap size} + {allocated Native Heap Memory} >= {VM Heap size Limit for the Device}، وكنت تحاول إنشاء صورة نقطية، فسيتم طرح OOM.

    يلاحظ:يتم حساب حجم VM HEAP SIZE بدلاً من VM ALLOCATED MEMORY.

  3. لن يتقلص حجم VM Heap أبدًا بعد نموه، حتى إذا تم تقليص ذاكرة VM المخصصة.

  4. لذلك يتعين عليك الحفاظ على ذروة ذاكرة VM عند أدنى مستوى ممكن لمنع حجم VM Heap Size من النمو بشكل كبير جدًا بحيث لا يمكن حفظ الذاكرة المتوفرة للصور النقطية.

  5. إن استدعاء System.gc() يدويًا لا معنى له، حيث سيقوم النظام باستدعاءه أولاً قبل محاولة زيادة حجم الكومة.

  6. لن يتقلص حجم الكومة الأصلي أيضًا أبدًا، ولكن لا يتم احتسابه بالنسبة لـ OOM، لذا لا داعي للقلق بشأن ذلك.

ثم دعونا نتحدث عن SDK الذي يبدأ من Honey Comb.

  1. يتم تخزين الصورة النقطية في كومة الذاكرة المؤقتة VM، ولا يتم احتساب الذاكرة الأصلية لـ OOM.

  2. شرط OOM أبسط بكثير:{حجم الكومة VM} >= {حد حجم الكومة VM للجهاز}.

  3. لذلك، يكون لديك المزيد من الذاكرة المتوفرة لإنشاء صورة نقطية بنفس الحد الأقصى لحجم الكومة، ومن غير المرجح أن يتم طرح OOM.

فيما يلي بعض ملاحظاتي حول جمع البيانات المهملة وتسرب الذاكرة.

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

وذلك لأن AsyncTask هو مثيل لفئة داخلية مجهولة، فهو يحمل مرجعًا للنشاط.

لن يؤدي استدعاء AsyncTask.cancel(true) إلى إيقاف التنفيذ إذا تم حظر المهمة في عملية إدخال/إخراج في مؤشر ترابط الخلفية.

تعد عمليات الاسترجاعات فئات داخلية مجهولة أيضًا، لذا إذا احتفظت بها نسخة ثابتة في مشروعك ولم تحررها، فسيتم تسرب الذاكرة.

إذا قمت بجدولة مهمة متكررة أو متأخرة، على سبيل المثال مؤقت، ولم تقم باستدعاء Cancel() وpurge() في onPause()، فسيتم تسرب الذاكرة.

لقد رأيت مؤخرًا الكثير من الأسئلة حول استثناءات OOM والتخزين المؤقت.دليل المطور لديه مقالة جيدة حقا على هذا، إلا أن البعض يميل إلى الفشل في تنفيذه بالشكل المناسب.

ولهذا السبب كتبت مثالاً لتطبيق يوضح التخزين المؤقت في بيئة Android.هذا التنفيذ لم يحصل بعد على OOM.

انظر إلى نهاية هذه الإجابة للحصول على رابط للكود المصدري.

متطلبات:

  • Android API 2.1 أو أعلى (لم أتمكن ببساطة من الحصول على الذاكرة المتوفرة لتطبيق ما في API 1.6 - وهذا هو الجزء الوحيد من التعليمات البرمجية الذي لا يعمل في API 1.6)
  • حزمة دعم أندرويد

Screenshot

سمات:

  • يحتفظ بذاكرة التخزين المؤقت إذا كان هناك تغيير في الاتجاه, ، باستخدام مفردة
  • يستخدم ثمن من ذاكرة التطبيق المخصصة لذاكرة التخزين المؤقت (قم بالتعديل إذا أردت)
  • الصور النقطية الكبيرة يتم تحجيمها (يمكنك تحديد الحد الأقصى لوحدات البكسل التي تريد السماح بها)
  • ضوابط أن هناك اتصال بالإنترنت متاح قبل تنزيل الصور النقطية
  • تأكد من أنك تقوم بإنشاء مثيل فقط مهمة واحدة لكل صف
  • لو أنت تقذف ال ListView بعيدًا، فهو ببساطة لن يقوم بتنزيل الصور النقطية بين

هذا لا يشمل:

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

عينة من الرموز:

الصور التي يتم تنزيلها هي صور (75x75) من Flickr.ومع ذلك، ضع أي عناوين URL للصورة التي تريد معالجتها، وسيقوم التطبيق بتقليصها إذا تجاوزت الحد الأقصى.في هذا التطبيق، تكون عناوين URL ببساطة في ملف String مجموعة مصفوفة.

ال LruCache لديه طريقة جيدة للتعامل مع الصور النقطية.ومع ذلك، في هذا التطبيق أضع مثيلا ل LruCache داخل فئة ذاكرة تخزين مؤقت أخرى قمت بإنشائها لجعل التطبيق أكثر جدوى.

الأشياء الهامة لـ Cache.java (ملف loadBitmap() الطريقة هي الأهم):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

لن تحتاج إلى تعديل أي شيء في ملف Cache.java إلا إذا كنت تريد تنفيذ التخزين المؤقت على القرص.

الأشياء الهامة لـ MainActivity.java:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView() يتم الاتصال به في كثير من الأحيان.ليس من الجيد عادةً تنزيل الصور هناك إذا لم نقم بتنفيذ فحص يضمن لنا أننا لن نبدأ عددًا لا حصر له من سلاسل الرسائل لكل صف.يتحقق Cache.java مما إذا كان ملف rowObject.mBitmapUrl موجود بالفعل في مهمة، وإذا كان كذلك، فلن يبدأ مهمة أخرى.ولذلك، فإننا على الأرجح لا نتجاوز قيود قائمة انتظار العمل من AsyncTask حمام سباحة.

تحميل:

يمكنك تنزيل الكود المصدري من https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip.


الكلمات الأخيرة:

لقد اختبرت هذا لبضعة أسابيع حتى الآن، ولم أحصل على استثناء واحد من OOM حتى الآن.لقد قمت باختبار هذا على المحاكي وعلى جهاز Nexus One وعلى جهاز Nexus S.لقد اختبرت عناوين URL للصور التي تحتوي على صور بجودة HD.العيب الوحيد هو أن التنزيل يستغرق وقتًا أطول.

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

الإبلاغ عن الأخطاء في التعليقات!:-)

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

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    

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

انظر هنا: BitmapFactory OOM يقودني إلى الجنون

يقودني ذلك إلى موضوع مناقشة آخر حيث وجدت حلولاً أخرى لهذه المشكلة.واحد هو الاتصالSystem.gc(); يدويًا بعد عرض الصورة.ولكن هذا في الواقع يجعل تطبيقك يستخدم المزيد من الذاكرة، في محاولة لتقليل الكومة الأصلية.الحل الأفضل اعتبارًا من الإصدار 2.0 (Donut) هو استخدام خيار BitmapFactory "inPurgeable".لذلك أضفت ببساطة o2.inPurgeable=true; بعد ذلك مباشرة o2.inSampleSize=scale;.

المزيد عن هذا الموضوع هنا: هل الحد الأقصى لكومة الذاكرة هو 6 ميجا فقط؟

الآن، بعد أن قلت كل هذا، أنا غبي تمامًا في التعامل مع Java وAndroid أيضًا.لذا، إذا كنت تعتقد أن هذه طريقة رهيبة لحل هذه المشكلة، فمن المحتمل أنك على حق.؛-) لكن هذا الأمر قد حقق نتائج مذهلة بالنسبة لي، ووجدت أنه من المستحيل تشغيل الجهاز الافتراضي من ذاكرة التخزين المؤقت الآن.العيب الوحيد الذي يمكنني العثور عليه هو أنك تقوم بإتلاف الصورة المرسومة المخزنة مؤقتًا.مما يعني أنك إذا عدت إلى تلك الصورة، فإنك تعيد رسمها في كل مرة.في حالة كيفية عمل طلبي، فهذه ليست مشكلة حقًا.قد تختلف المسافة المقطوعة الخاصة بك.

للأسف إذا لم يعمل أي مما سبق، قم بإضافة هذا إلى حسابك يظهر ملف.داخل طلب بطاقة شعار

 <application
         android:largeHeap="true"

استخدم هذا bitmap.recycle(); وهذا يساعد دون أي مشكلة في جودة الصورة.

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

سيعمل هذا لأن البيانات الثنائية الفعلية للصورة النقطية التي تم فك تشفيرها لا يتم تخزينها داخل كومة dalvik VM.يتم تخزينه خارجيا.لذا، في كل مرة تقوم فيها بفك تشفير صورة نقطية، فإنها تقوم بتخصيص ذاكرة خارج كومة VM التي لا يمكن استعادتها مطلقًا بواسطة GC

لمساعدتك على تقدير ذلك بشكل أفضل، تخيل أنك احتفظت بصورتك في المجلد القابل للرسم.يمكنك فقط الحصول على الصورة عن طريق إجراء getResources().getDrwable(R.drawable.).لن يؤدي هذا إلى فك تشفير صورتك في كل مرة، بل سيعيد استخدام مثيل تم فك تشفيره بالفعل في كل مرة تقوم باستدعاءها.لذلك يتم تخزينه مؤقتًا في جوهره.

الآن، نظرًا لأن صورتك موجودة في ملف في مكان ما (أو قد تكون قادمة من خادم خارجي)، تقع على عاتقك مسؤولية تخزين نسخة الصورة النقطية التي تم فك تشفيرها مؤقتًا لإعادة استخدامها في أي مكان تكون هناك حاجة إليها.

أتمنى أن يساعدك هذا.

لقد قمت بحل نفس المشكلة بالطريقة التالية.

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);

هناك نوعان من القضايا هنا....

  • ذاكرة الصورة النقطية ليست في كومة الذاكرة المؤقتة VM ولكنها موجودة في الكومة الأصلية - راجع BitmapFactory OOM يقودني إلى الجنون
  • يعد تجميع البيانات المهملة للكومة الأصلية أكثر كسلاً من كومة الذاكرة المؤقتة VM - لذا يجب أن تكون جريئًا جدًا في تنفيذ bitmap.recycle و bitmap =null في كل مرة تمر فيها بإيقاف مؤقت للنشاط أو onDestroy

لقد نجح هذا بالنسبة لي!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}

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

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.

إجابات رائعة هنا، لكني أردت فئة قابلة للاستخدام بالكامل لمعالجة هذه المشكلة..لذلك فعلت واحدة.

هنا أنا فئة BitmapHelper هذا هو دليل على خطأ OutOfMemoryError :-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}

هذا يعمل بالنسبة لي.

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

وهذا على C# monodroid.يمكنك بسهولة تغيير مسار الصورة.المهم هنا هو الخيارات التي سيتم تحديدها.

يبدو أن هذا هو المكان المناسب لمشاركة فئة الأدوات المساعدة الخاصة بي لتحميل الصور ومعالجتها مع المجتمع، فنحن نرحب بك لاستخدامها وتعديلها بحرية.

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://stackoverflow.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
     * @param width
     * @param height
     * @param config
     * @return
     */
    public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
        long pixels=width*height;
        switch(config){
        case ALPHA_8: // 1 byte per pixel
            return pixels;
        case ARGB_4444: // 2 bytes per pixel, but depreciated
            return pixels*2;
        case ARGB_8888: // 4 bytes per pixel
            return pixels*4;
        case RGB_565: // 2 bytes per pixel
            return pixels*2;
        default:
            return pixels;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}

في أحد طلباتي أحتاج إلى التقاط صورة إما من Camera/Gallery.إذا قام المستخدم بالنقر فوق الصورة من الكاميرا (قد تكون بدقة 2 ميجابكسل أو 5 ميجابكسل أو 8 ميجابكسل)، فسيختلف حجم الصورة من kBق ل MBس.إذا كان حجم الصورة أقل (أو يصل إلى 1-2 ميجابايت) أعلاه، فإن الكود يعمل بشكل جيد ولكن إذا كان لدي صورة بحجم أكبر من 4 ميجابايت أو 5 ميجابايت، فعندئذٍ OOM يأتي في الإطار :(

ثم عملت على حل هذه المشكلة وأخيرًا قمت بإجراء التحسين أدناه على كود Fedor (كل الفضل لـ Fedor لصنع مثل هذا الحل الرائع) :)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.inInputShareable = true; // Which kind of reference will be used to
                                    // recover the Bitmap data after being
                                    // clear, when it will be used in the
                                    // future

    BitmapFactory.decodeFile(fPath, opts);

    // The new size we want to scale to
    final int REQUIRED_SIZE = 70;

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

آمل أن يساعد هذا الأصدقاء الذين يواجهون نفس المشكلة!

للمزيد يرجى الرجوع هذا

لقد واجهت هذه المشكلة منذ بضع دقائق.لقد قمت بحلها من خلال القيام بعمل أفضل في إدارة محول عرض القائمة الخاص بي.اعتقدت أنها مشكلة تتعلق بمئات الصور مقاس 50 × 50 بكسل التي كنت أستخدمها، وتبين أنني كنت أحاول تضخيم العرض المخصص الخاص بي في كل مرة يتم فيها عرض الصف.ببساطة عن طريق الاختبار لمعرفة ما إذا كان الصف قد تم تضخيمه، قمت بإزالة هذا الخطأ، وأنا أستخدم مئات الصور النقطية.هذا في الواقع مخصص لـ Spinner، لكن المحول الأساسي يعمل بنفس الطريقة مع ListView.أدى هذا الإصلاح البسيط أيضًا إلى تحسين أداء المحول بشكل كبير.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...

لقد أمضيت اليوم بأكمله في اختبار هذه الحلول والشيء الوحيد الذي نجح بالنسبة لي هو الأساليب المذكورة أعلاه للحصول على الصورة واستدعاء GC يدويًا، وهو ما أعلم أنه ليس من المفترض أن يكون ضروريًا، ولكنه الشيء الوحيد الذي نجح عندما أضع تطبيقي تحت اختبار التحميل الثقيل للتبديل بين الأنشطة.يحتوي تطبيقي على قائمة بالصور المصغرة في عرض القائمة (على سبيل المثال النشاط أ) وعندما تنقر على إحدى تلك الصور، فإنه ينقلك إلى نشاط آخر (على سبيل المثال النشاط ب) يعرض صورة رئيسية لهذا العنصر.عندما كنت أقوم بالتبديل بين النشاطين، كنت أتلقى في النهاية خطأ OOM وسيفرض التطبيق الإغلاق.

عندما أصل إلى منتصف الطريق في عرض القائمة، سوف يتعطل.

الآن عندما أقوم بتنفيذ ما يلي في النشاط ب، يمكنني الاطلاع على عرض القائمة بالكامل دون أي مشكلة والاستمرار في العمل والاستمرار... وهذا سريع جدًا.

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}

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

لذا يرجى التحقق من الجهاز.قد يتم تشغيله في الجهاز.

سنتي 2:لقد قمت بحل أخطاء OOM الخاصة بي باستخدام الصور النقطية عن طريق:

أ) تحجيم صوري بعامل 2

ب) باستخدام رسام مكتبة في المحول المخصص الخاص بي لـ ListView، مع مكالمة واحدة في getView مثل هذا: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

استخدم هذه التعليمات البرمجية لكل صورة محددة من SdCard أو قابلة للرسم لتحويل كائن الصورة النقطية.

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

استخدم مسار الصورة الخاص بك بدلاً من ذلك ImageData_Path.get(img_pos).getPath() .

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

لقد توصلت إلى حل يعمل مع جهاز Samsung Galaxy S3 والعديد من الأجهزة الأخرى بما في ذلك الأجهزة الأقل قوة، مع جودة صورة أفضل عند استخدام جهاز أكثر قوة.

جوهر الأمر هو حساب الحد الأقصى للذاكرة المخصصة للتطبيق على جهاز معين، ثم ضبط المقياس على أدنى مستوى ممكن دون تجاوز هذه الذاكرة.إليك الكود:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

لقد قمت بتعيين الحد الأقصى للذاكرة المستخدمة بواسطة هذه الصورة النقطية ليكون 25% من الحد الأقصى للذاكرة المخصصة، وقد تحتاج إلى ضبط هذا وفقًا لاحتياجاتك والتأكد من تنظيف هذه الصورة النقطية وعدم بقائها في الذاكرة عند الانتهاء من استخدامها.عادةً ما أستخدم هذا الرمز لإجراء تدوير الصورة (الصورة النقطية للمصدر والوجهة) لذلك يحتاج تطبيقي إلى تحميل صورتين نقطيتين في الذاكرة في نفس الوقت، ويمنحني 25% مخزنًا مؤقتًا جيدًا دون نفاد الذاكرة عند إجراء تدوير الصورة.

آمل أن يساعد هذا شخص هناك..

هذه OutofMemoryException لا يمكن حلها بالكامل عن طريق استدعاء System.gc() وما إلى ذلك وهلم جرا .

بالإشارة إلى دورة حياة النشاط

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

يمكنك مراعاة حجم ودقة كل صورة من الصور النقطية المستخدمة.أوصي بتقليل الحجم وإعادة التشكيل إلى دقة أقل والرجوع إلى تصميم المعارض (صورة واحدة صغيرة PNG وصورة أصلية واحدة.)

بشكل عام، يبلغ حجم الكومة لجهاز Android 16 ميجابايت فقط (يختلف باختلاف الجهاز/نظام التشغيل، راجع المنشور أحجام الكومة)، إذا كنت تقوم بتحميل الصور ويتجاوز حجمها 16 ميجابايت، فسوف يؤدي ذلك إلى التخلص من استثناء الذاكرة، بدلاً من استخدام الصورة النقطية لـ، حاول تحميل الصور من بطاقة SD أو من الموارد أو حتى من الشبكة getImageUri أو أن تحميل الصورة النقطية يتطلب المزيد من الذاكرة، أو يمكنك تعيين الصورة النقطية على قيمة فارغة إذا كان عملك قد تم باستخدام تلك الصورة النقطية.

سيساعد هذا الرمز في تحميل صورة نقطية كبيرة من الرسوميات

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top