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가 갈 길이라고 분명히 명시되어 있지만 비트 맵을 재활용해야 할 필요성이나 그렇게하는 방법에 대해서는 언급하지 않습니다.
해결 : 경우에 다른 사람과의 유용, 허용 대답에 의해 제안이 문제의 해결책입니다 NOT I합니다 (에서 비트 맵을 재활용하지 않습니다 위의 코드 예제에서했던 일을 entryRemoved()
호출).
대신 ImageView 사용을 마쳤을 때 (예 : onPause()
활동에서 또는보기가 어댑터에서 재활용되는 경우) 비트 맵이 여전히 캐시 isImageInCache()
에 있는지 (캐시 클래스에 메서드를 추가했습니다 ) 확인하고 그렇지 않으면 비트 맵을 재활용하십시오. 그렇지 않으면 그대로 두십시오. 이것은 내 OutOfMemory
예외를 수정하고 여전히 사용중인 비트 맵을 재활용하는 것을 방지했습니다.
LRUCache가 다른 이미지를위한 공간을 만들기 위해 이미지를 제거 할 때 메모리가 해제되지 않는다고 생각합니다.
를 Bitmap
재활용하거나 쓰레기 수거 할 때까지는 그렇지 않습니다 .
빠른 Google 검색을 통해 표시되는 이미지가 재활용 되었기 때문에 이런 일이 발생하고 있음을 알 수 있습니다.
그렇기 때문에 그곳에서 재활용해서는 안됩니다.
재활용 된 이미지를 제거한 후에 만 재활용하는 경우 왜 LRUCache에 여전히 남아 있습니까?
아마도 그들은 LRUCache
. 그들은 ImageView
여전히 Bitmap
.
캐시를 구현하는 대안은 무엇입니까?
인수를 위해 .NET Framework와 같은 위젯 에서 Bitmap
개체를 사용한다고 가정 해 보겠습니다 .ImageView
ListView
작업을 마치면 Bitmap
(예 : a의 행 ListView
이 재활용 됨) 캐시에 여전히 있는지 확인합니다. 그렇다면 그대로 두십시오. 그렇지 않다면 바로 당신 recycle()
입니다.
캐시는 단순히 보유 할 Bitmap
가치가 있는 객체 를 알려 줍니다. 캐시는 Bitmap
어딘가에서 여전히 사용되고 있는지 알 수있는 방법이 없습니다 .
BTW, API 레벨 11 이상인 경우 inBitmap
. OutOMemoryErrors
할당을 수행 할 수 없을 때 트리거됩니다. 마지막으로 확인한 결과, Android에는 압축 가비지 수집기가 없으므로 OutOfMemoryError
조각화로 인해 발생할 수 있습니다 (사용 가능한 가장 큰 단일 블록보다 큰 것을 할당하려는 경우).
같은 문제에 직면했으며 토론에 대해 @CommonsWare에게 감사드립니다. 여기에 전체 솔루션을 게시하면 더 많은 사람들이 동일한 문제로 여기에 올 수 있습니다. 편집 및 의견을 환영합니다. 건배
When should I recycle a bitmap using LRUCache?
Bitmap이 캐시에 없거나 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();
}
}
그리고 ListView / GridView 어댑터의 getView () 는 평소처럼 정상적으로 보일 것입니다. setImageDrawable 메서드를 사용하여 ImageView에서 새 이미지를 설정할 때와 같습니다. 내부적으로 이전 비트 맵의 참조 횟수를 확인하고 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();
}
}
참조 URL : https://stackoverflow.com/questions/10743381/when-should-i-recycle-a-bitmap-using-lrucache
'programing' 카테고리의 다른 글
git repo를 두 번째 컴퓨터로 이동 하시겠습니까? (0) | 2021.01.15 |
---|---|
여러 파일 입력을 사용할 때 선택한 최대 파일을 제한하는 방법 (0) | 2021.01.15 |
removeAllViews ()와 removeAllViewsInLayout ()의 차이점은 무엇입니까? (0) | 2021.01.15 |
인수 또는 널이 전달되지 않은 경우 Java 3 도트 매개 변수 (varargs) 동작 (0) | 2021.01.15 |
setup.py의 entry_points / console_scripts와 스크립트의 차이점은 무엇입니까? (0) | 2021.01.15 |