Skip to content

Latest commit

 

History

History
761 lines (601 loc) · 39.5 KB

File metadata and controls

761 lines (601 loc) · 39.5 KB

13. 性能优化

优化概述

运行时代码执行基本原理

了解如何改进应用程序性能的关键是了解 Flash 平台运行时如何执行代码。运行时在一个循环中运行,其中某些操作是针对每个 “ 帧 ” 发生的。在这种情况下,帧只是由为应用程序指定的帧速率决定的一段时间。分配给每个帧的时间直接对应于帧速率。例如,如果指定帧速率为 30 帧 / 秒,则运行时会尝试使每个帧的执行时间为三十分之一秒。可以在创作应用程序时指定初始帧速率。可以使用 Adobe® Flash® Builder™ 或 Flash Professional 中的设置来设置帧速率。还可以在代码中指定初始帧速率。可通过对根文档类应用 [SWF(frameRate="24")] 元数据标签,在纯 ActionScript 应用程序中设置帧速率。在 MXML 中,可在 Application 或 WindowedApplication 标签中设置 frameRate 属性。

每个帧循环包括两个阶段,分为三部分:事件、enterFrame 事件和呈现。

第一阶段包括两部分 (事件和 enterFrame 事件) ,这两部分都可能会导致调用代码。在第一阶段的第一部分,发生并调度运行时事件。这些事件可以表示异步操作完成或异步操作的进度,例如来自通过网络加载数据的响应。还包括来自用户输入的事件。调度事件时,运行时在您已注册的侦听器中执行代码。如果没有发生事件,运行时会等待完成此执行阶段,而不会执行任何动作。由于缺少活动,运行时永远不会提高帧速率。如果在执行周期的其他部分发生事件,运行时将这些事件排队并在下一个帧中进行调度。

第一阶段的第二部分是 enterFrame 事件。此事件与其他事件不同,因为每个帧始终只调度一次该事件。

调度所有事件后,帧循环的呈现阶段开始。此时,运行时将计算屏幕上所有可见元素的状态并将其绘制到屏幕上。然后,此进程重复,就像赛跑者围绕跑道奔跑。

注:对于包括 updateAfterEvent 属性的事件,可强制立即执行呈现操作,而不是等到呈现阶段。但是,如果 updateAfterEvent 频繁导致性能问题,应避免使用它。

最简单的方法是假设帧循环中的两个阶段需要相同的时间。在这种情况下,一半的时间运行帧循环事件处理函数和应用程序代码,另一半的时间发生呈现。但是,事实通常是不同的。有时,应用程序代码会通过增加运行时间并减少用于呈现的时间来占用帧中多一半的可用时间。在其他情况下,特别是对于滤镜和混合模式等复杂的可见内容,呈现需要一半以上的帧时间。由于各阶段需要的实际时间是灵活的,所以帧循环常称为 “ 弹性跑道 ”。

如果帧循环的组合操作 (代码执行和呈现)所需时间太长,运行时将无法保持帧速率。帧会进行扩展,所需时间超过其分配的时间,因此在触发下个帧之前会出现延迟。例如,如果一个帧循环需要的时间超过三十分之一秒,则运行时不能以每秒三十帧的速度更新屏幕。如果帧速率减慢,将影响体验效果。最乐观的情况是动画断断续续。如果情况更糟糕,应用程序将冻结,并且窗口一片空白。

节省内存

选择适当的显示对象

ActionScript 3.0 包含很多显示对象。要限制内存用量,最简单的优化技巧之一是使用合适类型的显示对象。对于非交互式简单形状,请使用 Shape 对象。对于不需要时间轴的交互式对象,请使用 Sprite 对象。对于使用时间轴的动画,请使用 MovieClip 对象。应始终为应用程序选择最有效的对象类型。 以下代码显示不同显示对象的内存使用量: trace(getSize(new Shape())); // output: 236 trace(getSize(new Sprite())); // output: 412 trace(getSize(new MovieClip())); // output: 440 getSize() 方法显示对象在内存中占用的字节数。可以看到,如果不需要 MovieClip 对象的功能,使用多个 MovieClip 对象(而不是使用简单的 Shape 对象)会浪费内存。

重用对象

尽可能重复使用对象而不是重新创建对象。优化内存的另一种简单方法是尽可能重复使用对象并避免重新创建对象。例如,在循环中,不要使用以下代码:

const MAX_NUM:int = 18; 
const COLOR:uint = 0xCCCCCC; 
var area:Rectangle; 
for (var:int = 0; i < MAX_NUM; i++) 
{ 
	// Do not use the following code 
	area = new Rectangle(i,0,1,10); 
	myBitmapData.fillRect(area,COLOR); 
}

在各个循环迭代中重新创建 Rectangle 对象将使用更多内存且速度更慢,因为将在各个迭代中创建一个新对象。请使用以下方法:

const MAX_NUM:int = 18; 
const COLOR:uint = 0xCCCCCC; 
// Create the rectangle outside the loop 
var area:Rectangle = new Rectangle(0,0,1,10); 
for (var:int = 0; i < MAX_NUM; i++) 
{ 
	area.x = i; 
	myBitmapData.fillRect(area,COLOR); 
}

上面的示例中使用的对象对内存影响相对较小。下面的示例说明通过重复使用 BitmapData 对象可以节省更多的内存。以下用于创建平铺效果的代码浪费了内存:

var myImage:BitmapData; 
var myContainer:Bitmap; 
const MAX_NUM:int = 300; 
for (var i:int = 0; i< MAX_NUM; i++)  { 
	// Create a 20 x 20 pixel bitmap, non-transparent 
	myImage = new BitmapData(20,20,false,0xF0D062); 
	// Create a container for each BitmapData instance 
	myContainer = new Bitmap(myImage); 
	// Add it to the display list 
	addChild(myContainer); 
	// Place each container 
	myContainer.x = (myContainer.width + 8) * Math.round(i % 20); 
	myContainer.y = (myContainer.height + 8) * int(i / 20); 
}

注:使用正值时,将舍入值转换为整数比使用 Math.floor() 方法快得多。 以下图像显示位图平铺效果:
位图平铺效果图

优化的版本创建了由多个 Bitmap 实例引用的单一 BitmapData 实例,并产生相同的效果:

// Create a single 20 x 20 pixel bitmap, non-transparent 
var myImage:BitmapData = new BitmapData(20,20,false,0xF0D062); 
var myContainer:Bitmap; 
const MAX_NUM:int = 300; 
for (var i:int = 0; i< MAX_NUM; i++)  { 
	// Create a container referencing the BitmapData instance 
	myContainer = new Bitmap(myImage); 
	// Add it to the display list 
	addChild(myContainer); 
	// Place each container 
	myContainer.x = (myContainer.width + 8) * Math.round(i % 20); 
	myContainer.y = (myContainer.height + 8) * int(i / 20); 
}

此方法节省了大约 700 KB 的内存,这对于传统移动设备相当可观。使用 Bitmap 属性,不必更改原始 BitmapData 实例即可操纵每个位图容器:

// Create a single 20 x 20 pixel bitmap, non-transparent 
var myImage:BitmapData = new BitmapData(20,20,false,0xF0D062); 
var myContainer:Bitmap; 
const MAX_NUM:int = 300; 
for (var i:int = 0; i< MAX_NUM; i++)  { 
	// Create a container referencing the BitmapData instance 
	myContainer = new Bitmap(myImage); 
	// Add it to the DisplayList 
	addChild(myContainer); 
	// Place each container 
	myContainer.x = (myContainer.width + 8) * Math.round(i % 20); 
	myContainer.y = (myContainer.height + 8) * int(i / 20); 
	// Set a specific rotation, alpha, and depth 
	myContainer.rotation = Math.random()*360; 
	myContainer.alpha = Math.random(); 
	myContainer.scaleX = myContainer.scaleY = Math.random(); 
}

下图显示的是位图转换的效果:
位图转换效果图

尽可能使用对象池

另一个重要优化称为对象池,涉及到不断重复使用对象。在初始化应用程序期间创建一定数量的对象并将其存储在一个池中,例如 Array 或 Vector 对象。对一个对象完成操作后,停用该对象以免它占用 CPU 资源,然后删除所有相互引用。然而,不要将引用设置为 null,这将使它符合垃圾回收条件。只需将该对象放回到池中,在需要新对象时可以对其进行检索。

重用对象可减少实例化对象的需求,而实例化对象成本很高。还可以减少垃圾回收器运行的机会,从而提高应用程序运行速度。以下代码演示对象池技术:

package 
{ 	
	import flash.display.Sprite; 
	public final class SpritePool 
	{ 
		private static var MAX_VALUE:uint; 
		private static var GROWTH_VALUE:uint; 
		private static var counter:uint; 
		private static var pool:Vector.<Sprite>; 
		private static var currentSprite:Sprite; 

		public static function initialize( maxPoolSize:uint, growthValue:uint ):void 
		{ 
			MAX_VALUE = maxPoolSize; 
			GROWTH_VALUE = growthValue; 
			counter = maxPoolSize; 

			var i:uint = maxPoolSize; 

			pool = new Vector.<Sprite>(MAX_VALUE); 
			while( --i > -1 ) 
			pool[i] = new Sprite(); 
		} 
		public static function getSprite():Sprite 
		{ 
			if ( counter > 0 ) 
			return currentSprite = pool[--counter]; 
			var i:uint = GROWTH_VALUE; 
			while( --i > -1 ) 
				pool.unshift ( new Sprite() ); 
				counter = GROWTH_VALUE; 
				return getSprite(); 
			} 
		public static function 	disposeSprite(disposedSprite:Sprite):void 
		{ 
			pool[counter++] = disposedSprite; 
		} 
	} 
}

SpritePool 类在初始化应用程序时创建新的对象池。getSprite() 方法返回这些对象的实例,而 disposeSprite() 方法释放这些实例。代码允许池容量用尽时可以增长。还可以创建固定大小的池,这样当池容量用尽时将不会分配新对象。尽可能避免在循环中创建新对象。以下代码使用 SpritePool 类检索新实例:

const MAX_SPRITES:uint = 100; 
const GROWTH_VALUE:uint = MAX_SPRITES >> 1; 
const MAX_NUM:uint = 10; 
SpritePool.initialize ( MAX_SPRITES,  GROWTH_VALUE ); 
var currentSprite:Sprite; 
var container:Sprite = SpritePool.getSprite(); 
addChild ( container ); 
for ( var i:int = 0; i< MAX_NUM; i++ ) 
{ 
	for ( var j:int = 0; j< MAX_NUM; j++ ) 
	{ 
		currentSprite = SpritePool.getSprite(); 
		currentSprite.graphics.beginFill ( 0x990000 ); 
		currentSprite.graphics.drawCircle ( 10, 10, 10 ); 
		currentSprite.x = j * (currentSprite.width + 5); 
		currentSprite.y = i * (currentSprite.width + 5); 
		container.addChild ( currentSprite ); 
	} 
}

以下代码在当单击鼠标时,将删除显示列表中的所有显示对象,并在以后的其他任务中重复使用这些对象:

stage.addEventListener ( MouseEvent.CLICK, removeDots ); 
function removeDots ( e:MouseEvent ):void 
{ 
	while (container.numChildren > 0 ) 
	SpritePool.disposeSprite (container.removeChildAt(0) as Sprite ); 
}

注:池矢量始终引用 Sprite 对象。如果要从内存中完全删除对象,需要对 SpritePool 类使用 dispose() 方法,从而删除所有剩余引用。

释放内存

删除对对象的所有引用以确保触发垃圾回收。 在 Flash Player 的发行版中无法直接启动垃圾回收器。要确保将一个对象作为垃圾回收,请删除对该对象的所有引用。请记住,在 ActionScript 1.0 和 2.0 中使用的旧 delete 运算符在 ActionScript 3.0 中有不同的行为。它只能用于删除动态对象的动态属性。 注:在 Adobe® AIR® 和 Flash Player 的调试版中可以直接调用垃圾回收器。 例如,以下代码将 Sprite 引用设置为 null:

var mySprite:Sprite = new Sprite(); 
// Set the reference to null, so that the garbage collector removes  
// it from memory 
mySprite = null;

请记住,当对象设置为 null 时,不必将其从内存中删除。如果系统认为可用内存不是足够低,垃圾回收器可能不会运行。垃圾回收的执行时间不可预知。内存分配 (而不是对象删除)会触发垃圾回收。当垃圾回收器运行时,它将查找尚未收集的对象的图形。垃圾回收器通过在这些图形中查找相互引用但应用程序不再使用的对象,从而检测出处于非活动状态的对象。将删除通过这种方式检测到的处于非活动状态的对象。

在大型应用程序中,此进程会占用大量 CPU 并影响性能,还可导致应用程序运行速度显著降低。通过尽量重复使用对象,尝试限制使用垃圾回收。此外,尽可能地将引用设置为 null,以便垃圾回收器用较少处理时间来查找对象。将垃圾回收看作一项保护措施,并始终尽可能明确地管理对象生存期。 注:将对显示对象的引用设置为 null 不能确保冻结该对象。该对象在作为垃圾回收之前,仍将占用 CPU 周期。在将对对象的引用设置为 null 之前,先确保正确地停用对象。

可使用 Adobe AIR 和 Flash Player 的调试版中提供的 System.gc() 方法启动垃圾回收器。 将此设置与 Adobe® Flash® Builder™ 捆绑还可以手动启动垃圾回收器。通过运行垃圾回收器,可以了解应用程序的响应方式以及是否已将对象从内存中正确删除。

注:如果将某个对象用作事件侦听器,则其他对象可以引用它。如果是这样,先使用 removeEventListener() 方法删除事件侦听器,然后再将引用设置为 null。

幸运的是,这样可以立即减少位图使用的内存量。例如,BitmapData 类包括一个 dispose() 方法。下面的示例将创建一个 1.8 MB 的 BitmapData 实例。当前使用的内存增大到 1.8 MB,System.totalMemory 属性返回一个更小的值:

trace(System.totalMemory / 1024); 
// output: 43100 
// Create a BitmapData instance 
var image:BitmapData = new BitmapData(800, 600); 
trace(System.totalMemory / 1024); 
// output: 44964 

然后,手动将 BitmapData 从内存中删除并再次检查内存使用情况:

trace(System.totalMemory / 1024); 
// output: 43100 
// Create a BitmapData instance 
var image:BitmapData = new BitmapData(800, 600); 
trace(System.totalMemory / 1024); 
// output: 44964 
image.dispose(); 
image = null; 
trace(System.totalMemory / 1024); 
// output: 43084 

尽管 dispose() 方法可删除内存中的像素,但仍必须将引用设置为 null 才可完全释放内存。当不再需要 BitmapData 对象时,要始终调用 dispose() 方法并将引用设置为 null,以便立即释放内存。 注:Flash Player 10.1 和 AIR 1.5.2 在 System 类中引入了一个名为 disposeXML() 的新方法。借助此方法,通过将 XML 树作为参数传递,可立即使 XML 对象可用于垃圾回收。

使用位图

对于移动设备开发人员,位图的使用始终是一个重要话题。使用矢量 (而不是位图)是节省内存的好方法。然而,使用矢量(特别是大量矢量) ,会显著增加对 CPU 或 GUP 资源的需求。使用位图是优化呈现的一个好方法,因为 Flash Player 在屏幕上绘制像素需要的处理资源少于呈现矢量内容。

BitmapData 单个引用

通过尽量重复使用实例来优化 BitmapData 类的使用,这一点非常重要。Flash Player 10.1 引入了一个适用于所有平台的新功能,称为 BitmapData 单个引用。从嵌入图像创建 BitmapData 实例时,将对所有 BitmapData 实例使用位图的同一版本。如果稍后修改位图,则在内存中为该位图提供其自身的唯一位图。嵌入的图像可来自库或 [Embed] 标签。 注:此新功能同样适用于当前内容,因为 Flash Player 10.1 会自动重复使用位图。 当实例化嵌入的图像时,将在内存中创建相关联的位图。在 Flash Player 10.1 之前的版本中,内存中为每个实例都提供了一个单独的位图,如下图所示:

在 Flash Player 10.1 中,当创建同一图像的多个实例时,单个版本的位图将用于所有 BitmapData 实例。下图演示了此概念:

此方法大大降低了具有许多位图的应用程序的内存使用量。以下代码创建了 Star 符号的多个实例:

const MAX_NUM:int = 18; 
var star:BitmapData; 
var bitmap:Bitmap; 
for (var i:int = 0; i<MAX_NUM; i++) 
{ 
	for (var j:int = 0; j<MAX_NUM; j++) 
	{ 
		star = new Star(0,0); 
		bitmap = new Bitmap(star); 
		bitmap.x = j * star.width; 
		bitmap.y = i * star.height; 
		addChild(bitmap) 
		} 
}

下图显示该代码的效果:

使用 Flash Player 10,以上动画大约占用 1008 KB 的内存。在桌面和移动设备上,使用 Flash Player 10.1 时该动画仅占用 4 KB。 以下代码修改一个 BitmapData 实例:

const MAX_NUM:int = 18; 
var star:BitmapData; 
var bitmap:Bitmap; 
for (var i:int = 0; i<MAX_NUM; i++) 
{ 
	for (var j:int = 0; j<MAX_NUM; j++) 
	{ 
		star = new Star(0,0); 
		bitmap = new Bitmap(star); 
		bitmap.x = j * star.width; 
		bitmap.y = i * star.height; 
		addChild(bitmap) 
	} 
} 
var ref:Bitmap = getChildAt(0) as Bitmap; 
ref.bitmapData.pixelDissolve(ref.bitmapData, ref.bitmapData.rect, new Point(0,0),Math.random()*200,Math.random()*200, 0x990000);

下图显示修改一个 Star 实例的效果:

在内部,Flash Player 10.1 在内存中自动分配和创建一个位图以处理像素修改。当调用的 BitmapData 类的方法导致像素修改时,将在内存中创建一个新实例,而不更新其他实例。下图演示了此概念:

修改一个星形后,就会在内存中创建一个新副本;生成的动画在 Flash Player 10.1 上大约占用 8 KB 的内存。 在上一示例中,各个位图可单独用于转换。要仅创建平铺效果,则 beginBitmapFill() 方法是最合适的方法:

var container:Sprite = new Sprite(); 
var source:BitmapData = new Star(0,0); 
// Fill the surface with the source BitmapData 
container.graphics.beginBitmapFill(source); 
container.graphics.drawRect(0,0,stage.stageWidth,stage.stageHeight); 
addChild(container);

借助此方法,更新原始的源 BitmapData 对象时将自动更新其在舞台上的所有使用,这可能是一项功能强大的技术。然而,此方法不允许单独缩放各个星形,如前面的示例中所示。

事件模型与回调

考虑使用简单的回调代替事件模型。

ActionScript 3.0 事件模型基于对象调度的概念。事件模型是面向对象的,可以进行优化以重复使用代码。dispatchEvent() 方法循环访问侦听器列表,并对各个注册的对象调用事件处理函数方法。然而,事件模型的一个缺点是可能要在应用程序的生存期内创建许多对象。
假设您必须从时间轴调度一个事件来指示动画序列的末尾。要实现此通知,您可以从时间轴中的特定帧调度一个事件,如以下代码所示:

dispatchEvent( new Event ( Event.COMPLETE ) );

Document 类可以使用以下代码行侦听此事件:

addEventListener( Event.COMPLETE, onAnimationComplete );

虽然此方法是正确的,但使用本机事件模型与使用传统的回调函数相比,速度更慢且占用的内存更多。必须创建 Event 对象并为其分配内存,而这会降低性能。例如,当侦听 Event.ENTER_FRAME 事件时,将在各个帧上为事件处理函数创建一个新事件对象。在捕获和冒泡阶段 (如果显示列表很复杂,此成本会很高) ,显示对象的性能可能会特别低。

减少CPU使用

冻结和解冻对象

使用REMOVED_FROM_STAGE 和 ADDED_TO_STAGE 事件正确冻结和解冻对象。要优化您的代码,请始终冻结和解冻您的对象。冻结和解冻对所有对象都很重要,但对显示对象尤其重要。即使显示对象不再位于显示列表中并正在等待作为垃圾回收,其代码仍然占用大量 CPU。例如,它们仍然在使用 Event.ENTER_FRAME。因此,使用 Event.REMOVED_FROM_STAGE 和 Event.ADDED_TO_STAGE 事件正确冻结和解冻对象非常关键。以下示例显示一个在舞台上播放的、与键盘交互的影片剪辑:

// Listen to keyboard events 
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyIsDown); 
stage.addEventListener(KeyboardEvent.KEY_UP, keyIsUp); 
// Create object to store key states 
var keys:Dictionary = new Dictionary(true); 
function keyIsDown(e:KeyboardEvent):void 
{ 
	// Remember that the key was pressed 
	keys[e.keyCode] = true; 
	if (e.keyCode==Keyboard.LEFT || e.keyCode==Keyboard.RIGHT) { 
		runningBoy.play(); 
	} 
} 
function keyIsUp(e:KeyboardEvent):void 
{ 
	// Remember that the key was released 
	keys[e.keyCode] = false; 
	for each (var value:Boolean in keys) 
	if ( value ) return; 
	runningBoy.stop(); 
} 
runningBoy.addEventListener(Event.ENTER_FRAME, handleMovement); 
runningBoy.stop(); 
var currentState:Number = runningBoy.scaleX; 
var speed:Number = 15; 
function handleMovement(e:Event):void 
{ 
	if (keys[Keyboard.RIGHT])  { 
		e.currentTarget.x += speed; 
		e.currentTarget.scaleX = currentState; 
	} else if (keys[Keyboard.LEFT]) 
	{ 
		e.currentTarget.x -= speed; 
		e.currentTarget.scaleX = -currentState; 
	} 
}

与键盘交互的影片剪辑

单击 “ 删除 ” 按钮后,将从显示列表中删除该影片剪辑:

// Show or remove running boy 
showBtn.addEventListener (MouseEvent.CLICK,showIt); 
removeBtn.addEventListener (MouseEvent.CLICK,removeIt); 
function showIt (e:MouseEvent):void 
{ 
	addChild (runningBoy); 
} 
function removeIt(e:MouseEvent):void 
{ 
	if (contains(runningBoy)) removeChild(runningBoy); 
}

即使将该影片剪辑从显示列表中删除,它仍会调度 Event.ENTER_FRAME 事件。影片剪辑仍在运行,但不会呈现。要正确处理这种情况,请侦听正确的事件并删除事件侦听器,以阻止执行占用大量 CPU 资源的代码:

// Listen to Event.ADDED_TO_STAGE and Event.REMOVED_FROM_STAGE 
runningBoy.addEventListener(Event.ADDED_TO_STAGE,activate); 
runningBoy.addEventListener(Event.REMOVED_FROM_STAGE,deactivate); 
function activate(e:Event):void 
{ 
	// Restart everything 
	e.currentTarget.addEventListener(Event.ENTER_FRAME,handleMovement); 
} 
function deactivate(e:Event):void 
{ 
	// Freeze the running boy - consumes fewer CPU resources when not shown 
	e.currentTarget.removeEventListener(Event.ENTER_FRAME,handleMovement); 
	e.currentTarget.stop(); 
}

按下 “ 显示 ” 按钮后,将重新启动影片剪辑,它将再次侦听 Event.ENTER_FRAME 事件且键盘将正确控制该影片剪辑。

注:如果将显示对象从显示列表中删除,则完成删除后将其引用设置为 null 不能确保该对象是冻结的。如果不运行垃圾回收器,则对象将继续占用内存和 CPU 处理,即使此对象不再显示。要确保对象占用的 CPU 处理最少,请确保在将其从显示列表中删除后完全将其冻结。

在 Flash Player 10 及更高版本中,如果播放头遇到一个空帧,将自动冻结显示对象,即使没有实施任何冻结行为也是如此。

冻结的概念在使用 Loader 类加载远程内容时也很重要。 在 Flash Player 9 中使用 Loader 类时, 必须通过侦听由 LoaderInfo 对象调度的 Event.UNLOAD 事件来手动冻结内容。必须手动冻结每个对象,这是一项至关重要的任务。 Flash Player 10 对 Loader 类引入了一个重要的新方法,称为 unloadAndStop()。通过此方法,可以卸载 SWF 文件、自动冻结加载的 SWF 文件中的每个对象并强制运行垃圾回收器。

以下代码将加载 SWF 文件,然后使用 unload() 方法将其卸载,这需要更多的处理操作和手动冻结:

var loader:Loader = new Loader(); 
loader.load ( new URLRequest ( "content.swf" ) ); 
addChild ( loader ); 
stage.addEventListener ( MouseEvent.CLICK, unloadSWF ); 
function unloadSWF ( e:MouseEvent ):void 
{ 
	// Unload the SWF file with no automatic object deactivation 
	// All deactivation must be processed manually 
	loader.unload(); 
}

最好使用 unloadAndStop() 方法,该方法以本机方式处理冻结并强制运行垃圾回收过程:

var loader:Loader = new Loader(); 
loader.load ( new URLRequest ( "content.swf" ) ); 
addChild ( loader ); 
stage.addEventListener ( MouseEvent.CLICK, unloadSWF ); 
function unloadSWF ( e:MouseEvent ):void 
{ 
	// Unload the SWF file with automatic object deactivation 
	// All deactivation is handled automatically 
	loader.unloadAndStop(); 
}

调用 unloadAndStop() 方法时将执行下列操作:

  • 停止声音。
  • 将删除注册到 SWF 文件的主时间轴中的侦听器。
  • 停止 Timer 对象。
  • 释放硬件外围设备 (如摄像头和麦克风) 。
  • 停止每个影片剪辑。
  • 停止调度 Event.ENTER_FRAME、Event.FRAME_CONSTRUCTED、Event.EXIT_FRAME、Event.ACTIVATE 和 Event.DEACTIVATE。

鼠标交互

尽可能考虑禁用鼠标交互。

使用交互式对象 (例如 MovieClip 或 Sprite 对象)时,Flash Player 执行本机代码以检测和处理鼠标交互。当屏幕上显示许多交互式对象时,特别是当它们重叠时,检测鼠标交互可能会占用大量 CPU 资源。避免此处理的一种简便方法是对不需要任何鼠标交互的对象禁用鼠标交互。以下代码说明了 mouseEnabled 和 mouseChildren 属性的用法:

// Disable any mouse interaction with this InteractiveObject 
myInteractiveObject.mouseEnabled = false; 
const MAX_NUM:int = 10; 
// Create a container for the InteractiveObjects 
var container:Sprite = new Sprite(); 
for ( var i:int = 0; i< MAX_NUM; i++ ) { 
	// Add InteractiveObject to the container 
	container.addChild( new Sprite() ); 
} 
// Disable any mouse interaction on all the children 
container.mouseChildren = false;

尽可能考虑禁用鼠标交互,这有助于您的应用程序使用较少的 CPU 处理,从而减少电池使用量。

计时器与 ENTER_FRAME 事件

根据内容是否为动画,选择计时器或 ENTER_FRAME 事件

对于执行时间太长的非动画内容,优先选择计时器,而不是 Event.ENTER_FRAME 事件。

在 ActionScript 3.0 中,有两种方法可以特定的间隔调用函数。第一种方法是使用由交互式对象 (InteractiveObject) 调度的 Event.ENTER_FRAME 事件。第二种方法是使用计时器。ActionScript 开发人员通常使用 ENTER_FRAME 事件方法。需要对每个帧调度 ENTER_FRAME 事件。因此,调用函数的间隔与当前帧速率有关。可通过 Stage.frameRate 属性来查看帧速率。然而,在某些情况下,使用计时器比使用 ENTER_FRAME 事件更合适。例如,如果您没有使用动画,但又想以特定的间隔调用代码,则使用计时器可能是更好的选择。

计时器的行为与 ENTER_FRAME 事件的行为类似,但是调度事件无需考虑帧速率。通过此行为,可实现一些重要优化。以视频播放器应用程序为例。在这种情况下,由于仅移动应用程序控件,不需要使用高帧速率。

注:帧速率不影响视频,因为视频未嵌入时间轴中。视频是通过渐进式下载或流式下载动态加载的。

在此示例中,帧速率设置为一个较低的值 10 fps。计时器以每秒一次的速度更新控件。 TimerEvent 对象中提供的 updateAfterEvent() 方法可以提供更高的更新速率。如果需要,此方法会在每次计时器调度事件时强制更新屏幕。以下代码演示了这一概念:

// Use a low frame rate for the application 
stage.frameRate = 10; 
// Choose one update per second 
var updateInterval:int = 1000; 
var myTimer:Timer = new Timer(updateInterval,0); 
myTimer.start(); 
myTimer.addEventListener( TimerEvent.TIMER, updateControls ); 
function updateControls( e:TimerEvent ):void 
{ 
	// Update controls here 
	// Force the controls to be updated on screen 
	e.updateAfterEvent(); 
}

调用 updateAfterEvent() 方法不会修改帧速率。它只强制 Flash Player 更新屏幕上已更改的内容。时间轴仍以 10 fps 的速度运行。请记住,在低性能设备上,或者事件处理函数包含要求进行大量处理的代码时,计时器和 ENTER_FRAME 事件并不完全精确。就像 SWF 文件帧速率一样,计时器的更新帧速率随情况的不同而不同。

将应用程序中 Timer 对象和注册的 enterFrame 处理函数的数量降至最少

对于每个帧,运行时将为其显示列表中的每个显示对象调度一个 enterFrame 事件。尽管您可以使用多个显示对象为 enterFrame 事件注册侦听器,但这样做意味着将在每个帧上执行更多代码。或者,考虑使用一个集中的 enterFrame 处理函数,该函数执行要运行每个帧需要的所有代码。通过集中此类代码,更容易管理所有频繁运行的代码。

同样,如果使用的是 Timer 对象,将产生与从多个 Timer 对象创建和调度事件相关联的开销。如果您必须在不同的时间间隔触发不同的操作,以下提供了一些建议的替代方法:

  • 根据其发生的频率,使用最少数量的 Timer 对象和组操作
    例如,将一个 Timer 对象用于频繁执行的操作,设置为每 100 毫秒触发一次。将另一个 Timer 对象用于频率较低的操作或后台操作,设置为每 2000 毫秒触发一次。
  • 使用一个 Timer 对象,并以 Timer 对象的 delay 属性时间间隔的倍数触发操作。

例如,假设您希望某些操作每 100 毫秒发生一次,而其他操作每 200 毫秒发生一次。在这种情况下,请使用一个 delay 值为 100 毫秒的 Timer 对象。在 timer 事件处理函数中,添加一个条件语句,即仅每隔一次运行一次时间间隔为 200 毫秒的操作。以下示例对此技术进行了演示:

var timer:Timer = new Timer(100); 
timer.addEventListener(TimerEvent.Timer, timerHandler); 
timer.start(); 
var offCycle:Boolean = true; 
function timerHandler(event:TimerEvent):void 
{ 
	// Do things that happen every 100 ms 
	if (!offCycle) { 
		// Do things that happen every 200 ms 
	} 
	offCycle = !offCycle; 
}

停止未使用的 Timer 对象

如果 Timer 对象的 timer 事件处理函数仅在特定的条件下执行操作,则当不符合这些条件时调用 Timer 对象的 stop() 方法。

在 enterFrame 事件或 Timer 处理函数中,尽量减少对可导致重绘屏幕的显示对象外观的更改

对于每个帧,呈现阶段都将重绘在该帧期间更改的舞台部分。如果重绘区域很大,或者很小但包含大量或复杂的显示对象,则运行时需要更多时间才能呈现。要测试需要重绘的量,请使用 Flash Player 调试版或 AIR 中的 “ 显示重绘区域 ” 功能。

ActionScript 3.0 性能

Vector 类和 Array 类

尽可能使用 Vector 类而不是 Array 类。Flash Player 10 引入了 Vector 类,其读写访问速度比 Array 类快。一个简单的基准就可说明 Vector 类与 Array 类相比的优势所在。以下代码显示 Array 类的基准:

var coordinates:Array = new Array(); 
var started:Number = getTimer(); 
for (var i:int = 0; i< 300000; i++) { 
	coordinates[i] = Math.random()*1024; 
} 
trace(getTimer() - started); 
// output: 107 
以下代码显示 Vector 类的基准:
var coordinates:Vector.<Number> = new Vector.<Number>(); 
var started:Number = getTimer(); 
for (var i:int = 0; i< 300000; i++) { 
	coordinates[i] = Math.random()*1024; 
} 
trace(getTimer() - started); 
// output: 72 

通过为矢量分配特定长度并将其长度设为固定值,可进一步优化此示例:

// Specify a fixed length and initialize its length 
var coordinates:Vector.<Number> = new Vector.<Number>(300000, true); 
var started:Number = getTimer(); 
for (var i:int = 0; i< 300000; i++) { 
	coordinates[i] = Math.random()*1024; 
} 
trace(getTimer() - started); 
// output: 48 

如果不提前指定矢量的大小,则矢量大小将在矢量用完空间后增加。每次矢量大小增加时,都将分配一个新的内存块。矢量的当前内容会复制到新内存块中。这种额外的分配和复制数据会降低性能。

事件捕获与冒泡

使用事件捕获和冒泡可以最大程度地减少事件处理函数。 ActionScript 3.0 中的事件模型引入了事件捕获和事件冒泡的概念。利用事件冒泡可帮助您优化 ActionScript 代码执行时间。您可以在一个对象 (而不是多个对象)上注册事件处理函数以提高性能。例如,想象创建这样一种游戏,在该游戏中用户必须以最快的速度单击苹果以将其销毁。该游戏将删除屏幕上各个被击中的苹果并为用户增加分数。要侦听由各个苹果调度的 MouseEvent.CLICK 事件,您可能会编写以下代码:

const MAX_NUM:int = 10; 
var sceneWidth:int = stage.stageWidth; 
var sceneHeight:int = stage.stageHeight; 
var currentApple:InteractiveObject; 
var currentAppleClicked:InteractiveObject; 
for ( var i:int = 0; i< MAX_NUM; i++ ) { 
	currentApple = new Apple(); 
	currentApple.x = Math.random()*sceneWidth; 
	currentApple.y = Math.random()*sceneHeight; 
	addChild ( currentApple ); 
	// Listen to the MouseEvent.CLICK event 
	currentApple.addEventListener ( MouseEvent.CLICK, onAppleClick ); 
} 
function onAppleClick ( e:MouseEvent ):void 
{ 
	currentAppleClicked = e.currentTarget as InteractiveObject; 
	currentAppleClicked.removeEventListener(MouseEvent.CLICK, onAppleClick ); 
	removeChild ( currentAppleClicked ); 
}

此代码对各个 Apple 实例调用 addEventListener() 方法。此外,它还会在用户单击每个苹果时使用 removeEventListener() 方法删除对应的侦听器。然而, ActionScript 3.0 中的事件模型为某些事件提供了一个捕获和冒泡阶段,允许您侦听来自父 InteractiveObject 的这些事件。因此,可以优化以上代码并在最大程度上减少对 addEventListener() 和 removeEventListener() 方法的调用次数。以下代码使用捕获阶段侦听来自父对象的事件:

const MAX_NUM:int = 10; 
var sceneWidth:int = stage.stageWidth; 
var sceneHeight:int = stage.stageHeight; 
var currentApple:InteractiveObject; 
var currentAppleClicked:InteractiveObject; 
var container:Sprite = new Sprite(); 

addChild ( container ); 

// Listen to the MouseEvent.CLICK on the apple's parent 
// Passing true as third parameter catches the event during its capture phase 
container.addEventListener ( MouseEvent.CLICK, onAppleClick, true ); 

for ( var i:int = 0; i< MAX_NUM; i++ ) 
{ 
	currentApple = new Apple(); 
	currentApple.x = Math.random()*sceneWidth; 
	currentApple.y = Math.random()*sceneHeight; 
	container.addChild ( currentApple ); 
} 

function onAppleClick ( e:MouseEvent ):void 
{ 
	currentAppleClicked = e.target as InteractiveObject; 
container.removeChild ( currentAppleClicked ); 
}

上述代码不仅经过了简化,而且在很大程度进行了优化,只对父容器调用了一次 addEventListener() 方法。侦听器不再注册到 Apple 实例,因此不需要在单击苹果时将其删除。可通过停止事件传播 (此操作将阻止继续传播事件)来进一步优化 onAppleClick() 处理函数:

function onAppleClick ( e:MouseEvent ):void 
{ 
	e.stopPropagation(); 
	currentAppleClicked = e.target as InteractiveObject; 
	container.removeChild ( currentAppleClicked ); 
}

其他优化

对于 TextField 对象,请使用 appendText() 方法,而不要使用 += 运算符。

myTextField.appendText("ActionScript 3"); 代替 myTextField.text += "ActionScritp 3";

尽可能避免使用中括号运算符。

使用中括号运算符可能会降低性能。将您的引用存储在本地变量中可避免使用该运算符。以下代码示例演示了使用中括号运算 符的效率很低:

var lng:int = 5000; 
var arraySprite:Vector.<Sprite> = new Vector.<Sprite>(lng, true); 
var i:int; 
for ( i = 0; i< lng; i++ )  { 
	arraySprite[i] = new Sprite(); 
} 
var started:Number = getTimer(); 
for ( i = 0; i< lng; i++ )  { 
	arraySprite[i].x = Math.random()*stage.stageWidth; 
	arraySprite[i].y = Math.random()*stage.stageHeight; 
	arraySprite[i].alpha = Math.random(); 
	arraySprite[i].rotation = Math.random()*360; 
} 
trace( getTimer() - started ); 
// output : 16 

以下优化的版本减少了对中括号运算符的使用:

var lng:int = 5000; 
var arraySprite:Vector.<Sprite> = new Vector.<Sprite>(lng, true); 
var i:int; 
for ( i = 0; i< lng; i++ ) { 
	arraySprite[i] = new Sprite(); 
} 
var started:Number = getTimer(); 
var currentSprite:Sprite; 
for ( i = 0; i< lng; i++ ) { 
	currentSprite = arraySprite[i]; 
	currentSprite.x = Math.random()*stage.stageWidth; 
	currentSprite.y = Math.random()*stage.stageHeight; 
	currentSprite.alpha = Math.random(); 
	currentSprite.rotation = Math.random()*360; 
} 
trace( getTimer() - started ); 
// output : 9 
尽可能使用内联代码以减少代码中函数的调用次数。

调用函数成本很高。尝试通过移动内联代码来减少函数的调用次数。移动内联代码是优化代码以获得良好性能的一种好方法。

但请注意,使用内联代码可能会使代码很难重复使用,还会增加 SWF 文件的大小。一些函数调用 (如 Math 类方法)很容易以内联的方式移动。以下代码使用 Math.abs() 方法计算绝对值:

const MAX_NUM:int = 500000; 
var arrayValues:Vector.<Number>=new Vector.<Number>(MAX_NUM,true); 
var i:int; 
for (i = 0; i< MAX_NUM; i++) { 
	arrayValues[i] = Math.random()-Math.random(); 
} 
var started:Number = getTimer(); 
var currentValue:Number; 
for (i = 0; i< MAX_NUM; i++) { 
	currentValue = arrayValues[i]; 
	arrayValues[i] = Math.abs ( currentValue ); 
} 
trace( getTimer() - started ); 
// output : 70

由 Math.abs() 执行的计算可以手动完成并以内联方式移动:

const MAX_NUM:int = 500000; 
var arrayValues:Vector.<Number>=new Vector.<Number>(MAX_NUM,true); 
var i:int; 
for (i = 0; i< MAX_NUM; i++) { 
	arrayValues[i] = Math.random()-Math.random(); 
} 
var started:Number = getTimer(); 
var currentValue:Number; 
for (i = 0; i< MAX_NUM; i++) { 
	currentValue = arrayValues[i]; 
	arrayValues[i] = currentValue > 0 ? currentValue : -currentValue; 
} 
trace( getTimer() - started ); 
// output : 15

以内联方式移动函数调用可使代码运行速度提高四倍以上。这种方法适用于多种情况,但要注意它对可重用性和可维护性的影响。

避免计算循环中的语句。

不计算循环中的语句也可实现优化。以下代码遍历数组,但未进行优化,因为在每次遍历时都需要计算数组长度:

for (var i:int = 0; i< myArray.length; i++) 
{ 
}

最好存储该值并重复使用:

var lng:int = myArray.length; 
for (var i:int = 0; i< lng; i++) 
{ 
}
对 while 循环使用相反的顺序。

以相反顺序进行 while 循环的速度比正向循环快:

var i:int = myArray.length; 
while (--i > -1) 
{ 
}
要尽量减少加减操作

字符串的每次加减操作,都会导致在内存中分配一个新的字符串。在进行比较多的操作的时候,可以用Array的join方法,来进行合并。

其他提示
  1. 不要过多使用 Object 类型
  2. 可以使用 Vector 来代替存储同类型对象的 Array ,获得跟好的性能
  3. Dictionary 的性能比较 Object 和 Array 高,建议多使用Dictionary
  4. BitmapData 有一个 dispose 方法,可以及时清除它占用的内存,请尽量使用
  5. 尽量减少场景中的对象的数量,从而提高整个程序的效率
  6. 游戏地图的绘制原则:
    • 移除屏幕可视区域外的可视对象
    • 尽量合并不需要移动的由多个子对象组成的对象,draw成bitmap.
    • 尽量不要直接使用矢量图,可先draw成bitmap再使用

导航