Flash Timer无故停止之谜
项目底层资源Cache超时使用了Timer驱动,每个资源到期Cache超时时会进行“释放”。后来发现有明显内存泄漏,排查出是因为Timer未进行导致。Oh,卖糕的~
ResourceManager为单例,Timer在工程启动时开始执行,为了方便说明见如下示例:
public class Demo extends Sprite{
private var _t:Timer;
public function Demo(){
_t = new Timer(500);
_t.addEventListener(TimerEvent.TIMER, onTimer);
_t.addEventListener(TimerEvent.TIMER_COMPLETE, onComplete);
_t.start();
trace("is running:" + _t.running);
}
private function onTimer(e:Event):void
{
trace("timer");
}
private function onComplete(e:Event):void
{
trace("complete");
}
}
输出:
is running:true
以上结果应该在意料之中?
但,只打印了这一行,onTimer()和onComplete()中的trace()未执行,问题就出在这。正常情况应该至少会每500ms打印一次“timer”。
若增加一个ENTER_FRAME监听,每帧输出Timer的状态,如下:
public function Demo(){
_t = new Timer(500);
_t.addEventListener(TimerEvent.TIMER, onTimer);
_t.addEventListener(TimerEvent.TIMER_COMPLETE, onComplete);
_t.start();
trace("is running:" + _t.running);
addEventListener(Event.ENTER_FRAME, onEnterFame);
}
private function onEnterFame(e:Event):void
{
trace("is running:" + _t.running);
}
private function onTimer():void
{
trace("timer");
}
private function onComplete(e:Event):void
{
trace("complete");
}
输出:
is running:true
is running:false
is running:false
……
我们发现,从第二帧开始Timer已经停止,但TIMER、TIMER_COMPLETE事件监听未触发。
Timer莫名其妙地挂了有木有!这么基础的逻辑变得不可靠有木有!!有多少功能是依靠Timer驱动的有木有!!!再也不相信爱情了有木有!!!……
以上就是问题和现象的描述,首先我们不能轻易怀疑Flash SDK有Bug,由于问题不是100%重现,苦B地排查着。
直到有一天另一个问题出现,工程启动里需要切换播放几组动画,切换时机由一Timer驱动,问题表现为动画未切换且100%重现。代码就不帖了,断点结果与前一问一模一样,Timer启动后的第二帧莫名停止且不抛任何事件。
解决
请观察第一组代码Demo.as为工程入口,Timer的初始化和启动是在Demo的构造方法中进行,貌似有问题是不?
Flash在启动时几个时间结点的执行先后:
- 执行Demo(主入口类)构造方法
- 抛出Demo.swf加载器loaderInfo的INIT事件
- 抛出Demo被添加到Stage的事件ADDED_TO_STAGE
- 抛出loaderInfo的COMPLETE事件
以下摘自DOC
INIT:当已加载的 SWF 文件的属性和方法可供访问并做好使用准备时进行分派。不过,内容可能仍在下载之中。如果存在以下条件,则 LoaderInfo 对象将分派 init 事件:
- 可以访问与加载的对象关联的所有属性和方法以及与 LoaderInfo 对象关联的属性和方法。
- 所有子对象的构造函数已完成。
- 已执行所加载的 SWF 主时间轴的第一帧中的所有 ActionScript 代码。
COMPLETE:成功加载数据后分派。也就是说,当已下载所有内容并且已完成加载过程时分派它。complete 事件始终在 init 事件之后分派。当该对象已做好访问准备(尽管可能仍在下载内容)时,将分派 init 事件。
问题出在,我们在构造方法中执行的逻辑,当工程还未完全初始化或未加载完成时启动了Timer,当工程准备就绪时Timer将被重置。且不会触发任何事件。
将代码改为如下,在COMPLETE后进行Timer初始化,可解决问题:
public class Demo extends Sprite{
private var _t:Timer;
public function Demo(){
loaderInfo.addEventListener(Event.COMPLETE, onAppReady);
}
private function onAppReady(e:Event):void
{
_t = new Timer(500);
_t.start();
}
}