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

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

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

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

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

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

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

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

FYI: вот как я это делал:

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

30 ответов


на Android Обучение класс, "Отображение Растровых Изображений Эффективно", предлагает отличную информацию для понимания и работы с исключением java.lang.OutOfMemoryError: bitmap size exceeds VM budget при загрузке растровых изображений.


читать размеры растрового изображения и введите

на BitmapFactory класс предоставляет несколько методов декодирования (decodeByteArray(), decodeFile(), decodeResource(), etc.) для создания 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 или UI, в который загружается изображение.
  • размер экрана и плотность текущего устройства.

например, не стоит загружать изображение пикселя 1024x768 в память, если оно в конечном итоге будет отображаться в миниатюре пикселя 128x96 в ImageView.

чтобы сообщить декодеру о подмножестве изображения, загрузив меньшую версию в память, установите inSampleSize до true в своем


чтобы исправить ошибку OutOfMemory, вы должны сделать что-то вроде этого:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

этой уменьшает потребление памяти.

вот полный метод. Сначала он считывает размер изображения без декодирования самого содержимого. Тогда он находит лучшее 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;
}

Я сделал небольшое улучшение кода Федора. Он в основном делает то же самое, но без (на мой взгляд) уродливого цикла 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). Если по какой-либо причине вы должны использовать метод создания грубой силы, то по крайней мере pass Config.ARGB_4444.

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


Это известная ошибка, это не из-за больших файлов. Поскольку Android кэширует Drawables, он выходит из памяти после использования нескольких изображений. Но я нашел альтернативный способ для этого, пропустив систему кэша android по умолчанию.

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

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.decodeFileDescriptor вместо decodeStream или decodeFile):

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 Это посмотреть ему в лицо и понять его.

Я app чтобы намеренно причинить OutOfMemoryError, и контролировать использование памяти.

после того, как я сделал много экспериментов с этим приложением, у меня есть следующие выводы:

Я собираюсь поговорить о версиях SDK перед медовым гребнем.

  1. растровое изображение хранится в собственной куче, но оно автоматически собирает мусор, вызывая recycle() не требуется.

  2. Если {VM heap size} + {выделенная собственная память кучи} >= {VM heap size limit for the device}, и вы пытаетесь создать растровое изображение, будет брошен OOM.

    Примечание: размер кучи VM подсчитывается, а не выделенная память VM.

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

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

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

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

тогда давайте поговорим о SDK, начиная с Медового гребня.

  1. растровое изображение хранится в куче VM, собственная память не учитывается для OOM.

  2. условие для OOM намного проще: {размер кучи VM} >= {ограничение размера кучи VM для устройства}.

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

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

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

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

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

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

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


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

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

посмотрите в конце этого ответа для ссылки на источник код.

требования:

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

Screenshot

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

  • сохраняет кэш, если есть изменение ориентации, используя a синглтон!--22-->
  • использовать одна восьмая из назначенной памяти приложения в кэш (изменить, если вы хотите)
  • большие растровые изображения масштабируется (вы можете определить максимальные пиксели, которые вы хотите разрешить)
  • управления что есть подключение к интернету перед загрузкой рисунки
  • удостоверяется, что вы только создаете экземпляр одно задание в строке
  • если ты бросаешь the ListView прочь, он просто не будет загружать растровые изображения между

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

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

пример кода:

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

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

кэш.критический материал java (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.


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

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

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

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


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

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

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

 <application
         android:largeHeap="true"

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

см. здесь: BitmapFactory ум сводит меня с ума

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

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

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


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


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

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

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

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

надеюсь, что это помогает.


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

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

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

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

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

вот мой класс 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;
    }

}

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

это работает для меня.

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. Если пользователь нажимает изображение с камеры (может быть 2MP, 5MP или 8MP), размер изображения варьируется от kBs до MBs. Если размер изображения меньше (или до 1-2MB) выше кода, работающего нормально, но если у меня есть изображение размером выше 4MB или 5MB, то 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. Я думал, что это проблема с сотнями изображений 50x50px, которые я использовал, оказывается, я пытался раздуть свой пользовательский вид каждый раз, когда показывалась строка. Просто проверив, была ли строка завышена, я устранил эту ошибку, и я использую сотни растровых изображений. Это на самом деле для блесны, но базовый адаптер работает все равно для 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);
    }
...

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

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


Я провел весь день, тестируя эти решения, и единственное, что сработало для меня, - это вышеупомянутые подходы для получения изображения и ручного вызова GC, который, как я знаю, не должен быть необходимым, но это единственное, что сработало, когда я поместил свое приложение под тяжелое тестирование нагрузки, переключаясь между действиями. Мое приложение имеет список миниатюр изображений в listview в (скажем, activity A), и когда вы нажимаете на одно из этих изображений, оно переносит вас на другое действие (скажем, упражнение B), которое показывает основное изображение для этого элемента. Когда я буду переключаться между двумя действиями, я в конечном итоге получу ошибку OOM, и приложение принудительно закроется.

когда я доберусь до половины списка, он рухнет.

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

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

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

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

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

b) использование Пикассо библиотека в моем пользовательском адаптере для 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;

используйте свой путь изображения instend ImageData_Path.get (img_pos).getPath() .


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

со ссылкой на Жизненный Цикл Деятельности

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

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


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


этот код поможет загрузить большое растровое изображение из 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;
    }
}

все решения здесь требуют установки IMAGE_MAX_SIZE. Это ограничивает устройства с более мощным оборудованием, и если размер изображения слишком низок, он выглядит уродливо на экране HD.

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

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