Android Honeycomb中的Bitmap#recycle()实际上是做什么的?

15
我正在为Android Honeycomb编写一个内存使用量很大的应用程序,并且我一直非常小心地在尽可能的情况下回收未使用的位图;实际上,这对于应用程序的正常运行是必要的,因为位图不断地在内存中循环。然而,我刚刚在Activity中实现了onConfigurationChanged(),因此(出于许多原因),我正在尝试将内存释放例程放入onStop()中。
目前我的onStop()方法:
- 将一些View设置为显示默认的Drawable; - 对这些View之前使用过的Bitmap调用recycle (); - 将对Bitmap的引用设置为null。
不幸的是,使用Eclipse内存分析器,似乎这对内存使用没有任何影响。
如你所想象的,在努力释放资源的垃圾收集语言中,我本来希望能有更多的效果。因此,我的问题是:recycle()到底是做什么的?它是否会真正触发垃圾收集,或者即使你调用System.gc(),系统是否会保留该内存,直到它需要清除某些内容?
我知道Bitmap实际上并没有保存在常规堆中,但我认为调用recycle()就足以确保它们从本机堆中删除。
部分答案:
我发现,如果ImageView包含已经被回收的Bitmap,则该Bitmap数据仍会保留在内存中,直到在ImageView上调用setImageBitmap(null)。即使调用setImageResource(...)或setImageDrawable(...)(它们加载了一个相对较小的Nine-patch文件),这也可能是情况(然而MAT分析显示这并没有移除大的Bitmap,它包含在ImageView的私有成员中)。在onStop()中直接调用此函数可以从我们应用程序的堆中削减约10MB。不过,对于Android的早期版本,情况可能并非如此。

我已经实际步入了回收它们的代码,并在调试器中检查了isRecycled(),它返回了true - Andrew Wyld
哦,我明白你的意思了,抱歉(今天过得很长)。我的意思是,这个内存现在会被释放吗?还是等到 Android 感觉合适的时候才会释放? - Andrew Wyld
3个回答

6
正如Justin所说,位图数据并不是在VM堆中分配的。VM堆中有对它的引用(VM堆很小),但实际的数据是由底层Skia图形库在本地堆中分配的。[注意,在较新的Android版本中可能已经改变,但对于2.1和2.2是正确的]
当您执行recycle()时,会将VM堆中的小部分和本地堆中的实际数据标记为可用供GC使用。但是实际的收集是由两个不同的GC机制执行的。VM堆中的部分由Davlik GC收集——您可以通过DDMS看到这种情况发生。但本地堆数据由Skia GC收集,它似乎比较懒惰(运行频率较低?)。这意味着,即使进行了严格的recycle(),也有可能超越本地堆GC。
幸运的是,存在监视本地堆状态的机制。请参见BitmapFactory OOM driving me nuts

我已经实现了一些解决本地堆问题的方法(包括使用AsyncTask加载位图,捕获来自BitmapFactoryOutOfMemoryError并等待多达2秒钟重试),但在这种情况下,内存似乎位于ImageView对象内部。尽管听起来很疯狂,但调用setImageBitmap(null)setImageResource(resourceID)都有一定的好处... - Andrew Wyld
还有几点需要说明:这里的问题不是内存回收不够快(我已经使用了等待循环来处理),而是根本没有回收。现在我已经发现,ImageView 会保持 Bitmap 内存的存活状态;调用 setImageBitmap(null) 解决了这个问题(令人惊讶的是,调用 setImageDrawable(...)setImageResource(...) 并不能从内存中删除 Bitmap——显式的 null 已经在 onStop() 中削减了约 10MB 的内存占用)。 - Andrew Wyld
你有关于Skia图形垃圾回收的任何文档吗?我从未听说/阅读过任何相关信息,所以了解这些信息将是很好的。它是一个本地库,因此似乎极不可能存在任何类型的垃圾回收。 - Justin Breitfeller
你说得对。Skia是一个本地的(C++)库,有自己的堆管理覆盖了stdlib内存管理。我把它称为“GC”有点草率。至于文档,我不知道是否有任何文档;我只在必要时进行代码阅读。 - Torid
recycle() 方法本应释放原生 Bitmap 内存,但正如问题所述,它并没有。我还发现在 onStop() 中调用回收或位图清空代码不是很好,因为当启动新活动时活动方法执行的顺序是:Activity1.onPause(), Activity2.onCreate(), Activity2.onStart(), Activity2.onResume(), Activity1.onStop()。这意味着如果你从 onStop() 中释放它,而另一个活动已经启动,内存实际上并没有被释放。 - Andrew Wyld

6

我发现在Honeycomb及以上版本中,如果一个ImageView包含一个已被回收的Bitmap,则Bitmap数据仍然保留在内存中,直到在该ImageView上调用setImageBitmap(null)。即使调用了setImageResource(...)setImageDrawable(...)(在这种情况下,一个非常大的位图被替换为一个相当小的九宫格图案),只有在加载九宫格图案之前调用setImageBitmap(null)才会实际释放内存。


1
安德鲁,你在使用蜂巢版还是ICS进行开发?对于阅读此问题的其他SO用户来说,这将会非常有帮助,因为它确实会产生重大影响。你描述的行为我只会在蜂巢版或更高版本中看到。另外,请参阅有关调用System.gc()的Android文档。推荐调用该函数从来都不是一个好主意。垃圾回收器几乎总能找到最佳运行时间。 - Justin Breitfeller
3
重要提示:在ICS上显式调用Bitmaps的recycle()可能会导致致命错误。通常情况下,您必须在早期版本中回收以避免内存泄漏,而在ICS及以后的版本中则不能回收以避免崩溃。谢谢,Google... - Adrian
2
@Adrian,你有关于这个的更多细节吗?我在许多不同的ICS设备上运行了一个使用recycle()的应用程序,并没有看到崩溃。如果你有相关信息,我很想看看以确保我没有做什么危险的事情。 - cottonBallPaws
这是一些相当惊人的信息。你们有官方文档的参考资料吗? - Maarten
我不知道,但如果有其他人读到这篇文章,我会非常着迷去阅读它!这是通过实验和询问他人的专业知识的结合来发现的。 - Andrew Wyld
显示剩余2条评论

3

回收会释放为位图分配的本地内存。实际的Bitmap对象将保留在Dalvik堆中,直到下一次垃圾回收(但此对象占用的内存微不足道)。

据我所知,确实没有办法转储原生堆。因此,您将无法通过堆转储查看位图的本机数据是否已消失。但是,您应该能够看到应用程序使用的总内存量减少。这问题应该可以帮助您发现访问应用程序内存使用情况统计信息的各种方法。


1
很难说为什么你的内存使用量会增加,因为可能有很多原因。此外,垃圾回收器并不会一直运行,所以你真正需要担心的时间是在GC之后,如果数字没有显著下降。如果你正在使用带有Honeycomb或ICS模拟器的平板电脑,则位图内存应该显示在dalvik堆中。 - Justin Breitfeller
1
在进行回收调用后,如果通过DDMS选项卡强制执行垃圾回收(GC),位图会从堆中被移除吗?如果没有,您可以使用Eclipse MAT(内存分析工具)来查看哪个对象使得位图保持存在。 - Justin Breitfeller
啊,"未知的 HPROF 版本(JAVA PROFILE 1.0.3)"。 - Andrew Wyld
PS 感谢 @Justin Breitfeller 指出 MAT 工具 :) - Andrew Wyld
Diane Hackborn曾经评论说,从3.0版本开始,Android不再从本地堆中分配位图,而是直接从常规堆中分配。请参见她在此页面上的评论:https://dev59.com/dHI-5IYBdhLWcg3wQV8Z我不知道这会如何影响recycle()命令,或者recycle()命令如何与常规GC交互,但根据一些报告,答案可能是“不太好”。请参见Ephraim在此处的回答:https://dev59.com/puo6XIcBkEYKwwoYTzNK - Carl
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接