Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

Я пробовал все подходы здесь и в других ресурсах, но я пришел к выводу, что установка ссылки 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: Все SoftReferences, указывающие на объекты с возможностью достижения цели, гарантированно будут очищены до того, как VM выведет OutOfMemoryError.

Ответ 3

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

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

Ответ 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;
    }

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

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;

Ответ 9

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

Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path");

Ответ 10

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

См. метод Bitmap:

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

Ответ 11

Одной из наиболее распространенных ошибок, которые я обнаружил при разработке приложений для 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() исследует дерево представления рекурсивно и:

  • Удаляет обратные вызовы во всех фоновых рисунках
  • Удаляет дочерние элементы во всех группах просмотра

Ответ 12

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

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

              bounds.inSampleSize = 4;

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

              picturesView.setImageBitmap(myBitmap);

Ответ 13

Позволяет 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;
}