正如谷歌所说,当位图不在Android 3.0下使用时,我们必须手动调用Bitmap.recycle(),因为内存保留在本机堆中.
因此,我们可以为Bitmap提供引用计数,并在使用ListView时检查是否需要在ImageView的onDetachedFromWindow()中回收位图.这是来自Google的演示项目Bitmapfun(ImageFetcher)的解决方案.
但是当使用RecyclerView时,convertview通常会被分离并附加.onDetachedFromWindow()可能会回收位图,所以当它再次附加到父级时,位图就会被回收.
如何处理这个问题?使用RecyclerView时,在Android 3.0下回收位图的正确方法是什么?
这是Googls的Demo BitmapFun(ImageFetcher)的解决方案 - 扩展ImageView:
@Override protected void onDetachedFromWindow() { // This has been detached from Window, so clear the drawable setImageDrawable(null); super.onDetachedFromWindow(); } @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); } 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); } } }
当从内存缓存中删除LruCache时:
@Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) { if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { // The removed entry is a recycling drawable, so notify it // that it has been removed from the memory cache ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
RecyclingBitmapDrawable是:
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()) { if (BuildConfig.DEBUG) { 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(); }
}
重要的一点是在ImageView的onDetachedFromWindow:setImageDrawable(null)意味着清晰的drawable,它可以使当前drawble的ref count = 0并回收它!当使用ListView时,此解决方案很有效,因为仅当ListView的根活动被销毁时才会发生onDetachedFromWindow.
但是使用Recyclering是不同的,这个重用ConvertView的机制与ListView不同.ImageView可能经常分离,而且它不是回收Bitmap的正确时间!