您的位置:首页 > 其它

还是Flash垃圾回收那点事(转)

2014-05-10 15:58 239 查看
Flash垃圾回收机制是我们Flash编码人员必备的一个知识,若是不了解这玩意儿,哼哼~你就等着别人在玩你开发的应用的时候越来越卡吧……

很多人都多少在书本或者网贴上看到过有关垃圾回收机制的介绍,但是鲜有人付诸于实践,在写代码的时候常常没有这种意识,所以很多人会在QQ群里或者论坛上 求助说“哎~我的应用怎么那么卡?求优化方案”之类的话。事实上,在Flash开发过程中,让画面流畅(保持高帧率且不降帧)的关键是节省CPU和内存的 消耗。节省CPU消耗的关键在于算法以及使用的对象类型上的选择,而节省内存消耗的关键则在于编码方式上面,这也是有经验的程序员和菜鸟之间的差距之一。

我把Flash垃圾回收机制以一句话概括:一个对象满足垃圾回收的条件有二:1.没有其他对象保持着对其的引用;2.不存在强引用事件侦听。

光看文字自然很难领悟,让我们来看一些实例你就会懂了。

Test1:

Actionscript 代码

private var a:Sprite = new Sprite();

this.addChild( a );

a不能被回收,因为对象a保持着对它所创建的Sprite对象的引用。

Test2:

Actionscript 代码

private var a:Sprite = new Sprite();

this.addChild( a );

a = null;

a不能被回收,因为a被addChild到了this的显示列表中,因此this就保持着对a的引用,只清除了变量a对其所实例化的Sprite对象的引用却没有清除this对其的引用

Test3:

Actionscript 代码

private var a:Sprite = new Sprite();

this.addChild( a );

a = null;

this.removeChild( a );

a能被回收,因为a所创建的Sprite对象不仅从this的显示列表中移除了,还释放了a对其的引用,此时没有任何变量保持着对此Sprite对象的引用了。

从上面三段代码中我们基本上可以理解第一条原则:没有其他对象保持着对其的引用的概念了。但是当你用上数组之后,往往会忽略数组对需要回收的对象的引用,一起来看接下来几个例子:

Test4:

Actionscript 代码

private var a:Sprite = new Sprite();

private var arr:Array = [];

arr.push( a );

this.addChild( a );

a = null;

this.removeChild( a )

a不能被回收,因为数组arr中还保持着对a所实例化的Sprite对象的引用。为了能够成功地回收a,你还需要将a从数组中剔除:

Actionscript 代码

var index:int = arr.indexOf( a );

arr.splice( index, 1 );

或者直接把数组清空:

Actionscript 代码

arr = [];

//或者arr = null;

有了上面的基础之后我们来看一个编码习惯的问题,先看下面两段代码,大家看过后告诉我哪段代码结构更佳。

Code1:

Actionscript 代码

private var a:Sprite = new Sprite();

public function show():void

{

addChild( a );

}

Code2:

Actionscript 代码

private var a:Sprite;

public function show():void

{

if( !a )

{

a = new Sprite();

}

addChild( a );

}

Code2的代码结构比Code1的好,为什么?因为变量a只在公共方法show中用到,因此,在Code1中,就算你永远没有调用过show方 法,变量a所实例化的Sprite对象也一直会占据着内存(因为变量a保持着对其的引用),而在Code2中就不存在这个问题,直到需要用到a了才去实例 化之。这是一个较好的编码习惯,还请列位谨记。

接下来我们看看强引用事件侦听对对象垃圾回收的影响,若是一个对象存在强引用事件侦听,则其不会被垃圾回收。看下面的例子:

Test5

Actionscript 代码

private var a:Sprite = new Sprite();

this.addChild( a );

a.addEventLitener( MouseEvent.CLICK, onClick );

a = null;

this.removeChild( a );

private function onClick( e:MouseEvent ):void

{

trace("oh, shit!");

}

a不能被回收,因为有一个鼠标点击的侦听器在其身上。即使你将其从this的显示列表中移去并把a置为了null,你也不过是以为自己闭上眼睛看不见某个东西就以为这个东西不存在了,醒醒吧,少年,别在自己骗自己了。为了顺利回收a,你需要在把a置为null前移除其所有的事件侦听器。

但是,but,如果你为需要回收的对象添加的事件侦听器为弱引用的话就可以顺利地让对象被回收掉。如何设置事件侦听器为弱引用?设置addEventListener方法的最后一个参数为true即可。

Actionscript 代码

private var a:Sprite = new Sprite();

this.addChild( a );

a.addEventLitener( MouseEvent.CLICK, onClick, false, 0, true );//保持第三、四个参数为默认值,设置第五个参数为true

a = null;

this.removeChild( a );

private function onClick( e:MouseEvent ):void

{

trace("oh, shit!");

}

a能被回收,因为没有变量保持着对a所实例化的 Sprite对象的引用,且其身上也不具备强引用事件侦听器(由于addEventListener方法最后一个参数被设置为true,因此为a注册的鼠 标点击事件侦听器为弱引用)。但是,虽然弱引用事件侦听器不会妨碍对象的垃圾回收,但是回收速度会被大大减慢,毕竟若引用事件侦听器也是在侦听事件,它肯 定得等待一阵子发现没有动静后才会认为其宿主对象已无用,再去回收。综上所述,若是你的确用不到一个对象了,就手动调用
removeEventListener去移除它的全部事件侦听器吧。

需要注意的是,事件侦听器越多越是影响性能,因此对于一些仅使用一次的事件侦听器,及时移除是个很好的习惯,就像下面两段代码:

Code1:

Actionscript 代码

package

{

import flash.display.Sprite;

import flash.events.Event;

public class Test extends Sprite

{

public function Test()

{

addEventListener(Event.ADDED_TO_STAGE, onAdded);

}

private function onAdded( e:Event ):void

{

removeEventListener(Event.ADDED_TO_STAGE, onAdded);

init();

}

private function init():void

{

//init something

}

}

}

Code2:

Actionscript 代码

package

{

import flash.display.Bitmap;

import flash.display.Loader;

import flash.display.LoaderInfo;

import flash.display.Sprite;

import flash.events.Event;

import flash.net.URLRequest;

public class Test extends Sprite

{

public function Test()

{

var loader:Loader = new Loader();

loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComp);

loader.load(new URLRequest("pigdogwc.jpg"));

}

private function onComp( e:Event ):void

{

var loaderInfo:LoaderInfo = e.currentTarget as LoaderInfo;

loaderInfo.removeEventListener(Event.COMPLETE, onComp);

var bmp:Bitmap = loaderInfo.content as Bitmap;

}

}

}

我们看到,那些比较流行的事件,如添加到舞台事件(ADDED_TO_STAGE),加载完成事件(COMPLETE)事实上都是只用一次的事件,在触发事件后我们就可以毫无顾忌地移除事件侦听器了,这是一种良性循环,不会造成内存的不断堆积却找不到问题根源。

有同学会担心一个问题,就是当一个类A中的某一个变量被外部保持着引用,如果该变量没有被回收的话会不会牵连到整个A对象都不能被回收呢?具体代码如下:

Actionscript 代码

package

{

import flash.display.Sprite;

public class A extends Sprite

{

public var aa:Sprite=new Sprite();

}

}

在类A中声明了一个aa的Sprite变量,那么在类B中会保持着对其的引用:

Actionscript 代码

package

{

import flash.display.Sprite;

public class B extends Sprite

{

private var a:A;

private var b:Sprite;

public function B()

{

a = new A();

b = a.aa;

a = null;

}

}

}

此时,如果将a置为空,但是由于B中的一个变量b保持着对A.aa变量的引用的话会不会造成a不能被回收呢?答案是否定的,a可以被回收,但是它的aa 变量实例化的那个Sprite对象被保留了下来,存于B.b中。若是在外部没有一个类保持着对A.aa的引用的话,它也会跟着它生父——a一起离开人 世……

要构建一个结构良好的项目并非易事,尤其是刚搞开发不久的新人缺乏经验,就很容易出现一些严重问题,如内存溢出。为了活用刚才上面介绍的垃圾回收机制,我们在项目开发中需要注意哪些编码习惯呢?

首先,避免开放过多的复杂对象为共有静态对象,开放过多共有静态对象会造成的一个问题是,这个共有静态对象由于可以在项目中任何类中访问,则会被这个类加 点东西,被那个类减点东西,到最后你自己都不知道该对象到底被外部哪些对象保持着引用,也不知道到底被外面的对象访问并添加了多少个事件侦听器,以至于垃 圾回收成为了难以完成的使命。比如,存在一个单例类MyModel,其中有一个共有变量a,类型为Sprite:

Actionscript 代码

package

{

import flash.display.Sprite;

public class MyClass extends Sprite

{

public static var a:Sprite = new Sprite();

}

}

a被开放为共有静态变量,则有可能你在类C,D,E中都存在一个变量或多个变量持有MyClass.a的引用,如

Actionscript 代码

package

{

import flash.display.Sprite;

public class C extends Sprite

{

public static var c:Sprite = MyClass.a;

}

}

C 类中存在一个变量c持有了MyClass的a变量的引用,类D、E中也存在类似写法,声明了一个或多个变量一直持有着对MyClass.a的引用,那么当 你想回收MyClass.a对象的时候你很容易清理不干净外部的全部对a的引用,比如你把C、D中对MyClass.a的引用置为了null,却忘记了处 理E,只要存在任一个对MyClass.a的引用,其就不会被垃圾回收。因此我们在项目开发过程中必须注意开放成静态变量的对象的数目,且心中一定要清楚 地知道所有对静态变量的引用的地方,以便日后清理。

要清理所有的事件侦听,我有一个比较好的办法,就是每次调用一个对象的addEventListener方法时把添加的事件侦听器记录起来,日后若要移除全部事件侦听只需要把全部记录着的侦听器遍历一遍,一次移除干净即可:

Actionscript 代码

package

{

import flash.display.Sprite;

public class Test extends Sprite

{

private var _eventListeners:Object;

public function Test(){}

override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void

{

super.addEventListener(type, listener, useCapture, priority, useWeakReference);

if( !_eventListeners )

_eventListeners = new Object();

//将添加的事件侦听器侦听事件类型以及事件处理函数保存起来

_eventListeners[type] = listener;

}

override public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void

{

super.removeEventListener(type, listener, useCapture);

if( _eventListeners && _eventListeners[type] )

{

delete _eventListeners[type];//删除该侦听器记录

}

}

public function removeAllEventListener():void

{

for(var event:String in _eventListeners)

{

removeEventListener(event, _eventListeners[event]);

}

}

}

}

这段代码应该多少能给你一些启示。不过有少数时候我们可能会给同一个事件添加两个事件侦听器:

Actionscript 代码

a.addEventListener( MouseEvent.CLICK, onClick1 );

a.addEventListener( MouseEvent.CLICK, onClick2 );

那么这样子的话用上面那重载了的addEventListener的代码就无法记录全所有的事件侦听器了,因为在一个Object对象中,一个键只 能对应一个值,当第一次调用addEventListener方法后,_eventListeners[MouseEvent.CLICK] 属性的值等于 onClick1 这个方法,但是第二次调用addEventListener方法后该属性值就会改成onClick2这个方法了,此时onClick1这个侦听器就被漏记 录了。因此,我们需要改写一下记录方式,使用数组来记录全部的事件侦听器:

Actionscript 代码

package

{

import flash.display.Sprite;

public class Test extends Sprite

{

private var _eventListeners:Object;

public function Test(){}

override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void

{

super.addEventListener(type, listener, useCapture, priority, useWeakReference);

if( !_eventListeners )

_eventListeners = new Object();

//将添加的事件侦听器侦听事件类型以及事件处理函数保存起来全部事件侦听函数

//都保存在一个数组中,这个数组可以通过_eventListeners[事件类型]来访问

if( _eventListeners[type] == null )

{

_eventListeners[type] = new Array();

}

//防止重复

if( (_eventListeners[type] as Array).indexOf(listener) == -1 )

{

_eventListeners[type].push(listener);

}

}

override public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void

{

super.removeEventListener(type, listener, useCapture);

//查询需要移除的事件类型对应侦听器是否存在,若存在则从记录中移除

if( _eventListeners[type] != null )

{

//查询欲移除的侦听函数是否存在于记录中,若存在则移除

var index:int = _eventListeners[type].indexOf( listener );

if( index != -1 )

{

_eventListeners[type].splice( listener, 1 );

//若一个事件的全部侦听器都移除完毕,则在记录本中将记录该事件的数组移去

if( _eventListeners[type].length == 0 )

{

delete _eventListeners[type];

}

}

}

}

public function removeAllEventListener():void

{

for(var event:String in _eventListeners)

{

for each(var listener:Function in _eventListeners[event])

{

removeEventListener(event, listener);

}

}

}

}

}

转载自:http://www.iamsevent.com/post/3.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: