WPF/Silverlight深度解决方案:(八)誓将内存释放到底
WPF/Silverlight应用程序长时间运行后会产生非常多的内存垃圾(内存泄露例外),特别是在经常需要进行Remove操作的粒子、动画、游戏等方面的应用,国外高手们提出的方案我归纳了一下主要有:
1) UIElement控件实例= null
2) 定时调用GC.Collect()
3) 让控件继承Idisposable接口,并实现相应逻辑
这三种方法都有一定的作用,但是实际使用中均往往难以达到预期效果。特别是在Silverlight应用中,目前的Flash/Flex制作的RPG网页游戏都有一个通病:内存不断增加导致运行二、三十分钟后浏览器即进入假死状态,Silverlight如果无法处理好内存的释放,命运或许终究一样。
那么我们是否还有其他更好的方法实现内存垃圾的释放?大家不妨做这么一个测试,以IE浏览器为例,当我们打开一个运行有Silverlight应用程序的页面后,记录下任务管理器中该IEXPLORE.EXE的内存使用量,然后运行一段时间,再记录此时的内存使用量,最后将该页面最小化再还原回来,大家将看到该IEXPLORE.EXE的内存使用量已被完全的释放干净,如同新开的网页一般。这老掉牙的东西已算不上什么技巧,但是却给了我们一条内存彻底释放的思路:既然Silverlight应用程序是镶嵌在网页中的,我们能否通过该方法去释放内存呢?答案是肯定的。
以我的Silverlight游戏引擎Demo为例,刚加载完时内存使用约56M:
然后我刷了80个怪,并将它们全部消灭干净后内存使用量约为132M:
接着将此页面最小化再还原,内存瞬间释放到约20M:
并且在不刷出新怪的前提下,我环绕整个地图走完每个角落后,内存最大时不超过56M,从而证明了IE最小化后将瞬间释放完Silverlight内存垃圾的观点是正确的。至于例如FireFox浏览器如何最小化时释放内存大家可以参考:让火狐浏览器最小化时释放内存这篇文章。
很多朋友或许要说了:这些都是废话嘛,连蚂蚁都知道。难不成我还要在Silverlight页面上放个注释:客户们注意啦~为了您和浏览器的健康,请定时最小化浏览器。
其实我想和大家说的,这仅仅是一条思路。大家不妨想想,其实我们是完全可以将这种方法无缝的嵌入到页面中去的。
在Winform/WPF中,实现该方法仅仅只需通过下面一段代码即可实现模拟释放内存(原文地址):
[DllImport("kernel32.dll")]
public static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);
public void FlushMemory() {
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}
但是Windows的API无法在Silverlight中使用,我们该怎么办呢?
再看下面这段代码:
我们可以使用一个Js的setInterval方法每间隔一段时间去自动释放内存,该方法原理为将浏览器最小化后再找回焦点,我们肉眼看到仅是页面闪了一下,再无任何其他感觉。就这么一瞬间,就算Silverlight应用程序导致浏览器占用了数百M的内存,照样完全释放无误。这是该例子Demo,有兴趣的朋友可以下载体验一下。
<script type="text/javascript">
function intervalFlushMemory() {
setInterval(FlushMemory, 60000);
}
function FlushMemory() {
min.Click();
window.focus();
}
</script>
<body onload="intervalFlushMemory();">
……
<object id="min"
type="data:application/x-oleobject" classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11">
<param name="Command" value="Minimize" />
</object>
<button onclick="FlushMemory();">释放内存</button>
……
</body>
非常不幸的是,通过<object>实现内存释放的方法目前只能在IE中使用,而且由于诸多限制还无法很好的广泛使用,解决的最终方法目前我只想到两个:
1) 能否有相关的Silverlight的API直接实现类似功能,或者后台代码C#中去实现呢?
2) 通过将该<object>制作成控件并打包成cab,并加上数字签名,以ActiveX的形式让用户使用,实在是够麻烦的…
文章要结尾了,但是我无法兴奋起来,C++开发者的冷眼嘲笑让我始终有种沉痛的感觉:Silverlight的内存垃圾本是可以轻松释放的,为何MS就不能让我们畅快一点?托管是否能更绅士些?
最后,我想告诉大家的是,本文的目的并没有真正达到,因为这所有的方法都不是完美的解决方案,作为一些思路,我希望大家能给予我更多的指点,欢迎大家留言参与Silverlight内存的相关讨论,因为我们的最终目的只有一个:誓将内存释放到底!
Silverlight还是个孩子,它的路还有很长要走;但我一直坚信:明天会更好。