Повторное использование растрового изображения с включенным LargeHeap

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

Но при этом происходит неожиданное поведение, похоже, что recycle() метод растровых изображений не работает в большинстве случаев, и приложение, которое работает в 58 МБ памяти (и превышает иногда бросая OutOfMemoryException) теперь потребляет память в геометрической прогрессии и продолжает расти (сейчас тест, который я проводил, показал выделенную память размером 231 МБ), ожидаемое поведение состоит в том, что управление памятью продолжает работать, и приложение не будет использовать более 60 МБ.

Как я могу избежать этого? Или эффективно перерабатывать растровые изображения?

РЕДАКТИРОВАТЬ: На самом деле, я сделал это дать OutOfMemoryError при выделении более 390 МБ памяти на устройстве. Чтение журналов GC_* показало, что только GC_FOR_ALLOC, который иногда освобождает 3,8 МБ, но почти никогда другие GC запускает, освобождает что-то.

5 ответов

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

  • Эффективная загрузка больших растровых изображений
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;

Это даст вам размер изображения перед загрузкой, и на этой основе вы можете проверить размер вашего устройства и масштабировать его с помощью calculateInSampleSize() а также decodeSampledBitmapFromResource() дано в пояснении к док.

Расчет того, сколько нам нужно масштабировать изображение,

if (imageHeight > reqHeight || imageWidth > reqWidth) {
      if (imageWidth > imageHeight ) {
          inSampleSize = Math.round((float)imageHeight / (float)reqHeight);
      } else {
          inSampleSize = Math.round((float)imageWidth / (float)reqWidth);
      }
    }
int inSampleSize = Math.min(imageWidth / reqWidth,imageHeight / reqHeight);

Вы можете установить inSampleSize,

 options.inSampleSize = inSampleSize;

Тогда, наконец, обязательно позвони,

options.inJustDecodeBounds = false;

иначе он вернет Bitmap как null

  • Обработка растровых изображений вне потока пользовательского интерфейса

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

  • Кэширование растровых изображений

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

  • Очистка кеша

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

    Я создал демонстрационный пример для того же, вы можете скачать здесь

Ваш случай ведет себя как ожидалось. До Сота, recycle() безоговорочно освобождает память. Но в версии 3.0 и выше растровые изображения являются частью обычной памяти, собираемой мусором. У вас достаточно ОЗУ на устройстве, вы позволили JVM выделить больше 58М предела, теперь сборщик мусора удовлетворен и не имеет стимулов для восстановления памяти, занятой вашими растровыми изображениями.

Вы можете убедиться в этом, запустив эмулятор с контролируемым объемом ОЗУ, или загрузив на ваше устройство какой-либо сервис, потребляющий память - GC начнет работать. Вы можете использовать DDMS для дальнейшего изучения использования вашей памяти.

Вы можете попробовать некоторые решения для управления растровой памятью: растровые изображения в Android Утечки памяти растрового изображения http://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/, но начните с официального Советы по растровым изображениям Android, как объяснено в подробном ответе Lalit Poptani.

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

Определенно ответ @Lalit Poptani - способ сделать это, Вы должны действительно измерить Bitmaps если они очень большие. Предпочтительным способом является то, что это сделано server-side если это возможно, так как вы также уменьшите NetworkOperation время.

Что касается реализации MemoryCache а также DiskCache это снова лучший способ сделать это, но я все равно рекомендую использовать существующую библиотеку, которая делает именно это (Ignition), и вы сэкономите много времени, а также много утечек памяти, поскольку Heap не опустошается после GC Я могу предположить, что у вас, вероятно, есть некоторые memory leaks тоже.

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

Перед сотовой структурой битмапы размещались на уровне C++, поэтому использование ОЗУ было невидимым для Java и не могло быть доступно сборщику мусора. Несжатое растровое изображение на 3 Мп с цветовым пространством RGB24 использует около 9-10 мегабайт (около 2048x1512). Таким образом, большие изображения могут легко заполнить вашу кучу. Также помните, что во всем, что используется для видеопамяти (иногда выделенной, иногда совместно используемой системой), данные обычно хранятся без сжатия.

По сути, если вы ориентируетесь на устройства, предшествующие Honeycomb, вам почти необходимо управлять растровым объектом, как если бы вы кодировали программу на C++. Запуск растрового изображения recycle() onDestory() обычно работает, если изображений не так много, но если у вас есть тонна изображений на экране, вам, возможно, придется обрабатывать их на лету. Кроме того, если вы запускаете другое действие, вам, возможно, придется рассмотреть вопрос о включении логики в onPause() и onResume().

Вы также можете кэшировать изображения, используя файловую систему Android или SQLite, когда они не находятся в видеопамяти. Вы можете обойтись без кэширования в оперативной памяти, если вы используете такой формат, как.jpg или.png с большим количеством повторяющихся данных /

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

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

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

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

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

/**
 * Calculates and adjusts the cache size based on amount of memory available and average file size
 * @return
 */
synchronized private int calculateCacheSize(){
    if(this.cachedBitmaps.size()>0){
        long maxMemory = this.getMaxMemory(); // Total max VM memory minus runtime memory 
        long maxAllocation = (long) (ImageCache.MEMORY_FRACTION*maxMemory);
        long avgSize = this.bitmapCacheAllocated / this.cachedBitmaps.size();
        this.bitmapCacheSize = (int) (maxAllocation/avgSize);
    }
    return this.bitmapCacheSize;
}

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

Другие вопросы по тегам