如果您看了我的上一篇博文,您就应该了解Flash Player运行时的帧频和Timer计时是不精确的;其原因之一是AVM虚拟机每次分配的时间片间隔不是一个固定的值;另一个原因则可能是一次时间片所运行的代码较大,如果一个时间片的时间执行不完,则时间片就会延长并占用后面的时间片。
在这篇文章里,我给“时间片执行不完而延长该时间片,并造成帧频、Timer暂时停止”的情况起了个名词叫“Flash运行时阻塞”(这个名词或许并不完全恰当,如果您有更适合的词语,可以告诉我)。如果您没有看过我之前的博文,下面有两个链接,您有必要看一下:
执行模型之可变跑道
Flash Player帧频、Timer计时 的时间间隔测试
Flash运行时阻塞也会分为多种情况,比如:单次时间片所运行的代码量大,在给定的时间片内执行不完、计算机配置较低或运行过多的程序造成Flash所获得的CPU资源较少、Flash加载外部大数据文件等,本文的重点是关于Flash加载外部数据时,所造成的阻塞测试,其他几种情况较容易理解,我就暂时不进行测试了,有兴趣的朋友可以试一下。
AS3中,与加载外部数据有关的类有Loader、URLLoader、FileReference、NetConnection、SharedObject、Socket、XMLSocket等,另外我还测试了Flex中的SWFLoader和开源文件加载类BulkLoader,下面列出我的测试代码,后面总结一下。我使用的是Flex开发工具,创建了一个Flex项目。
对于这几个类的测试方式,大都是类似的,我就不全部列出了,我们看一下URLLoader在加载的时候对Timer计时和帧频的影响:
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script> <!--[CDATA[ import flash.utils.getTimer; // 测试计时器 private var timer:Timer; // 记录开始时间 private var startTime:int; // 记录上一次计时的时间 private var prev:int; // 用于加载数据的URLLoader private var loader:URLLoader; private function start(event:Event):void { // 创建一个Timer timer = new Timer(100); timer.addEventListener(TimerEvent.TIMER, onTimer); // 创建一个URLLoader loader = new URLLoader(); loader.addEventListener(Event.COMPLETE, onComplete); // 记录当前时间 startTime = getTimer(); prev = startTime; trace("start time: " + startTime); // 启动Timer和URLLoader timer.start(); loader.load(new URLRequest("assets/2.rar")); } private function onTimer(event:TimerEvent):void { // 计时器执行,获取当前时间 var t:int = getTimer(); var span:int = t - prev; // 打印出当前时间与上一次计时的时间间隔 trace("time span: " + span); // 记录当前时间 prev = t; } private function onComplete(event:Event):void { trace("load complete span: " + (getTimer() - startTime)); } private function stop(event:Event):void { timer.stop(); timer = null; } ]]--> </mx:Script> <mx:Button x="30" y="30" label="START" click="start(event)"/> <mx:Button x="130" y="30" label="STOP" click="stop(event)"/> </mx:Application> /** 测试结果 start time: 1366 load complete span: 2890 time span: 2899 time span: 136 time span: 126 time span: 131 time span: 118 **/
上面代码中我加载的是一个100M左右的rar文件,在代码的32行,Timer计时和URLLoader加载是同时开始的,但测试结果的前3行我们可以看出Timer计时并没有隔100多毫秒就运行,而是等待了将近3秒才开始第一次计时,而这3秒内,URLLoader加载完毕,由此可以证明URLLoader在加载的过程中,Timer计时被阻塞了。如果只针对于Timer你还不相信URLLoader会阻塞Flash的运行,那么我们来看一下ENTER_FRAME事件的执行效果。下面的代码跟上面Timer的测试代码类似,只是把Timer的部分换成了ENTER_FRAME事件。
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Script> <!--[CDATA[ import flash.utils.getTimer; // 记录开始时间 private var startTime:int; // 记录上一次计时的时间 private var prev:int; // 用于加载数据的URLLoader private var loader:URLLoader; private function start(event:Event):void { // 创建一个URLLoader loader = new URLLoader(); loader.addEventListener(Event.COMPLETE, onComplete); // 记录当前时间 startTime = getTimer(); prev = startTime; trace("start time: " + startTime); // 启动ENTER_FRAME和URLLoader addEventListener(Event.ENTER_FRAME, onEnterFrame); loader.load(new URLRequest("assets/2.rar")); } private function onEnterFrame(event:Event):void { // 获取当前时间 var t:int = getTimer(); var span:int = t - prev; // 打印出当前时间与上一次计时的时间间隔 trace("time span: " + span); // 记录当前时间 prev = t; } private function onComplete(event:Event):void { trace("load complete span: " + (getTimer() - startTime)); } private function stop(event:Event):void { removeEventListener(Event.ENTER_FRAME, onEnterFrame); } ]]--> </mx:Script> <mx:Button x="30" y="30" label="START" click="start(event)"/> <mx:Button x="130" y="30" label="STOP" click="stop(event)"/> </mx:Application> /** 测试结果 start time: 1827 time span: 31 load complete span: 2871 time span: 2849 time span: 14 time span: 43 time span: 28 time span: 47 **/
前4行测试结果中显示,ENTER_FRAME事件也被阻塞了,如果您仍然认为URLLoader是异步加载的,并且对这个测试结果抱有怀疑的话,您可以动手测试一下,多动手往往能发现一些很有趣的事情。对于上面URLLoader阻塞ENTER_FRAME事件的测试,您或许注意到测试结果的第二行执行了一次ENTER_FRAME事件,如果您看过“执行模型之可变跑道”这篇文章,或许知道其中的原因,在这里我解释一下:对于ENTER_FRAME事件,会在每帧开始的时候最先执行;URLLoader调用load方法后,会在下一帧开始时执行加载,ENTER_FRAME事件是在每帧的最开始执行的,所以会先于load加载,这就是为什么第二句会是ENTER_FRAME事件trace出来的。很明显,这并不影响数据加载阻塞了ENTER_FRAME事件。
我使用类似的方式测试了Loader、SWFLoader、BulkLoader,它们在加载大数据的时候都会阻塞ENTER_FRAME事件和Timer计时事件,这里我就不给出测试代码了。
FileReference是AS3中用来上传下载文件的类,经过测试,在上传文件的过程中,FileReferece并不影响Timer和ENTER_FRAME事件,下载暂时未测试。
NetConnection不是加载外部数据的类,我主要想测试一下它的connect方法,测试证明connect在连接远程服务器时不影响Timer和ENTER_FRAME事件(即使连接服务器端所用的时间很长或根本连接不上服务器)。
在使用远程SharedObject时,多个用户连接该共享对象,如果某个用户使用SharedObject的send方法发送一个大数据时,发送者和接收者的Timer和ENTER_FRAME事件都会被阻塞。
Socket和XMLSocket因为时间的关系,我目前还没有做测试,有兴趣的朋友可以测一下,不要忘记告诉我结果哈。另外如果您觉得还有其他类会出现阻塞的情况,请一块分享。
总结一下:对于Flash运行时阻塞,一直认为是代码执行时间过长引起的,现在看来可能会存在多种情况造成阻塞。如果您觉得您的Flash应用程序有的时候会比较卡,并且会从外部加载一些数据,可以检查一下是否是因为加载的外部数据量较大造成的呢?比如 如果使用Loader加载多个可显示对象,并且网速比较慢的情况下(由此可见,网速慢也是引起Flash应用程序卡的一个原因)。