利用Dictionary的弱引用特性实现动态内存管理

Posted on April 22, 2012

这几天看了些文章,提供了一种显示对象位图化并共享位图数据的优化方案,测试了下能提高不少性能,所以想自己也实现一个试试。基本思路如下:

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;

public class CachedSprite extends Sprite
{
	protected static var cachedData:Dictionary = new Dictionary;

	public var clip:Bitmap;

	public function CachedSprite(asset:Object)
	{
		var key = getQualifiedClassName(asset);
		var data:BitmapData = cachedData[key];
		if(data==null)
		{
			var instance:Sprite = new asset();
			data = new BitmapData(instance.width, instance.height, true, 0x0);
			data.draw(instance, null, null, null, null, true);
			cachedData[key] = data;
		}

		clip = new Bitmap(data, "auto", true);

		addChild(clip);
	}
}

写的过程中,发现了一个老问题。共享的位图数据bitmapData是缓存在类静态变量里的。如果不显式去销毁,就永远无法被回收。而位图数据通常是最消耗内存的,不能忽视。销毁的时机当然就是所有引用这个bitmapData的实例都被回收了之后。但是我们怎么才能知道一个对象被回收了呢?答案是:不能。至少目前FP没有给我们提供内存管理的api,让我们知道什么对象要被回收了。

不能知道对象实例什么时候被回收,我们只好采用另一种常见方案了:用一个静态变量记录当前的实例数,写一个dispose()方法去显式递减实例数,然后在代码里每个使用完这个实例的地方,都调用一次dispose(),当实例数为0时销毁bitmapData。但是这个方法很容易造成内存泄露的隐患。只要你在一个地方使用完没有调用dispose()就会必然导致内存泄露。这对于大型项目来说是很难避免的。感觉这样就退回到低级语言的开发效率上去了,需要花额外的时间去一个个检查dispose()。

那就没有能自动管理内存的方案了吗?我想了一晚也没想出来。早上决定放弃的时候,突然发现了新方案:可以用字典类Dictionary的弱引用特性来解决这个问题!弱引用只存在事件监听和Dictionary的key上。而Dictionary的key是可以使用复杂对象的。使用弱引用,相当于告诉FP这个引用计数无效。也就是其他对象还存在对目标对象的引用时,我靠这个弱引用依然可以访问到它。而当其他的引用都断开的时候,它就会在GC时被强制回收,正好巧妙地解决了这个问题!并且因为GC不是立即进行的,其他引用断开后还能缓存一段时间,经过测试,只要GC还没有销毁对象,通过字典类仍然可以访问到缓存的数据,这样也同时解决了需要设置延迟回收时间的问题。浑然天成。

具体就是在初始化Dictionary时传入一个true,开启key的弱引用。然后把之前的写法调转一下,key和value换一下位置就行了。因为只有key是可以当做若引用的。下面把改过的代码再贴一遍:

public class CachedSprite extends Sprite
{
	protected static var cachedData:Dictionary = new Dictionary(true);

	public var clip:Bitmap;

	public function CachedSprite(asset:Object)
	{
		var data:BitmapData;
		var key = getQualifiedClassName(asset);
		for(var value:* in cachedData)
		{
			if(cachedData[value]==key)
			{
				data = value as BitmapData;
			}
		}
		if(data==null)
		{
			var instance:Sprite = new asset();
			data = new BitmapData(instance.width, instance.height, true, 0x0);
			data.draw(instance, null, null, null, null, true);
			cachedData[data] = key;
		}

		clip = new Bitmap(data, "auto", true);

		addChild(clip);
	}
}

[更新]后来全面测试了下:

1.Dictionary的弱引用只对key为引用型对象是有效的,也就是如果用int,String这些基本数据类型作为key,是不存在弱引用的情况的。

2.如果开启了弱引用,并且使用引用型对象作为key,无论value是否是引用型,是否被外部引用,都会回收key,这样会导致key-value的映射关系也会被移除。但是Dictionary[key] = [key]情况除外。