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

17

В моем приложении загружается несколько изображений из файлов 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)
...

Почему это может случиться?

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

    
задан Fixpoint 05.11.2010 в 01:56
источник
  • В каком состоянии вы тестируете вышеуказанный код? В эмуляторе или в режиме реального устройства есть USB? Скорее всего, ваш режим USB установлен в режим диска, который блокирует SD-карту. –  xandy 05.11.2010 в 01:59
  • Я запускаю этот код в эмуляторе. –  Fixpoint 05.11.2010 в 08:37
  • Каков размер файлов jpg / png? –  Fedor 08.11.2010 в 09:10
  • Размер файла наибольшей загрузки JPG I составляет 400KB, это 800x600x24. –  Fixpoint 09.11.2010 в 23:43
  • У вас есть экран 800x600. Если вы этого не сделаете, вы должны использовать суперзамер, чтобы использовать меньше памяти. stackoverflow.com/questions/477572/... –  Fedor 10.11.2010 в 14:25
Показать остальные комментарии

13 ответов

4

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

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

    
ответ дан Reuben Scratton 07.11.2010 в 18:29
источник
  • Таким образом, действительно кажется, что AssetManager на эмуляторе может только избежать полной загрузки размера битмапа. :( –  Fixpoint 13.11.2010 в 23:05
8

Я попробовал все подходы, упомянутые здесь и amp; на других ресурсах, но я пришел к выводу, что установка ссылки 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;
    }

& амп; вы закончили!

Примечание. Хотя это может решить проблему выше, но я бы предложил вам проверить Том ван Зуммерен оптимизированная загрузка изображений.

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

    
ответ дан 100rabh 09.11.2010 в 14:46
источник
5
  • Когда вы делаете много с растровыми изображениями, не отлаживайте приложение - просто запустите его. Отладчик оставит утечку памяти.
  • Растровые изображения очень дороги. Если возможно, уменьшите их нагрузку, создав BitmapFactory.Options и установив inSampleSize в & gt; 1.

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

    
ответ дан EboMike 05.11.2010 в 02:02
источник
  • Я не отлаживаю это приложение, просто запустив его. Остальная часть кода на 100% одинакова, так как может быть, что растровые изображения загружаются из одного места, а другие нет? Что касается масштабирования - я бы хотел использовать изображения в высоком качестве (для их загрузки должно быть достаточно памяти, потому что они загружаются без ошибок из активов). –  Fixpoint 05.11.2010 в 08:40
4
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;
    }
    
ответ дан Arun 04.07.2012 в 11:48
источник
3

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

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

    
ответ дан the100rabh 14.11.2010 в 08:55
источник
  • Я рассматривал это, но это также означает, что я должен был бы сделать 2 запроса для одного изображения –  Bostone 30.10.2011 в 17:42
  • Да, но я havnt столкнулся с любым замедлением из-за двух вызовов, возможно, потому что inJustDecodeBounds не делает много работы –  the100rabh 01.11.2011 в 14:48
2

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

  • Растровая память не находится в куче VM, а скорее в нативной куче - см. ответ дан Torid 12.05.2011 в 06:42
источник
1

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

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

ответ дан Skaty 07.11.2010 в 13:03
источник
1

Благодаря всем потокам я нашел решение, которое работает для меня на реальном устройстве. Трюки - это использование

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;
    
ответ дан zontar 10.12.2012 в 11:54
источник
0

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

См. метод Bitmap:

void recycle () Освободите память, связанную с пикселями этого растрового изображения, и пометьте растровое изображение как «мертвое», то есть он вызовет исключение, если вызывается getPixels () или setPixels () и ничего не рисует.

    
ответ дан imcaptor 10.11.2010 в 06:16
источник
  • Я не хочу перерабатывать все эти растровые изображения, я их использую. –  Fixpoint 10.11.2010 в 15:13
  • Если одно растровое изображение скрыто для пользователя, вам лучше его переработать. Если они снова появятся, вы снова загрузите их. Память телефона ограничена. –  imcaptor 11.11.2010 в 06:05
  • При попытке загрузить растровое изображение, которое было переработано, оно выдаст исключение, которое не может загрузить переработанную растровую карту ... –  66CLSjY 22.12.2010 в 06:51
0

Одной из наиболее распространенных ошибок, которые я обнаружил при разработке приложений для Android, является ошибка «java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget». Я быстро обнаружил эту ошибку при работе с большим количеством растровых изображений после изменения ориентации: активность уничтожена, снова создана, а макеты «завышены» из XML, потребляющего память VM, доступную для растровых изображений.

Растровые изображения в предыдущем макете деятельности не были должным образом освобождены сборщиком мусора, поскольку они пересекли ссылки на их активность. После многих экспериментов я нашел неплохое решение этой проблемы.

Сначала установите атрибут «id» в родительском представлении вашего XML-макета:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:id="@+id/RootView"
     >
     ...

Затем в методе onDestroy () вашей операции вызовите метод unbindDrawables (), передающий подтверждение родительскому представлению, а затем выполните команду System.gc ()

    @Override
    protected void onDestroy() {
    super.onDestroy();

    unbindDrawables(findViewById(R.id.RootView));
    System.gc();
    }

    private void unbindDrawables(View view) {
        if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
        }
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
            }
        ((ViewGroup) view).removeAllViews();
        }
    }

Этот метод unbindDrawables () исследует дерево представления рекурсивно и:

  1. Удаляет обратные вызовы во всех фоновых рисунках
  2. Удаляет дочерние элементы во всех группах просмотра
ответ дан hp.android 21.07.2011 в 18:01
источник
0

Попробуйте по-другому ...

Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path");
    
ответ дан Raju Jadhav 09.11.2010 в 16:34
источник
  • Пробовал, тот же результат, что и с InputStream: OutOfMemory. –  Fixpoint 09.11.2010 в 23:42
0

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

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

              bounds.inSampleSize = 4;

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

              picturesView.setImageBitmap(myBitmap);
    
ответ дан krisDrOid 20.03.2012 в 12:02
источник
0

Позволяет 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;
}
    
ответ дан fingerup 03.01.2013 в 17:37
источник