Странная проблема с нехваткой памяти при загрузке изображения в растровый объект

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

Вопрос

У меня есть представление списка с парой кнопок изображения в каждой строке.Когда вы щелкаете по строке списка, запускается новое действие.Мне пришлось создать свои собственные вкладки из-за проблемы с расположением камеры.Действие, которое запускается для получения результата, - это карта.Если я нажимаю на свою кнопку, чтобы запустить предварительный просмотр изображения (загрузить изображение с SD-карты), приложение возвращается из действия обратно в listview действие к обработчику результата, чтобы перезапустить мое новое действие, которое является не чем иным, как виджетом изображения.

Предварительный просмотр изображения в виде списка выполняется с помощью курсора и ListAdapter.Это делает это довольно простым, но я не уверен, как я могу поместить изображение с измененным размером (т.е.Меньший размер бита, а не пикселя, поскольку src для кнопки изображения на лету.Поэтому я просто изменил размер изображения, полученного с камеры телефона.

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

  • Есть ли способ, которым я могу легко построить адаптер списка строка за строкой, где я могу изменять размер на лету (немного мудро)?

Это было бы предпочтительнее, поскольку мне также нужно внести некоторые изменения в свойства виджетов / элементов в каждой строке, поскольку я не могу выбрать строку с сенсорным экраном из-за проблемы с фокусировкой.(Я могу использовать роликовый мяч.)

  • Я знаю, что могу изменить размер моего изображения вне диапазона и сохранить его, но на самом деле это не то, что я хочу сделать, но какой-нибудь пример кода для этого был бы неплох.

Как только я отключил изображение в списке, оно снова заработало нормально.

К ТВОЕМУ СВЕДЕНИЮ:Вот как я это делал:

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
Это было полезно?

Решение

В Обучение Android класс, "Эффективное отображение растровых изображений", предлагает некоторую полезную информацию для понимания и устранения исключения 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 в исключительных случаях проверьте размеры растрового изображения перед его декодированием, если только вы не абсолютно уверены в том, что источник предоставит вам данные изображения предсказуемого размера, которые удобно помещаются в доступной памяти.


Загрузите уменьшенную версию в память

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

  • Предполагаемое использование памяти при загрузке полного изображения в память.
  • Объем памяти, который вы готовы выделить для загрузки этого изображения, определяется любыми другими требованиями вашего приложения к памяти.
  • Размеры целевого ImageView или компонента пользовательского интерфейса, в который должно быть загружено изображение.
  • Размер экрана и плотность текущего устройства.

Например, не стоит загружать в память изображение с разрешением 1024х768 пикселей, если в конечном итоге оно будет отображаться в виде миниатюры размером 128х96 пикселей в ImageView.

Чтобы указать декодеру сделать подвыборку изображения, загрузив уменьшенную версию в память, установите inSampleSize Для true в вашем BitmapFactory.Options объект.Например, изображение с разрешением 2048x1536, которое декодируется с помощью 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 который отображает миниатюру размером 100x100 пикселей, как показано в следующем примере кода:

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;
}

Я внес небольшое улучшение в код Fedor.Это в основном делает то же самое, но без (на мой взгляд) уродливого цикла 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 по умолчанию.

Решение:Переместите изображения в папку "ресурсы" и используйте следующую функцию, чтобы получить возможность растрового отображения:

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.Декодируемый дескриптор вместо того , чтобы Декодирование потока или Декодированный файл):

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. Bitmap хранится в собственной куче, но мусор будет собран автоматически, вызывать recycle() нет необходимости.

  2. Если {размер кучи виртуальной машины} + {выделенная собственная память кучи} >= {Ограничение размера кучи виртуальной машины для устройства}, и вы пытаетесь создать растровое изображение, будет выдан ООМ.

    УВЕДОМЛЕНИЕ:Учитывается РАЗМЕР КУЧИ ВИРТУАЛЬНОЙ машины, а не ВЫДЕЛЕННАЯ ВИРТУАЛЬНОЙ машине ПАМЯТЬ.

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

  4. Таким образом, вы должны поддерживать максимальный объем памяти виртуальной машины как можно меньшим, чтобы размер кучи виртуальной машины не становился слишком большим для экономии доступной памяти для растровых изображений.

  5. Ручной вызов System.gc() бессмыслен, система сначала вызовет его, прежде чем пытаться увеличить размер кучи.

  6. Собственный размер кучи тоже никогда не уменьшится, но он не учитывается для ООМ, так что не нужно беспокоиться об этом.

Затем давайте поговорим о том, что SDK начинается с Honey Comb.

  1. Растровое изображение хранится в куче виртуальной машины, собственная память не учитывается для ООМ.

  2. Условие для ООМ намного проще:{Размер кучи виртуальной машины} >= {Ограничение размера кучи виртуальной машины для устройства}.

  3. Таким образом, у вас есть больше доступной памяти для создания растрового изображения с тем же ограничением размера кучи, что снижает вероятность выброса ООМ.

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

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

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

Вызов AsyncTask.cancel(true) не остановит выполнение, если задача заблокирована при операции ввода-вывода в фоновом потоке.

Обратные вызовы также являются анонимными внутренними классами, поэтому, если статический экземпляр в вашем проекте хранит их и не освобождает, произойдет утечка памяти.

Если вы запланировали повторяющуюся или отложенную задачу, например Таймер, и вы не вызываете cancel() и purge() в onPause(), произойдет утечка памяти.

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

Из-за этого я написал пример приложения, которое демонстрирует кэширование в среде Android.Эта реализация еще не получила ООМ.

Посмотрите в конце этого ответа ссылку на исходный код.

Требования:

  • Android API 2.1 или выше (мне просто не удалось получить доступную память для приложения в API 1.6 - это единственный фрагмент кода, который не работает в API 1.6)
  • Пакет поддержки Android

Screenshot

Характеристики:

  • Сохраняет кэш при изменении ориентации, используя синглтон
  • Использование одна восьмая из выделенной памяти приложения в кэш (измените, если хотите)
  • Большие растровые изображения масштабируется (вы можете определить максимальное количество пикселей, которое вы хотите разрешить)
  • Элементы управления что доступно подключение к Интернету перед загрузкой растровых изображений
  • Гарантирует, что вы только создаете экземпляр одна задача в строке
  • Если ты швыряешься в ListView вдали, он просто не будет загружать растровые изображения между

Это не включает в себя:

  • Кэширование диска.В любом случае, это должно быть легко реализовать - просто укажите на другую задачу, которая извлекает растровые изображения с диска

Пример кода:

Загружаемые изображения - это изображения (75x75) с Flickr.Однако укажите любые URL-адреса изображений, которые вы хотите обработать, и приложение уменьшит их масштаб, если он превысит максимальный.В этом приложении URL-адреса просто находятся в String массив.

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

Cache.java критический материал (the 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();
        }
    } 
}

Вам не нужно будет ничего редактировать в кэше.файл 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() ему звонят очень часто.Обычно не рекомендуется загружать изображения туда, если мы не внедрили проверку, гарантирующую нам, что мы не будем запускать бесконечное количество потоков в строке.Кэш.java проверяет, является ли rowObject.mBitmapUrl уже находится в задаче, и если это так, то она не запустит другую.Следовательно, мы, скорее всего, не превышаем ограничение рабочей очереди из AsyncTask Бассейн.

Скачать:

Вы можете загрузить исходный код с https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip.


Последние слова:

Я тестировал это уже несколько недель, но пока не получил ни одного исключения из ООМ.Я протестировал это на эмуляторе, на моем Nexus One и на моем Nexus S.Я протестировал URL-адреса изображений, которые содержат изображения в качестве HD.Единственным узким местом является то, что загрузка занимает больше времени.

Есть только один возможный сценарий, в котором я могу представить, что ООМ появится, и это если мы загрузим много действительно больших изображений, и прежде чем они будут масштабированы и помещены в кэш, они одновременно займут больше памяти и вызовут ООМ.Но в любом случае это даже не идеальная ситуация, и, скорее всего, ее невозможно будет решить более выполнимым способом.

Сообщайте об ошибках в комментариях!:-)

Я сделал следующее, чтобы взять изображение и изменить его размер на лету.Надеюсь, это поможет

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

Похоже, что это очень длительная проблема, имеющая множество различных объяснений.Я воспользовался советом двух наиболее распространенных представленных здесь ответов, но ни один из них не решил моих проблем с виртуальной машиной, утверждающей, что она не может позволить себе байты для выполнения декодирование часть процесса.После некоторого копания я узнал, что настоящая проблема здесь заключается в процессе декодирования, отвлекающем от РОДНОЙ куча.

Смотрите здесь: BitmapFactory ООМ сводит меня с ума

Это привело меня к другой теме обсуждения, где я нашел еще пару решений этой проблемы.Один из них - позвонитьSystem.gc(); вручную после отображения вашего изображения.Но это на самом деле заставляет ваше приложение использовать БОЛЬШЕ памяти в попытке уменьшить собственную кучу.Лучшим решением, начиная с версии 2.0 (Donut), является использование опции BitmapFactory "inPurgeable".Поэтому я просто добавил o2.inPurgeable=true; сразу после o2.inSampleSize=scale;.

Подробнее на эту тему здесь: Является ли предел кучи памяти всего 6M?

Теперь, сказав все это, я тоже полный профан в Java и Android.Так что, если вы думаете, что это ужасный способ решить эту проблему, вы, вероятно, правы.;-) Но это сотворило со мной чудеса, и я обнаружил, что теперь невозможно запустить виртуальную машину из кэша кучи.Единственный недостаток, который я могу найти, - это то, что вы уничтожаете свое кэшированное нарисованное изображение.Это означает, что если вы сразу вернетесь к этому изображению, вы будете перерисовывать его каждый раз.В случае с тем, как работает мое приложение, на самом деле это не проблема.Ваш пробег может отличаться.

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

 <application
         android:largeHeap="true"

Используй это bitmap.recycle(); Это помогает без каких-либо проблем с качеством изображения.

У меня есть гораздо более эффективное решение, которое не нуждается в каком-либо масштабировании.Просто расшифруйте свое растровое изображение только один раз, а затем кэшируйте его на карте напротив его названия.Затем просто извлеките растровое изображение по названию и установите его в ImageView.Больше ничего не нужно делать.

Это сработает, потому что фактические двоичные данные декодированного растрового изображения не хранятся в куче виртуальной машины dalvik.Он хранится снаружи.Таким образом, каждый раз, когда вы декодируете растровое изображение, оно выделяет память за пределами кучи виртуальной машины, которая никогда не восстанавливается GC

Чтобы помочь вам лучше оценить это, представьте, что вы сохранили свое изображение в папке drawable.Вы просто получаете изображение, выполняя getResources().getDrwable(R.drawable.).Это не будет каждый раз декодировать ваше изображение, но при каждом вызове вы будете повторно использовать уже декодированный экземпляр.Так что, по сути, он кэшируется.

Теперь, поскольку ваше изображение находится где-то в файле (или может даже поступать с внешнего сервера), вы несете ответственность за кэширование декодированного экземпляра bitmap для повторного использования там, где это необходимо.

Надеюсь, это поможет.

Я решил ту же проблему следующим образом.

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);

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

  • Растровая память находится не в куче виртуальной машины, а скорее в собственной куче - см. BitmapFactory ООМ сводит меня с ума
  • Сборка мусора для собственной кучи более ленива, чем для кучи виртуальной машины, поэтому вам нужно быть довольно агрессивным в выполнении bitmap.recycle и bitmap = null каждый раз, когда вы выполняете onPause или 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());
    }
}

Ни один из приведенных выше ответов не сработал для меня, но я придумал ужасно уродливое обходное решение, которое решило проблему.Я добавил очень маленькое изображение размером 1x1 пиксель в свой проект в качестве ресурса и загрузил его в свой 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ы к MBs.Если размер изображения меньше (или до 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;

}

Я надеюсь, что это поможет приятелям, столкнувшимся с той же проблемой!

для получения дополнительной информации, пожалуйста, обратитесь это

Я столкнулся с этой проблемой всего пару минут назад.Я решил эту проблему, улучшив управление моим адаптером listview.Я думал, что это проблема с сотнями изображений размером 50x50 пикселей, которые я использовал, оказывается, я пытался увеличить свой пользовательский вид каждый раз, когда показывалась строка.Просто проверив, не была ли строка раздута, я устранил эту ошибку, и я использую сотни растровых изображений.На самом деле это для счетчика, но базовый адаптер работает все равно для 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, которые, как я знаю, не должны быть необходимыми, но это единственное, что сработало, когда я подвергал свое приложение интенсивному нагрузочному тестированию, переключаясь между действиями.В моем приложении есть список уменьшенных изображений в listview (допустим, действие A), и когда вы нажимаете на одно из этих изображений, оно переводит вас к другому действию (допустим, действие B), которое показывает основное изображение для этого элемента.Когда я переключался между этими двумя действиями взад и вперед, я в конечном итоге получал ошибку ООМ, и приложение принудительно закрывалось.

Когда я добирался до половины списка, он выходил из строя.

Теперь, когда я реализую следующее в упражнении B, я могу без проблем просмотреть весь listview и продолжать, и продолжать, и продолжать...и это достаточно быстро.

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

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

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

Поэтому, пожалуйста, проверьте устройство.Это может быть запущено в устройстве.

Мои 2 цента:я решил свои ошибки ООМ с помощью растровых изображений с помощью:

a) масштабирование моих изображений в 2 раза

б) использование Пикассо библиотека в моем пользовательском адаптере для ListView, с одним вызовом в getView, подобным этому: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

используйте этот код для каждого изображения в select from SdCard или drewable для преобразования растрового объекта.

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 .Это ограничивает возможности устройств с более мощным оборудованием, и если размер изображения слишком мал, это выглядит некрасиво на экране высокой четкости.

Я разработал решение, которое работает с моим 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% от максимально выделенной памяти, возможно, вам потребуется настроить это в соответствии с вашими потребностями и убедиться, что это растровое изображение очищено и не остается в памяти, когда вы закончите его использовать.Обычно я использую этот код для выполнения поворота изображения (исходного и целевого растровых изображений), поэтому моему приложению необходимо загружать 2 растровых изображения в память одновременно, и 25% дает мне хороший буфер без нехватки памяти при выполнении поворота изображения.

Надеюсь, это кому-то там поможет..

Такой OutofMemoryException не может быть полностью устранен вызовом System.gc() и так далее .

Ссылаясь на Жизненный цикл деятельности

Состояния активности определяются самой операционной системой в зависимости от использования памяти для каждого процесса и приоритета каждого процесса.

Вы можете выбрать размер и разрешение для каждого из используемых растровых изображений.Я рекомендую уменьшить размер, выполнить повторную выборку в меньшем разрешении, обратиться к оформлению галерей (одна маленькая картинка в формате PNG и одна оригинальная картинка).)

Обычно размер кучи устройства Android составляет всего 16 МБ (зависит от устройства / ОС, см. Сообщение Размеры кучи), если вы загружаете изображения и их размер превышает 16 МБ, это вызовет исключение из памяти, вместо того чтобы использовать Bitmap для загрузки изображений с SD-карты или из ресурсов или даже из сети, попробуйте использовать Получите изображение , загрузка bitmap требует больше памяти, или вы можете установить bitmap равным null, если ваша работа выполнена с этим bitmap.

Этот код поможет загрузить большое растровое изображение из drawable

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