Ява.ленг.Исключение OutOfMemoryError: Bitmap с размерами превышает ВМ бюджет - Андроид


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

приложение запускается один раз, заполняет информацию на экране (Layouts,Listviews,Textviews,ImageViews, etc) и потребитель читает информацию.

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

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

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

Итак, каков наилучший / правильный способ обработки многих изображений?

должен ли я поместить их в статические методы, чтобы они не загружались все время? Должен ли я очистить макет или изображения, используемые в макете особым образом?

13 154

13 ответов:

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

трудно сказать, почему это не глядя на ваш код. Однако в этой статье есть несколько советов, которые могут помочь:

http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html

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

одной из наиболее распространенных ошибок, которые я нашел при разработке приложений для Android, является "java.ленг.Исключение OutOfMemoryError: Bitmap с размерами превышает ВМ бюджет" ошибка. Я часто находил эту ошибку в действиях, использующих много растровых изображений после изменения ориентации: действие уничтожается, создается снова, а макеты "раздуваются" из XML, потребляя память виртуальной машины, доступную для растровых изображений.

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

во-первых, установите атрибут "id" в Родительском представлении вашего XML-макета:

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

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

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

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

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

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

  1. удаляет обратные вызовы на всех фоновых чертежей
  2. удаляет детей из каждой группы просмотра

чтобы избежать этой проблемы вы можете использовать собственный метод Bitmap.recycle() до null - ing Bitmap объект (или установка другого значения). Пример:

public final void setMyBitmap(Bitmap bitmap) {
  if (this.myBitmap != null) {
    this.myBitmap.recycle();
  }
  this.myBitmap = bitmap;
}

и далее вы можете изменить myBitmap без вызова System.gc() как:

setMyBitmap(null);    
setMyBitmap(anotherBitmap);

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

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

Это объяснение может помочь: http://code.google.com/p/android/issues/detail?id=8488#c80

"Быстрые Подсказки:

1) никогда не вызывайте систему.ГХ() себя. Это было распространено как исправление здесь, и это не работает. Не делай этого. Если вы заметили в моем объяснении, прежде чем получить OutOfMemoryError, JVM уже запускает сборку мусора, поэтому нет причин делать это снова (его замедление вашей программы). Выполнение одного в конце вашей деятельности-это просто скрываю проблему. Это может привести к тому, что растровое изображение будет помещено в очередь финализатора быстрее, но нет причин, по которым вы не могли бы просто вызвать recycle для каждого растрового изображения.

2) всегда вызывайте recycle () на растровых изображениях, которые вам больше не нужны. По крайней мере, в onDestroy вашей деятельности пройти и переработать все растровые изображения, которые вы использовали. Кроме того, если вы хотите, чтобы растровые экземпляры собирались из кучи dalvik быстрее, не помешает очистить любые ссылки на битовый массив.

3) вызов recycle () и затем System.gc () все еще не может удалить растровое изображение из кучи Dalvik. Не беспокойтесь об этом. recycle () выполнил свою работу и освободил собственную память, просто потребуется некоторое время, чтобы выполнить шаги, которые я описал ранее, чтобы фактически удалить растровое изображение из кучи Dalvik. Это не имеет большого значения, потому что большой кусок родной памяти уже свободен!

4) всегда предполагайте, что есть ошибка в рамках последнего. Дальвик-это делать именно то, что он должен делать. Это может быть не то, что вы ожидаете или что вы хотите, но его, как это работает. "

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

С. П. Сначала я пытался уменьшить размер изображения без масштабирования изображения. Это не остановило ошибку.

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

  1. использовать контекст приложения (вместо действия.это) где только возможно.
  2. Остановите и отпустите свои потоки в методе onPause () действия
  3. отпустите свои представления / обратные вызовы в методе onDestroy () activity

Я предлагаю удобный способ решить эту проблему. Просто назначьте атрибут "android: configChanges" значение, как следует в Mainfest.xml для вашей ошибочной деятельности. вот так:

<activity android:name=".main.MainActivity"
              android:label="mainActivity"
              android:configChanges="orientation|keyboardHidden|navigation">
</activity>

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

как описано в OOM, я использовал слишком много памяти во время выполнения. Итак, я уменьшаю размер изображения в ~ / res / drawable моего проекта. Например, сверхквалифицированное изображение, которое имеет разрешение 128X128, может быть изменено до 64x64, что также подходит для моего приложения. И после того, как я сделал это с кучей фотографий, ошибка OOM больше не возникает.

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

мое приложение будет выдавать ошибку outofmemory всякий раз, когда пользователь переходит от одного действия к другому. Установка моих чертежей в null и вызывающая система.ГК() не работа, как и переработка моих bitmapDrawables с getBitMap ().рециркулировать.)( Android будет продолжать выбрасывать ошибку outofmemory с первым подходом, и он будет выбрасывать сообщение об ошибке canvas всякий раз, когда он пытался использовать переработанное растровое изображение со вторым подходом.

Я взял еще третий подход. Я установил все представления в нуль, а фон-в черный. Я делаю эту очистку в моем методе onStop (). Это метод, который вызывается, как только действие больше не видно. Если вы это сделаете это в методе onPause (), пользователи будут видеть черный фон. Не идеальный. Что касается выполнения этого в методе onDestroy (), нет никакой гарантии, что он будет вызван.

чтобы предотвратить появление черного экрана, если пользователь нажимает кнопку "назад" на устройстве, я перезагружаю активность в методе onRestart (), вызывая методы startActivity(getIntent ()), а затем finish ().

Примечание: На самом деле нет необходимости менять фон на черный.

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

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

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

в моем случае код выглядит так это:

private static BitmapDrawable currentBGDrawable;

if (new File(uriString).exists()) {
    if (!uriString.equals(currentBGUri)) {
        freeBackground();
        bg = BitmapFactory.decodeFile(uriString);

        currentBGUri = uriString;
        bgDrawable = new BitmapDrawable(bg);
        currentBGDrawable = bgDrawable;
    } else {
        bgDrawable = currentBGDrawable;
    }
}

У меня была такая же проблема только с переключением на фоне картинки с разумными размерами. Я получил лучшие результаты с установкой ImageView в null перед вводом нового изображения.

ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));

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

/**
 * Lightweight cache for Bitmap objects. 
 * 
 * There is no thread-safety built into this class. 
 * 
 * Note: you may wish to create bitmaps using the application-context, rather than the activity-context. 
 * I believe the activity-context has a reference to the Activity object. 
 * So for as long as the bitmap exists, it will have an indirect link to the activity, 
 * and prevent the garbaage collector from disposing the activity object, leading to memory leaks. 
 */
public class BitmapCache { 

    private Hashtable<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();  

    private StringBuilder sb = new StringBuilder(); 

    public BitmapCache() { 
    } 

    /**
     * A Bitmap with the given width and height will be returned. 
     * It is removed from the cache. 
     * 
     * An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen.  
     * 
     * Note that thread-safety is the caller's responsibility. 
     */
    public Bitmap get(int width, int height, Bitmap.Config config) { 
        String key = getKey(width, height, config); 
        ArrayList<Bitmap> list = getList(key); 
        int listSize = list.size();
        if (listSize>0) { 
            return list.remove(listSize-1); 
        } else { 
            try { 
                return Bitmap.createBitmap(width, height, config);
            } catch (RuntimeException e) { 
                // TODO: Test appendHockeyApp() works. 
                App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height); 
                throw e ; 
            }
        }
    }

    /**
     * Puts a Bitmap object into the cache. 
     * 
     * Note that thread-safety is the caller's responsibility. 
     */
    public void put(Bitmap bitmap) { 
        if (bitmap==null) return ; 
        String key = getKey(bitmap); 
        ArrayList<Bitmap> list = getList(key); 
        list.add(bitmap); 
    }

    private ArrayList<Bitmap> getList(String key) {
        ArrayList<Bitmap> list = hashtable.get(key);
        if (list==null) { 
            list = new ArrayList<Bitmap>(); 
            hashtable.put(key, list); 
        }
        return list;
    } 

    private String getKey(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        Config config = bitmap.getConfig();
        return getKey(width, height, config);
    }

    private String getKey(int width, int height, Config config) {
        sb.setLength(0); 
        sb.append(width); 
        sb.append("x"); 
        sb.append(height); 
        sb.append(" "); 
        switch (config) {
        case ALPHA_8:
            sb.append("ALPHA_8"); 
            break;
        case ARGB_4444:
            sb.append("ARGB_4444"); 
            break;
        case ARGB_8888:
            sb.append("ARGB_8888"); 
            break;
        case RGB_565:
            sb.append("RGB_565"); 
            break;
        default:
            sb.append("unknown"); 
            break; 
        }
        return sb.toString();
    }

}