Грешка в Android: java.lang.OutOfMemoryError: размерът на растерното изображение надвишава бюджета на VM

Попаднах на много въпроси в stackoverflow относно тази грешка, но никой от тях не намери обяснение на правилното решение за моя сценарий.

В моето приложение за Android трябва да позволя на потребителя да щракне върху бутон, за да отвори галерията и да избере изображение. И след това трябва да зареди това конкретно избрано изображение в ImageView в моето оформление (UI).

Правенето на това е доста добре. Следва кодът, който използвам, за да постигна това.

В бутона Качване щракнете ->

        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(intent,"Select Picture"), REQUEST_UPLOAD_IMG);

И след това onActivityResult ->

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) 
{       
    //super.onActivityResult(requestCode, resultCode, data);        
    if(resultCode == Activity.RESULT_OK)
    {
        if(requestCode==REQUEST_UPLOAD_IMG)
        {               
            Uri selectedImageURI = data.getData();
            uploadImgVW.setImageURI(selectedImageURI);              
        }
        else
        {
            Toast.makeText(MainActivity.this, "You can only select an Image.", Toast.LENGTH_LONG).show();
        }
    }
}

Но ако потребителят избере изображение с по-голям размер (като 2MB размер), приложението се затваря със следната грешка. Но е доста добре с нормални (ниво KB) изображения и се чудя какво мога да направя за този проблем (за да се справя с тази грешка). Благодаря...

Грешка ->

06-20 16:43:58.445: E/AndroidRuntime(2075): FATAL EXCEPTION: main
06-20 16:43:58.445: E/AndroidRuntime(2075): java.lang.OutOfMemoryError: bitmap size exceeds VM budget

person JibW    schedule 21.06.2013    source източник
comment
Вероятно трябва да го рециклирате всеки път, когато растерното изображение се зарежда if(bitmap !=null){ bitmap.recycle();   -  person Mayank Saini    schedule 21.06.2013
comment
Приложението се срива дори от първия път. Това е проблема. Добавих и следните кодове, само за да изпълня събирача на отпадъци при щракване на бутона. [ System.gc(); Runtime.getRuntime().gc(); ]   -  person JibW    schedule 21.06.2013
comment
@JibW събирането на боклука не е проблемът, трябва да коригирате размера на изображението, както казва Гунар   -  person tyczj    schedule 21.06.2013
comment
трябва да проверите този въпрос, публикувах примерен пример за ефективно зареждане на големи растерни изображения: stackoverflow.com/questions/20692385/   -  person Zhar    schedule 16.04.2014


Отговори (3)


Има поредица от статии, които описват как да управлявате ефективно растерните изображения. Гледайки кода си, зареждате изображението, без да знаете колко е голямо и в крайна сметка ще се сблъскате с тези проблеми, особено ако зареждате и обработвате много изображения.

Идеята, описана в една от тези статии, е да се зареди вече намалено растерно изображение ( първо проверявате колко голямо е изображението за зареждане, след това изчислявате фактора на намаляване на мащаба и едва след това ще заредите намаленото изображение). За целта първо трябва да знаете размерите на ImageView, след което ще трябва да използвате BitmapFactory.decode(...), тъй като имате URI на целевия файл за показване. URI към файла трябва да е тривиален.

Освен това трябва да проверите и потреблението на памет на вашето приложение ... може да имате други ресурси, които висят в паметта и трябва да ги почистите. Използвам много полезен инструмент - MAT. Много добра статия за това може да намерите тук. Авторът, Patrick Dubroy, проведе много интересна сесия на Google IO 2011 по тази тема. Вижте това, за мен беше много полезно...

person gunar    schedule 21.06.2013

преоразмерете изображението и след това задайте

public static Bitmap decodeUri(Context c, Uri uri, final int requiredSize) 
            throws FileNotFoundException {
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(c.getContentResolver().openInputStream(uri), null, o);

        int width_tmp = o.outWidth
                , height_tmp = o.outHeight;
        int scale = 1;

        while(true) {
            if(width_tmp / 2 < requiredSize || height_tmp / 2 < requiredSize)
                break;
            width_tmp /= 2;
            height_tmp /= 2;
            scale *= 2;
        }

        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(c.getContentResolver().openInputStream(uri), null, o2);
    }   

или можете да използвате така

if(resultCode == RESULT_OK){  
                Uri selectedImage = data.getData();
                InputStream imageStream = null;
                try {
                    imageStream = getContentResolver().openInputStream(selectedImage);
                } catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Bitmap yourSelectedImage = BitmapFactory.decodeStream(imageStream);
                profileImage.setImageBitmap(Bitmap.createScaledBitmap(yourSelectedImage , 120, 120, false));
                }
person Sunil Kumar    schedule 21.06.2013

Може да опитате това...

BitmapFactory.Options options = new BitmapFactory.Options();
options.inTempStorage = new byte[16*1024];

Bitmap bitmapImage = BitmapFactory.decodeFile(path, options);
person Amresh    schedule 21.06.2013