我的图像缓存过程是否会泄漏内存?

时间:2014-08-26 15:42:28

标签: android listview caching android-activity

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

这是我的全局图像缓存。偶尔会运行一个函数来查看时间戳并查看超过一小时的事物。

“年龄”是指它从long删除整个条目。

这是我的问题:

假设我有LongSparseArray ActivityListView中的每一行都有一个ListView,其中填充了缓存中的图像。

ImageView

现在,假设用户点击了某个按钮,将其转到新的Bitmap image = ((MyApp)getApplicationContext()).getImage(id); holder.imgImage.setImageBitmap(image); 。在新的Activity上,之前已分配给前一ActivityListView行中某一行的图片。

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

Java LongSparseArray真的能够被回收吗?在Bitmap ImageView的{​​{1}}中,ListView是否仍然引用它?当然,假设Android没有回收Activity使用的内存。

我问这个问题的原因是我以前的老化功能也会调用Activity上的.Recycle()。在这种情况下,当用户点击后退按钮并返回到使用该Bitmap的上一个Activity时,应用程序将崩溃,大概是因为Bitmap不仅丢失了缓存,也来自内存。所以我刚刚删除了Bitmap来电。

顺便说一下,一旦.Recycle()从缓存中删除,并且具有该ID的对象再次显示在屏幕上,应用程序将再次下载Bitmap并将其放入缓存中。如果前一个留在内存中,你可以看到这会产生问题。

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

如果我设置Bitmap会怎样?

有2个myImageView.setDrawingCacheEnabled(false);使用此图片缓存。一个是在用户执行搜索后显示项目列表(及其图像)的搜索屏幕。另一个是用户随后选择保留的项目列表。

3 个答案:

答案 0 :(得分:4)

问题:在位图上调用recycle()方法后,永远不应再次使用位图。如果尝试绘制位图,则会抛出异常。来自docs

  

只有在确定位图为no时才应使用recycle()   更长时间的使用。如果你调用recycle()然后尝试绘制   位图,您将收到错误:" Canvas:尝试使用回收   位图"

在这种特定情况下,您已经回收了位图,但ListViewImageView具有对位图的强引用。当您返回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,以便知道列表视图项何时放入回收站。这是您将减少位图的引用计数的位置。老化函数只有在引用计数为零时才需要回收位图。

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

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

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

答案 1 :(得分:0)

  

这个Bitmap真的能够被Java回收吗?还没有   由前一个ListView中的ImageView引用   活动?当然,假设Android还没有收回   该活动使用的内存。

Bitmap使用了ListView(一个强大的参考资料),因此dalvik无法回收其记忆。

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

  

如果我设置myImageView.setDrawingCacheEnabled(false)会怎么样??

如果您禁用了绘图缓存,则每次需要重新绘制视图时,都会调用onDraw方法。我对ImageView不是很熟悉,您可以去阅读它深刻理解的来源。 (注意:当启用/禁用硬件加速时,绘图缓存的使用会有所不同,这里我假设您正在使用软件渲染)。

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

  1. 当Bitmap缓存变得陈旧时,你将它从缓存数组中删除(然后你的应用程序会尝试获取一个新的,我认为)。
  2. ListView.getView中,您可以检查当前使用的位图是否老化。它应该很简单,因为您知道第一次调用setImageBitmap时的时间戳和最新的时间戳。如果它们不相同,则使用新的位图再次调用setImageBitmap,旧的位图将被回收。
  3. 希望这有帮助。

答案 2 :(得分:0)

关于,"此外,是否有人对更有效的解决方案有任何想法?"

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

Picasso是#34;一款功能强大的Android图像下载和缓存库"

" Android上图像加载的许多常见陷阱由毕加索自动处理:

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