【问题标题】:Is my image caching process going to leak memory?我的图像缓存过程会泄漏内存吗?
【发布时间】:2014-08-26 15:42:27
【问题描述】:

我有一个覆盖Application 对象的对象。在其中,我有一个成员变量,它是 LongSparseArray,其中键是 long 类型的标识符,值是具有 2 个成员变量的对象:Bitmaplong,用作时间戳。

这是我的全局图像缓存。有时,会运行一个函数来查看超过一小时的时间戳和年龄。

“年龄”是指它将整个条目从LongSparseArray 中删除。

这是我的问题:

假设我有一个Activity 和一个ListViewListView 中的每一行都有一个 ImageView,其中填充了缓存中的图像。

Bitmap image = ((MyApp)getApplicationContext()).getImage(id);
holder.imgImage.setImageBitmap(image);

现在,假设用户单击某个按钮,将他们带到一个新的Activity。在这个新的Activity 上,之前分配给ListView 中一行的图像在之前的Activity 年龄。

因此,回顾一下,Bitmap 键/值条目现在不再存在于全局 LongSparseArray 中。

那个Bitmap真的可以被Java回收吗?之前ActivityListView中的ImageView不是还在引用吗?当然,假设 Android 没有回收 Activity 使用的内存。

我问这个的原因是我以前的老化函数也会在Bitmap 上调用.Recycle()。在这种情况下,当用户点击后退按钮并返回到之前使用 BitmapActivity 时,应用程序将崩溃,可能是因为 Bitmap 不仅从缓存中丢失,而且从内存中丢失.所以我刚刚删除了.Recycle() 电话。

顺便说一句,一旦 Bitmap 从缓存中删除,并且具有该 ID 的对象再次出现在屏幕上,应用程序将再次下载 Bitmap 并将其放入缓存中。如果前一个保留在内存中,您可以看到这会带来什么问题。

另外,有人对更有效的解决方案有任何想法吗?

如果我设置myImageView.setDrawingCacheEnabled(false);会发生什么?

有 2 个Activities 使用此图像缓存。一种是在用户执行搜索后显示项目列表(及其图像)的搜索屏幕。另一个是用户选择保留的那些项目的列表。

【问题讨论】:

  • O' CommonsWare,O' CommonsWare,你在哪里 CommonsWare?

标签: android listview caching android-activity


【解决方案1】:

问题:一旦在位图上调用recycle() 方法,就不能再使用该位图。如果尝试绘制位图,则会引发异常。来自docs

只有在确定位图不存在时才应该使用 recycle() 使用时间更长。如果您调用 recycle() 并稍后尝试绘制 位图,你会得到错误:“画布:试图使用回收的 位图”。

在这种特定情况下,您已经回收了位图,但ListView 项目的ImageView 对位图有很强的引用。返回Activity时,ListView项尝试绘制位图,因此抛出异常。

位图内存管理:在Android 2.3.3 之前,位图的支持像素数据存储在本机内存中,而位图本身则存储在 Dalvik 内存中。因此要释放本机内存,必须调用recycle 方法。

这里是Bitmap.recycle函数定义:

    public void recycle() {
        if (!mRecycled) {
            if (nativeRecycle(mNativeBitmap)) {
                // return value indicates whether native pixel object was actually recycled.
                // false indicates that it is still in use at the native level and these
                // objects should not be collected now. They will be collected later when the
                // Bitmap itself is collected.
                mBuffer = null;
                mNinePatchChunk = null;
            }
            mRecycled = true;
        }
    }

发布Android 3.0,支持像素数据也存储在 Dalvik 内存中。当不再需要位图时,我们需要确保我们不持有对位图的任何强引用,以便对其进行垃圾回收。

解决方案:如果您还支持Android 2.3.3及更低版本,您仍然需要使用recycle释放位图。

您可以使用引用计数来跟踪该位图当前是否被ListView 项引用,这样即使它已老化,您也不会在位图上调用recycle

ListView 适配器的getView 方法是位图分配给ImageView 的地方。在这里你增加引用计数。您可以将setRecyclerListener 附加到 ListView 以了解何时将 listview 项放入回收站。这是您将减少位图的引用计数的地方。只有当引用计数为零时,老化函数才需要回收位图。

您也可以考虑使用LruCache 进行缓存,如docs 中所述。

setDrawingCacheEnabled:通过使用 true 参数调用此方法,下次调用 getDrawingCache 会将视图绘制为位图。视图的位图版本可以渲染到屏幕上。由于它只是一个位图,我们不能像使用实际视图那样与它进行交互。几个用例是:

  • ListView 被滚动时,显示项目视图的位图被捕获并呈现。这样滚动的视图就不会经过测量和布局传递。
  • 在 DDMS 中查看层次结构功能。

【讨论】:

  • 我正在考虑切换到LruCache,但我不明白这如何避免您在上面描述的过程。你可以看到我的担忧here。另外,我目前仍然支持 2.3.3,尽管这可能很快就会改变。
  • @Andrew 我已经回答了你的问题here
  • 感谢您的回复,Manish。
【解决方案2】:

那个Bitmap真的可以被Java回收吗?是不是还在 被前一个ListView中的ImageView引用 活动?当然,假设 Android 没有收回 该 Activity 使用的内存。

Bitmap 仍用于ListView(强引用),因此 dalvik 无法回收其内存。

显然你不能在 Bitmap 上调用recycle 否则会发生不好的事情(例如应用程序崩溃)。

如果我设置 myImageView.setDrawingCacheEnabled(false); 会发生什么?

如果禁用绘图缓存,每次需要重绘视图时,都会调用onDraw方法。我对ImageView不是很熟悉,你可以去阅读它的源码来深入了解。 (注意:硬件加速开启/关闭时,绘图缓存的使用是不同的,这里我假设你使用的是软件渲染)。

对于解决方案,您可以尝试以下方法:

  1. 当位图缓存变得陈旧时,您将其从缓存数组中删除(然后您的应用程序将尝试获取一个新缓存,我认为)。
  2. ListView.getView,可以查看当前使用的Bitmap是否有年龄。这应该很容易,因为您知道第一次调用setImageBitmap 时的时间戳和最新的时间戳。如果它们不一样,您使用新的位图再次调用setImageBitmap,旧的位图将被回收。

希望这会有所帮助。

【讨论】:

  • 每次调用getView() 时,它都会访问图像的缓存。问题是,如果位于Activity 堆栈中间的Activity 有一个使用已从缓存中弹出的旧图像之一的ImageView 怎么办?有没有办法,在onPause() 期间,我可以告诉所有ActivityImageViews 停止指向位图?
  • 这似乎是很多很多很多应用都应该有的问题。那么他们都是如何解决的呢?为什么我以前从未见过这种讨论?
【解决方案3】:

关于“另外,有人对更有效的解决方案有任何想法吗?”

毕加索图书馆将帮助解决您面临的问题http://square.github.io/picasso/

Picasso 是“一个强大的 Android 图像下载和缓存库”

“Android 上许多常见的图像加载陷阱都由 Picasso 自动处理:

  • 在适配器中处理 ImageView 回收和下载取消。
  • 自动内存和磁盘缓存。”

【讨论】:

    猜你喜欢
    • 2012-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多