English 中文(简体)
我什么时候应该用路卡来回收一个位图?
原标题:When should I recycle a bitmap using LRUCache?

我使用 < code> LRUCAche 来缓存存储在文件系统中的位图。 我根据下面的例子建立了缓存 : < a href="http:// developinger.android.com/training/displaying-bittmaps/cache-bittmap.html" rel=“ noreferrerr” > > http:// developinger.android.com/ training/displaying-bittmaps/cache-bittmap.html

问题在于我在使用应用程序时经常看到 Outof Memory 撞车。 我相信当路运联盟驱逐一个图像让另一个图像腾出空间时, 记忆并没有被解开。

我给Bitmap. recurcy ()加了一个电话,

  // 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;
                }
            };

这修正了崩溃, 但是它也导致图像 < em> sometimes 没有出现在应用程序中( 只是图像应该出现在一个黑色空间 ) 。 任何时间我都会在我的 Logcat 中看到此信息 : Cannot 生成来自位图 的纹理 。

快速谷歌搜索显示,之所以发生这种情况,是因为正在显示的图像已被回收。

So what is happening here? Why are recycled images still in the LRUCache if I m only recycling them after they ve been removed? What is the alternative for implementing a cache? The Android docs clearly state that LRUCache is the way to go, but they do not mention the need to recycle bitmaps or how to do so.

RESOLVED: In case its useful to anyone else, the solution to this problem as suggested by the accepted answer is to NOT do what I did in the code example above (don t recycle the bitmaps in the entryRemoved() call).

相反,当您在一项活动中用图像View(例如 onPause () ) 完成一次活动时,或者当一个视图在适配器中被回收时) 检查位图是否还在缓存中(我在我的缓存类中添加了 is ImaageInCache () 方法),如果不是,则对位图进行再循环。否则,就不要管它了。这固定了我的 < code> OutteofMemory 例外,并防止了仍在使用的回收位图 。

最佳回答

我相信,当路运联盟驱逐一个图像给另一个图像腾出空间时,记忆并没有释放出来。

直到回收或垃圾收集Bitmap 为止,不会是回收或垃圾收集。

快速谷歌搜索显示,之所以发生这种情况,是因为正在显示的图像已被回收。

这就是为什么你不应该在那里回收。

为什么再生图像在LRUCAche中仍然存在?

估计它们不在 < code> LRUCAche 中,而是在 < code> ImageView 或仍在使用 < code> Bitmap 的其他东西中。

实施暗藏的替代办法是什么?

为了论证,让我们假设您正在使用 Bitmap 部件中的 ImageView 对象,例如 ListView 的行。

当您使用 Bitmap (例如, ListView 中的行将被回收) 完成后, 您会检查是否还在缓存中。 如果还在缓存中, 您就不要管它。 否则, 您会使用 < code> recurcy () 。

缓存只是让您知道哪些 Bitmap 对象值得保留。 该缓存无法知道 Bitmap 是否仍在某处使用 。

BTW,如果您在 API 11+ 级别上, 请考虑使用 < a href=" http:// developmenter.android. com/ reference/android/graphics/ Bitmap Factory. options. html#inBitmap" rel= “ noreferrer" {pcode> in Bitmap 。 OutomemoryErrors 在无法完成分配时会被触发。 最后我检查了, 机器人没有压缩的垃圾收集器, 以便您可以得到一个由于破碎( 想要分配比最大单个可用区块更大的部分) 。

问题回答

Faced the same and thanks to @CommonsWare for the discussion. Posting the full solution here so it helps more people coming here for the same issue. Edits and Comments are welcomed. Cheers

 When should I recycle a bitmap using LRUCache?
  • 当你的Bitmap既不在缓存 也不在任何图像视图中被引用时

  • To maintain the reference count of bitmap we have to extend the BitmapDrawable class and add reference attributes to them.

  • This android sample has the answer to it exactly. DisplayingBitmaps.zip

我们会找到下面的细节和代码

(don t recycle the bitmaps in the entryRemoved() call).

不尽然

  • In entryRemoved delegate check whether Bitmap is still referenced from any ImageView. If not. Recycle it there itself.

  • 而反之亦然,在公认的答案中也提到了这一点,即当视图即将被再利用或被倾弃时检查其位图(在视图被再利用时,前位位图)在缓存中。如果存在的话,就让它单独回收它。

  • 关键是我们要在这两个地方 检查一下我们是否可以回收比特图

I will explain my specific case where i am using LruCache to hold bitmaps for me. And displaying them in ListView. And calling recycle on bitmaps when there are no longer in use.

RecyclingBitmapDrawable.java and RecyclingImageView.java of the sample mentioned above are the core pieces we need here. They are handling things beautifully. Their setIsCached and setIsDisplayed methods are doing what we need.

代码可以在上文提到的样本链接中找到。 但也可以在答案底部张贴完整的文件代码, 以防链接今后会下降或更改。 略微修改了超标设置图像Resource, 以检查上一个位图的状态 。

- 这是给你的密码...

所以,你的LruCache经理应该 看起来像这样。

LruCacheManache.java 管理者.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();
    }
}


And your getView() of ListView/GridView adapter should look normal like usual. As when you are setting a new image on ImageView using setImageDrawable method. Its internally checking the reference count on previous bitmap and will call recycle on it internally if not in 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;

    }

这里是您的 回收图像View.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);
            }
        }
    }

}

这里是您的 回收BitmapDrawable.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();
    }

}




相关问题
Android - ListView fling gesture triggers context menu

I m relatively new to Android development. I m developing an app with a ListView. I ve followed the info in #1338475 and have my app recognizing the fling gesture, but after the gesture is complete, ...

AsyncTask and error handling on Android

I m converting my code from using Handler to AsyncTask. The latter is great at what it does - asynchronous updates and handling of results in the main UI thread. What s unclear to me is how to handle ...

Android intent filter for a particular file extension?

I want to be able to download a file with a particular extension from the net, and have it passed to my application to deal with it, but I haven t been able to figure out the intent filter. The ...

Android & Web: What is the equivalent style for the web?

I am quite impressed by the workflow I follow when developing Android applications: Define a layout in an xml file and then write all the code in a code-behind style. Is there an equivalent style for ...

TiledLayer equivalent in Android [duplicate]

To draw landscapes, backgrounds with patterns etc, we used TiledLayer in J2ME. Is there an android counterpart for that. Does android provide an option to set such tiled patterns in the layout XML?

Using Repo with Msysgit

When following the Android Open Source Project instructions on installing repo for use with Git, after running the repo init command, I run into this error: /c/Users/Andrew Rabon/bin/repo: line ...

Android "single top" launch mode and onNewIntent method

I read in the Android documentation that by setting my Activity s launchMode property to singleTop OR by adding the FLAG_ACTIVITY_SINGLE_TOP flag to my Intent, that calling startActivity(intent) would ...

From Web Development to Android Development

I have pretty good skills in PHP , Mysql and Javascript for a junior developer. If I wanted to try my hand as Android Development do you think I might find it tough ? Also what new languages would I ...

热门标签