programing

LRUCache를 사용하여 비트 맵을 언제 재활용해야합니까?

copyandpastes 2021. 1. 15. 20:18
반응형

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개체를 사용한다고 가정 해 보겠습니다 .ImageViewListView

작업을 마치면 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.javaRecyclingImageView.java 가 여기에 필요한 핵심 부분입니다. 그들은 일을 아름답게 처리하고 있습니다. 그들의 setIsCachedsetIsDisplayed 메소드는 우리가 필요한 것을 수행합니다.

코드는 위에서 언급 한 샘플 링크에서 찾을 수 있습니다. 그러나 향후 링크가 다운되거나 변경 될 경우를 대비하여 답변 하단에 파일의 전체 코드를 게시하십시오. 이전 비트 맵의 ​​상태를 확인하기 위해 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

반응형