同じことに直面し、議論してくれた@CommonsWareに感謝します。ここに完全な解決策を投稿して、同じ問題でより多くの人がここに来るのを助けます。編集とコメントを歓迎します。乾杯
When should I recycle a bitmap using LRUCache?
正確には、ビットマップがキャッシュになく、ImageViewから参照されていない場合。
ビットマップの参照カウントを維持するには、BitmapDrawableクラスを拡張し、それらに参照属性を追加する必要があります。
このAndroidサンプルには、正確な答えがあります。
DisplayingBitmaps.zip
以下に詳細とコードを示します。
(don't recycle the bitmaps in the entryRemoved() call).
ではない正確に。
entryRemovedデリゲートで、ビットマップがどの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();
}
}
また、 ListView / GridViewアダプタのgetView()は、通常のように正常に見えるはずです。setImageDrawableメソッドを使用してImageViewに新しい画像を設定する場合と同じです。以前のビットマップの参照カウントを内部的にチェックし、lrucacheにない場合は内部でrecycleを呼び出します。
@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();
}
}