Android如何在使用RecyclerView时正确地回收位图?

11

按照Google的说法,我们必须在Android 3.0以下版本中手动调用Bitmap.recycle()以释放本地堆中的内存。

因此,在使用ListView时,我们可以为Bitmap设置一个引用计数器,并在ImageView的onDetachedFromWindow()方法中检查是否需要回收bitmap。这是Google的演示项目Bitmapfun(ImageFetcher)中提出的解决方案。

但在使用RecyclerView时,convertView经常被分离和连接。onDetachedFromWindow()可能会回收位图,因此当它再次附加到父级时,位图已被回收。

如何解决这个问题?在使用RecyclerView时,如何正确地回收位图?

以下是Google的演示项目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,这可能会使当前drawable的引用计数= 0并回收它!该解决方案在使用ListView时效果很好,因为onDetachedFromWindow仅在销毁ListView的根活动时发生。

但是,在RecyclerView中使用Recycling不同,这种重复使用ConvertView的机制与ListView不同。ImageView可能经常分离,这不是回收位图的正确时间!

1个回答

0
你可以使用Glide来实现这个功能。Glide能够高效地加载图片,并且你可以在需要的时候请求它清除资源。
为了实现这个功能,你需要在RecyclerView的适配器中使用onViewRecycled()方法。你可以像这样使用Glide的clear()方法来清除资源:
override fun onViewRecycled(holder: VHCustom) {
        super.onViewRecycled(holder)
        Glide.with(ctx).clear(holder.imageView)
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接