Исключение OutOfMemory при загрузке растрового изображения из внешнего хранилища - PullRequest
17 голосов
/ 05 ноября 2010

В моем приложении я загружаю пару изображений из файлов JPEG и PNG.Когда я помещаю все эти файлы в каталог ресурсов и загружаю его таким образом, все в порядке:

InputStream stream = getAssets().open(path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
return new BitmapDrawable(bitmap);

Но когда я пытаюсь загрузить те же самые изображения с SD-карты, я получаю исключение OutOfMemory!

InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
return new BitmapDrawable(bitmap);

Вот что я получаю в журнале:

11-05 00:53:31.003: ERROR/dalvikvm-heap(13183): 827200-byte external allocation too large for this process.
11-05 00:53:31.003: ERROR/GraphicsJNI(13183): VM won't let us allocate 827200 bytes
...
11-05 00:53:31.053: ERROR/AndroidRuntime(13183): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
11-05 00:53:31.053: ERROR/AndroidRuntime(13183):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
...

Почему это может произойти?

ОБНОВЛЕНИЕ: Опробовал оба из них на реальном устройстве - кажетсяЯ не могу загрузить более 12 МБ растровых изображений в то, что называется «внешней памятью» (это не SD-карта).

Ответы [ 13 ]

8 голосов
/ 09 ноября 2010

Я перепробовал все упомянутые подходы здесь & на других ресурсах, но пришел к выводу, что установка ссылки ImageView на null решит проблему:

  public Bitmap getimage(String path ,ImageView iv)
   {
    //iv is passed to set it null to remove it from external memory
    iv=null;
    InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path);
    Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
    stream.close();
    stream=null;
    return bitmap;
    }

и все готово!

Примечание. Хотя это может решить вышеуказанную проблему, но я бы посоветовал вам проверить оптимизированную загрузку изображения Tom van Zummeren .

, а также проверить SoftReference: Все программные ссылки, указывающие на объекты с мягким доступом, гарантированно будут очищены до того, как виртуальная машина выдаст ошибку OutOfMemoryError.

5 голосов
/ 05 ноября 2010
  • Когда вы много делаете с растровыми изображениями, не отлаживайте приложение - просто запустите его. Отладчик оставит утечки памяти.
  • Растровые изображения очень дороги. Если возможно, уменьшите их при загрузке, создав BitmapFactory.Options и установив inSampleSize в> 1.

РЕДАКТИРОВАТЬ: Кроме того, не забудьте проверить ваше приложение на утечки памяти. Утечка растрового изображения (наличие static растровых изображений - отличный способ сделать это) быстро истощит вашу доступную память.

4 голосов
/ 07 ноября 2010

Вероятно, нет ничего плохого в использовании вашего API, я думаю, все, что мы можем сделать, - это заключить, что использование AssetManager требует меньшего количества закулисного выделения кучи, чем открытие случайного файла с SD-карты.

800 КБ - серьезное выделение в чьей-либо книге ... это, несомненно, будет для пикселей с распакованным изображением. Учитывая, что вы знаете размер изображения, какая это глубина? Если это 32bpp, попробуйте переопределить это, используя inPreferredConfig.

3 голосов
/ 14 ноября 2010

Это довольно распространенная проблема, с которой все мы сталкиваемся при загрузке изображений с SDCard.

Решением, которое я нашел, было использование inJustDecodeBounds первым при загрузке изображения с использованием decodeFileDescriptor . Это не будет на самом деле декодировать изображение, но даст размер изображения. Теперь я могу масштабировать его соответствующим образом (используя параметры), чтобы изменить размер изображения для области отображения. Это необходимо потому, что на 5-мегапиксельном изображении легко может быть занято нехватка памяти в телефоне. Это, я считаю, самое элегантное решение.

2 голосов
/ 12 мая 2011

Здесь есть две проблемы ...

  • Память растрового изображения находится не в куче виртуальной машины, а в собственной куче - см. BitmapFactory OOM сводит меня с ума
  • Сборка мусора для собственной кучи ленивее, чем кучи виртуальных машин, поэтому вам нужно быть очень агрессивным в отношении выполнения bitmap.recycle и bitmap = null каждый раз, когда вы проходите действия onPause или onDestroy
1 голос
/ 10 декабря 2012

Благодаря всем темам, я нашел решение, которое работает для меня на реальном устройстве.Все хитрости в том, чтобы использовать

BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inSampleSize=(int)(target_size/bitmap_size); //if original bitmap is bigger

Но для меня этого было недостаточно.Мое исходное изображение (взято из приложения Камера) было 3264x2448.Правильное соотношение для меня было 3, так как я хотел получить простое изображение VGA 1024x768.

Но установки inSampleSize в 3 было недостаточно: по-прежнему не хватает памяти.В итоге я выбрал итеративный подход: я начинаю с вычисленного правильного размера и увеличиваю его до тех пор, пока не перестану иметь исключение OOM.Для меня это было на выборке 4.

// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
// o2.inSampleSize = scale;
float trueScale = o.outWidth / 1024;
o2.inPurgeable = true;
o2.inDither = false;
Bitmap b = null;
do {
     o2.inSampleSize = (int) trueScale;
     Log.d(TAG, "Scale is " + trueScale);
 try {
    b = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (OutOfMemoryError e) {
        Log.e(TAG,"Error decoding image at sampling "+trueScale+", resampling.."+e);
        System.gc();
    try {
        Thread.sleep(50);
     } catch (InterruptedException e1) { 
         e1.printStackTrace();
     }
}
    trueScale += 1;
} while (b==null && trueScale < 10);
return b;
1 голос
/ 04 июля 2012
The best solution i found and edited according to my need

public static Bitmap getImageBitmap(String path) throws IOException{
        // Allocate files and objects outside of timingoops             
        File file = new File(thumbpath);        
        RandomAccessFile in = new RandomAccessFile(file, "rws");
        final FileChannel channel = in.getChannel();
        final int fileSize = (int)channel.size();
        final byte[] testBytes = new byte[fileSize];
        final ByteBuffer buff = ByteBuffer.allocate(fileSize);
        final byte[] buffArray = buff.array();
        @SuppressWarnings("unused")
        final int buffBase = buff.arrayOffset();

        // Read from channel into buffer, and batch read from buffer to byte array;
        long time1 = System.currentTimeMillis();
        channel.position(0);
        channel.read(buff);
        buff.flip();
        buff.get(testBytes);
        long time1 = System.currentTimeMillis();
        Bitmap bmp = Bitmap_process(buffArray);
        long time2 = System.currentTimeMillis();        
        System.out.println("Time taken to load: " + (time2 - time1) + "ms");

        return bmp;
    }

    public static Bitmap Bitmap_process(byte[] buffArray){
        BitmapFactory.Options options = new BitmapFactory.Options();

        options.inDither=false;                     //Disable Dithering mode
        options.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
        options.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
        options.inTempStorage=new byte[32 * 1024];  //Allocate some temporal memory for decoding

        options.inSampleSize=1;

        Bitmap imageBitmap = BitmapFactory.decodeByteArray(buffArray, 0, buffArray.length, options);
        return imageBitmap;
    }
1 голос
/ 20 марта 2012

Используйте приведенный ниже код и вы никогда не получите следующую ошибку: java.lang.OutOfMemoryError: размер растрового изображения превышает бюджет виртуальной машины

              BitmapFactory.Options bounds = new BitmapFactory.Options();

              bounds.inSampleSize = 4;

              myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds);

              picturesView.setImageBitmap(myBitmap);
1 голос
/ 07 ноября 2010

Вместо прямой загрузки с SD-карты, почему бы не переместить изображение в кэш во внутреннем хранилище телефона с помощью getCacheDir () или использовать временный каталог для хранения изображений в?

См. это , это об использовании внешней памяти. Также эта статья может иметь отношение к вам.

0 голосов
/ 03 января 2013

Позволяет inSampleSize изменить размер окончательного считанного изображения. getLength () из AssetFileDescriptor позволяет получить размер файла.

Вы можете изменить inSampleSize в соответствии с getLength (), чтобы предотвратить OutOfMemory следующим образом:

private final int MAX_SIZE = 500000;

public Bitmap readBitmap(Uri selectedImage)
{
    Bitmap bm = null;
    AssetFileDescriptor fileDescriptor = null;
    try
    {
        fileDescriptor = this.getContentResolver().openAssetFileDescriptor(selectedImage,"r");
        long size = fileDescriptor.getLength();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = (int) (size / MAX_SIZE);
        bm = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options);
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
    finally
    {
        try {
            if(fileDescriptor != null) fileDescriptor.close();
        } catch (IOException e) {}
    }
    return bm;
}
...