Intereting Posts
Лотские свойства и значения Kotlin сброшены: сбрасываемый ленивый делегат Как проверить, существует ли значение в HashMap Предупреждение при отмене меню ActionBar Overflow на Android 4.1.x Как я могу получить временную метку видеокадра во время записи? Ширина экрана Проблема при использовании разных браузеров на ручных / интеллектуальных устройствах Android GreenDao – удаляет только кешированные объекты определенной сущности Выделение цвета текста с помощью Html.fromHtml () в Android? Нестандартные атрибуты пользовательского вида Android не работают после переключения на градус Android – Как измерить качество полосы пропускания в локальной сети одним телефоном Android: Как изменить текст в поле EditText? Пользовательские шрифты ttf не отображаются должным образом в TextView на Android 4.4 KitKat Как получить имя файла от намерения? Viewpager setonpagechangelistener устарел Создание ключа API для открытия карты Google Как получить эффект ряби на элементах меню на моей настраиваемой панели инструментов?

Когда следует перерабатывать растровое изображение с использованием LRUCache?

Я использую LRUCache для кэширования растровых изображений, которые хранятся в файловой системе. Я построил кеш, основанный на примерах здесь: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

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

Я добавил вызов Bitmap.recycle (), когда изображение выведено:

  // use 1/8 of the available memory for this memory cache final int cacheSize = 1024 * 1024 * memClass / 8; mImageCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldBitmap, Bitmap newBitmap) { oldBitmap.recycle(); oldBitmap = null; } }; 

Это исправляет сбои, однако это также приводит к тому, что изображения иногда не появляются в приложении (просто черное пространство, где должно быть изображение). В любое время, когда это происходит, я вижу это сообщение в своем Logcat: Cannot generate texture from bitmap .

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

Так что здесь происходит? Почему переработанные изображения все еще находятся в LRUCache, если я только перерабатываю их после их удаления? Какова альтернатива для реализации кеша? В документах Android четко указано, что LRUCache – это путь, но они не упоминают о необходимости переработать растровые изображения или как это сделать.

ПОСТАНОВИЛИ: В случае, если это полезно кому-либо еще, решение этой проблемы, предложенное принятым ответом, НЕ должно делать то, что я сделал в приведенном выше примере кода (не перерабатывайте растровые изображения в entryRemoved() ).

Вместо этого, когда вы закончите работу с ImageView (например, onPause() в onPause() или когда представление переработано в адаптере), проверьте, все ли isImageInCache() в кеше (я добавил метод isImageInCache() в свой кеш Класс), а если нет, то переработайте растровое изображение. В противном случае оставьте это в покое. Это фиксировало мои исключения OutOfMemory и предотвратило использование растровых изображений, которые все еще использовались.

Solutions Collecting From Web of "Когда следует перерабатывать растровое изображение с использованием LRUCache?"

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

Этого не будет, пока Bitmap будет переработан или собран из мусора.

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

Вот почему вы не должны перерабатывать там.

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

Предположительно, они не находятся в LRUCache . Они находятся в ImageView или что-то еще, которое все еще использует Bitmap .

Какова альтернатива для реализации кеша?

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

Когда вы закончите с Bitmap (например, строка в ListView переработана), вы проверяете, все еще ли она в кеше. Если это так, вы оставите его в покое. Если это не так, вы recycle() .

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

BTW, если вы находитесь на уровне API 11+, подумайте об использовании inBitmap . OutOMemoryErrors запускаются, когда распределение не может быть выполнено. Последнее, что я проверил, у Android нет сборщика мусора, поэтому вы можете получить OutOfMemoryError из-за фрагментации (хотите выделить что-то большее, чем самый большой единственный доступный блок).

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

  When should I recycle a bitmap using LRUCache? 
  • Точно, когда ваш битмап не находится ни в кеше, ни в ссылках с любого ImageView.

  • Чтобы поддерживать ссылочный счет битмапа, мы должны расширить класс BitmapDrawable и добавить к ним ссылочные атрибуты.

  • Этот образец андроида отвечает на это точно. DisplayingBitmaps.zip

Мы перейдем к деталям и коду ниже.

 (don't recycle the bitmaps in the entryRemoved() call). 

Не совсем.

  • В делегате entryRemoved проверьте, по-прежнему ли Bitmap ссылается на любой ImageView. Если не. Переработайте его там сами.

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

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

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

RecyclingBitmapDrawable.java и RecyclingImageView.java образца, упомянутого выше, являются основными элементами, которые нам нужны здесь. Они прекрасно справляются с вещами. Их методы setIsCached и setIsDisplayed делают то, что нам нужно.

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

— Вот код для вас —

Поэтому ваш менеджер LruCache должен выглядеть примерно так.

LruCacheManager.java

 package com.example.cache; import android.os.Build; import android.support.v4.util.LruCache; public class LruCacheManager { private LruCache<String, RecyclingBitmapDrawable> mMemoryCache; private static LruCacheManager instance; public static LruCacheManager getInstance() { if(instance == null) { instance = new LruCacheManager(); instance.init(); } return instance; } private void init() { // We are declaring a cache of 6Mb for our use. // You need to calculate this on the basis of your need mMemoryCache = new LruCache<String, RecyclingBitmapDrawable>(6 * 1024 * 1024) { @Override protected int sizeOf(String key, RecyclingBitmapDrawable bitmapDrawable) { // The cache size will be measured in kilobytes rather than // number of items. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { return bitmapDrawable.getBitmap().getByteCount() ; } else { return bitmapDrawable.getBitmap().getRowBytes() * bitmapDrawable.getBitmap().getHeight(); } } @Override protected void entryRemoved(boolean evicted, String key, RecyclingBitmapDrawable oldValue, RecyclingBitmapDrawable newValue) { super.entryRemoved(evicted, key, oldValue, newValue); oldValue.setIsCached(false); } }; } public void addBitmapToMemoryCache(String key, RecyclingBitmapDrawable bitmapDrawable) { if (getBitmapFromMemCache(key) == null) { // The removed entry is a recycling drawable, so notify it // that it has been added into the memory cache bitmapDrawable.setIsCached(true); mMemoryCache.put(key, bitmapDrawable); } } public RecyclingBitmapDrawable getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } public void clear() { mMemoryCache.evictAll(); } } 

И ваш getView () адаптера ListView / GridView должен выглядеть нормально, как обычно. Как при установке нового изображения в ImageView с использованием метода setImageDrawable. Его внутренняя проверка ссылочного счета на предыдущем растровом изображении и вызовет его переработку внутри, если не в lrucache.

 @Override public View getView(int position, View convertView, ViewGroup parent) { RecyclingImageView imageView; if (convertView == null) { // if it's not recycled, initialize some attributes imageView = new RecyclingImageView(getActivity()); imageView.setLayoutParams(new GridView.LayoutParams( GridView.LayoutParams.WRAP_CONTENT, GridView.LayoutParams.WRAP_CONTENT)); imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); imageView.setPadding(5, 5, 5, 5); } else { imageView = (RecyclingImageView) convertView; } MyDataObject dataItem = (MyDataObject) getItem(position); RecyclingBitmapDrawable image = lruCacheManager.getBitmapFromMemCache(dataItem.getId()); if(image != null) { // This internally is checking reference count on previous bitmap it used. imageView.setImageDrawable(image); } else { // You have to implement this method as per your code structure. // But it basically doing is preparing bitmap in the background // and adding that to LruCache. // Also it is setting the empty view till bitmap gets loaded. // once loaded it just need to call notifyDataSetChanged of adapter. loadImage(dataItem.getId(), R.drawable.empty_view); } return imageView; } 

Вот ваш RecyclingImageView.java

 /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.cache; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; import android.widget.ImageView; /** * Sub-class of ImageView which automatically notifies the drawable when it is * being displayed. */ public class RecyclingImageView extends ImageView { public RecyclingImageView(Context context) { super(context); } public RecyclingImageView(Context context, AttributeSet attrs) { super(context, attrs); } /** * @see android.widget.ImageView#onDetachedFromWindow() */ @Override protected void onDetachedFromWindow() { // This has been detached from Window, so clear the drawable setImageDrawable(null); super.onDetachedFromWindow(); } /** * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable) */ @Override public void setImageDrawable(Drawable drawable) { // Keep hold of previous Drawable final Drawable previousDrawable = getDrawable(); // Call super to set new Drawable super.setImageDrawable(drawable); // Notify new Drawable that it is being displayed notifyDrawable(drawable, true); // Notify old Drawable so it is no longer being displayed notifyDrawable(previousDrawable, false); } /** * @see android.widget.ImageView#setImageResource(android.graphics.drawable.Drawable) */ @Override public void setImageResource(int resId) { // Keep hold of previous Drawable final Drawable previousDrawable = getDrawable(); // Call super to set new Drawable super.setImageResource(resId); // Notify old Drawable so it is no longer being displayed notifyDrawable(previousDrawable, false); } /** * Notifies the drawable that it's displayed state has changed. * * @param drawable * @param isDisplayed */ private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) { if (drawable instanceof RecyclingBitmapDrawable) { // The drawable is a CountingBitmapDrawable, so notify it ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed); } else if (drawable instanceof LayerDrawable) { // The drawable is a LayerDrawable, so recurse on each layer LayerDrawable layerDrawable = (LayerDrawable) drawable; for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) { notifyDrawable(layerDrawable.getDrawable(i), isDisplayed); } } } } 

Вот ваш RecyclingBitmapDrawable.java

 /* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.cache; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.util.Log; /** * A BitmapDrawable that keeps track of whether it is being displayed or cached. * When the drawable is no longer being displayed or cached, * {@link android.graphics.Bitmap#recycle() recycle()} will be called on this drawable's bitmap. */ public class RecyclingBitmapDrawable extends BitmapDrawable { static final String TAG = "CountingBitmapDrawable"; private int mCacheRefCount = 0; private int mDisplayRefCount = 0; private boolean mHasBeenDisplayed; public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) { super(res, bitmap); } /** * Notify the drawable that the displayed state has changed. Internally a * count is kept so that the drawable knows when it is no longer being * displayed. * * @param isDisplayed - Whether the drawable is being displayed or not */ public void setIsDisplayed(boolean isDisplayed) { //BEGIN_INCLUDE(set_is_displayed) synchronized (this) { if (isDisplayed) { mDisplayRefCount++; mHasBeenDisplayed = true; } else { mDisplayRefCount--; } } // Check to see if recycle() can be called checkState(); //END_INCLUDE(set_is_displayed) } /** * Notify the drawable that the cache state has changed. Internally a count * is kept so that the drawable knows when it is no longer being cached. * * @param isCached - Whether the drawable is being cached or not */ public void setIsCached(boolean isCached) { //BEGIN_INCLUDE(set_is_cached) synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } // Check to see if recycle() can be called checkState(); //END_INCLUDE(set_is_cached) } private synchronized void checkState() { //BEGIN_INCLUDE(check_state) // If the drawable cache and display ref counts = 0, and this drawable // has been displayed, then recycle if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { Log.d(TAG, "No longer being used or cached so recycling. " + toString()); getBitmap().recycle(); } //END_INCLUDE(check_state) } private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); } }