方法调用时很耗内存的,尤其是在调用一个不同类的方法时。如果这个类属于一个不同的包或者是一个静态的方法那就更糟糕了。这有一个最好的例子就是Math.foor()方法,它使用了Flames类去让小数四舍五入为整数。这种方法调用可以用uint类型而不是Number类型存储整数的方式避免。
- // So instead of having:
- anchors[i] = Math.floor( i * colWidth );
- // we instead cast the value to a uint
- anchors[i] = uint( i * colWidth );
- // The same optimization can be performed by simply assigning the uint data type, for example changing
- varsize:uint= Math.floor( particle.size * particle.life/100);
- // into
- varsize:uint= particle.size * particle.life/100;
- 复制代码
在上面的例子中,Math.foor()的调用时不必要的,因为当指定数据类型为整形时FLASH会自动将小数四舍五入。
步骤七:乘法运算会快于比值运算
Flash播放器显然知道乘法比比值容易,因为我们在Flames类这块要把所有比值运算转换为等价的乘法运算。换算公式包括获取运算符右值的倒数,并且乘以左值。一个数的倒数就是用1比上这个数。
- varcolWidth:uint= frame.width / 10; //division by ten
- varcolWidth:uint= frame.width * 0.1; //produces the same result as multiplication
- 复制代码
我们来快速看一下最新优化效果的结果。CPU使用率最后从41.7%下降到37.5%,得到了性能提升,但是内存消耗展现了不一样的结果。内存最大消耗值已经升到了9.4MB,处于最高水平,图表牙齿形状锋利的线条展现了内存回收运行更频繁了。因此优化技巧在对内存和CPU使用率上有相对作用,提升一个却降低了另一个。
随着内存消耗差不多返回到矩形的形状,有很多事情要去做了。
步骤八:回收对环境有利
你也可以在优化环境方面进行改进你的代码。当在写AS3代码时回收你的对象去减少程序资源的消耗。创建对象和销毁对象都是耗内存的操作。如果你的程序需要不断地创建和销毁同一类型的对象,回收这些对象能获得很大的效益。来看Flames 这个类,我们可以看到有很多粒子对象在每秒钟内被不断地创建和销毁。
- privatefunctiondrawParticles( event ):void{
- createParticles( 20);
- fireMC.graphics.clear();
- for( vari:* infireParticles ) {
- varparticle:Object= fireParticles[i];
- if(particle.life == 0) {
- delete( fireParticles[i] );
- continue;
- }
- 复制代码
回收对象有很多种方法,大多都是创建一个秒变量去存储暂时不需要的对象而不是去销毁它们。然后当一个新的对象类型需要的时候,它就从存储器中回收而不是重新创建。仅仅是在存储器为空是,新对象才会被创建。在Flames这个类中,我们就要用类似的方法处理粒子对象。
首先,我们创建一个名为inactiviveFireParticles[]的数组,它存储了生命值为0的例子。在drawParticles()方法中,我们加上一个inactiviveFireParticles[]数组,而不是删除生命值为0的粒子。
- privatefunctiondrawParticles( event ):void{
- createParticles( 20);
- fireMC.graphics.clear();
- for( vari:* infireParticles ) {
- varparticle:Object= fireParticles[i];
- if( particle.life <= 0) {
- if( particle.life == 0){
- particle.life = -1;
- inactiveFireParticles.push( particle );
- }
- continue;
- }
- 复制代码
接下来,我们修改一下createParticles()这个方法获取存储在inactiveFireParticles[]数组中的粒子,并使用它们创建新的粒子。
- privatefunctioncreateParticles( count ):void{
- varanchorPoint = 0;
- for(vari:uint= 0; i < count; i++){
- varparticle:Object;
- if( inactiveFireParticles.length > 0){
- particle = inactiveFireParticles.shift();
- }else{
- particle = newObject();
- fireParticles.push( particle );
- }
- particle.x = uint( Math.random() * frame.width * 0.1) + anchors[anchorPoint];
- particle.y = frame.bottom;
- particle.life = 70+ uint( Math.random() * 30);
- particle.size = 5+ uint( Math.random() * 10);
- if(particle.size > 12){
- particle.size = 10;
- }
- particle.anchor = anchors[anchorPoint] + uint( Math.random() * 5);
- anchorPoint = (anchorPoint == 9)? 0: anchorPoint + 1;
- }
- }
- 复制代码
步骤九:无论何时尽可能使用对象和数组常量结构
当我们在创建新的对象和数组时,常量结构会比新的实例操作高效。
- privatevarmemoryLog:Array= newArray(); // array created using the new operator
- privatevarmemoryLog:Array= []; // array created using the faster array literal
- particle = newObject(); // object created using the new operator
- particle = {}; // object created using the faster object literal
- 复制代码
步骤10:避免使用动态类
在AS中的类既可以被封装也可以是动态的。它们以默认的方式封装,意味着一个对象只能继承已经在类中定义的属性和方法。在动态类中,在运行周期内可以添加新的属性和方法。封装类比动态类高效很多,因为一些flash播放器在已经了解一个类所有可能的方法后可以进行性能优化。在Flames类中,对象类的基础上扩展了成千的粒子,这是动态的。因为没有心得属性需要在一个粒子的运行周期内加上,那么我们可以通过给粒子创建封装类的方法来节省内存资源。这是新的粒子对象,已经在相同的Flames.as文件中加上了。
- classParticle{
- publicvarx:uint;
- publicvary:uint;
- publicvarlife:int;
- publicvarsize:Number;
- publicvaranchor:uint;
- }
- 复制代码
方法crateParticles()会被调整,改变代码行为:
- varparticle:Object;
- particle = {};
- 复制代码
替换:
- varparticle:Particle;
- particle = newParticle();
- 复制代码
步骤十一:当你不必使用时间轴时使用Sprites
像对象类一样,MoiveClips也是动态类。MoiveClip类继承了Sprite类,这两者主要的区别就是MoiveClip有时间轴。因为Sprites类拥有除了时间轴外MoiveClip的所有功能,所以当任何你需要一个DisplayObject时而不需要用时间轴的时候使用Sprite。Flames类扩展了MoiveClip但它不需要使用时间轴,因为它通过AS代码来控制动画。火焰粒子在fireMC中绘制,这也是使用了MoiveClip但不需要使用时间轴。我们修改一下Flames和fireMC替换为扩展Sprite:
- importflash.display.MovieClip;
- privatevarfireMC:MovieClip = newMovieClip();
- publicclassFlames extendsMovieClip{
- 复制代码
以及
- importflash.display.Sprite;
- privatevarfireMC:Sprite = newSprite();
- publicclassFlames extendsSprite{
- 复制代码
步骤十二:当你不需要使用子对象显示和鼠标输入时使用Shapes替代Sprites
Shape类甚至比Sprite类轻量级,但是它不支持鼠标事件也不能包含子显示对象。就如fireMC不需要两者中的任何功能,我们可以安全地把Sprites变为Shape.
- importflash.display.Shape;
- privatevarfireMC:Shape = newShape();
- 复制代码

- for( vari = 0; i < memoryLog.length; i++ ){
- // loop body
- }
- 复制代码
下面的代码就是drawGrapf()方法中的第一个循环。循环执行的时候经过了memoryLog数组的每一项,在图中使用每个值去绘制点。每次执行的开始,他都会检查memoryLog数组的长度并且和循环计数器比较。如果memoryLog数组有200项,循环200次就会检查200次。因为memory数组的长度没有发生改变,重复的检查是浪费时间且不必要的。所以最好在检查开始前和存储局部变量之前对数组长度只检查一次,因为获取本地变量会比获取对象的属性效率高。
- varmemoryLogLength:uint= memoryLog.length;
- for( vari = 0; i < memoryLogLength; i++ ){
- // loop body
- }
- 复制代码
在Flames类中,我们调整drawGraph()方法中的两个循环,如上所示。
步骤十四:条件语句尽可能先设置为真
想到了到下面if…else这块的条件语句,3是从drawParticles()方法继承而来的:
- if( particle.life > 90){ // a range of 10 values, between 91 - 100
- size *= 1.5;
- }elseif( particle.life > 45){ // a range of 45 values, between 46 - 90
- particle.x += Math.random() * 6- 3;
- size *= 1.2;
- }else{ // a range of 45 values, values between 0 - 45
- transperency = 0.1;
- size *= 0.3;
- particle.x += Math.random() * 4- 2;
- }
- 复制代码
粒子的生命值可以是0到100的任何值。If语句检测粒子的生命值是否是在91到100之间,如果是就执行块内的代码。Else-if语句检测生命值是否在46到90之间,同时else语句对生命值在0到45之间的粒子进行一些赋值操作。考虑到粒子符合第一个检测的可能性比较小而且检测范围小,它应该是最后检测的条件。所给出的这块需要重写,因此尽可能低优先考虑条件,使得检测更高效。
- if( particle.life < 46){
- transperency = 0.1;
- size *= 0.3;
- particle.x += Math.random() * 4- 2;
- }elseif( particle.life < 91){
- particle.x += Math.random() * 6- 3;
- size *= 1.2;
- }else{
- size *= 1.5;
- }
- 复制代码
步骤十五:在数组的末尾添加元素时不要用push操作
Array.push()方法在Flames类中使用得相当多。它可以用使用数组的长度属性操作来替代。
- cpuLog.push( cpu ); // slow and pretty
- cpuLog[ cpuLog.length ] = cpu; // fast and ugly
- 复制代码
当我们知道数组长度的时候,就可以更高效地替代Array.push()了。如下所示:
- varoutput:Array= []; //output is a new, empty array. Its length is 0
- for( vari:uint= 0; i < steps; i++ ){ // the value of i also starts at zero. Each loop cycle increases both i and output.length by one
- varprogress:Number= i / steps;
- varcolor:uint= Color.interpolateColor( color1, color2, progress );
- output[i] = color; // faster than cpuLog[ cpuLog.length ] = cpu;
- }
- 复制代码
步骤十六:用向量替代数组
数组和向量类都是很小的,除了有两个主要的区别向量只能存储同种类型的对象,这比数组更高效。因为在Flames中所有的数组存储的既是同种int\uints类型的变量而且存储的是向量,就像所说的那样:我们把它们都转化为向量。数组:
- privatevarmemoryLog:Array= [];
- privatevarcpuLog:Array= [];
- privatevarfireParticles:Array= [];
- privatevarpalette:Array= [];
- privatevaranchors:Array= [];
- privatevarinactiveFireParticles:Array= [];
- 复制代码
用等价的向量替代后:
- privatevarmemoryLog:Vector.<Number> = newVector.<Number>();
- privatevarcpuLog:Vector.<Number> = newVector.<Number>();
- privatevarfireParticles:Vector.<Particle> = newVector.<Particle>();
- privatevarpalette:Vector.<uint> = newVector.<uint>();
- privatevaranchors:Vector.<uint> = newVector.<uint>();
- privatevarinactiveFireParticles:Vector.<Particle> = newVector.<Particle>();
- 复制代码
然后我们修改getColorRange()方法以向量的方式执行而不是数组:
- privatefunctiongetColorRange( color1, color2, steps):Vector.<uint>{
- varoutput:Vector.<uint> = newVector.<uint>();
- for( vari:uint= 0; i < steps; i++ ){
- varprogress:Number= i / steps;
- varcolor:uint= Color.interpolateColor( color1, color2, progress );
- output[i] = color;
- }
- returnoutput;
- }
- 复制代码
步骤十七:少用事件模板
AS事件模板是基于事件监听器、分发器和对象的严密结构之上的,便捷上手:但存在事件传递和事件不断,还有更多其它的,所有这些结果都能写成一本书了。所以必要的时候,通常直接创建一个方法而不是使用事件模板。
- addEventListener( Event.ENTER_FRAME, drawParticles );
- addEventListener( Event.ENTER_FRAME, getStats );
- addEventListener( Event.ENTER_FRAME, drawGraph );
- 复制代码
Flames类有三个不同的事件监听器,都是ENTER_FRAME事件。在这个例子中,我们可以保留第一个事件监听器移除另外两个,也可以把drawParticles()方法和称为getStats(),把getStats()称为drawGraph()。我们可以有选择性地创建一个新的函数直接称为getStats(), drawGraph() 和drawParticles () ,然后对新方法只设置一个事件监听器。第二种选择无论如何都代价高,因此我们选择第一种。
- // this line is added before the end of the <code> drawParticles </code>() method
- getStats();
- // this line is added before the end of the <code> getStats() </code> method
- drawGraph();
- 复制代码
我们也可以在drawGraph()和getStats中移除事件的参数(传递事件的对象),因为不再需要它们了。
步骤十八:在不需要显示对象时禁用所有的鼠标事件
因为FLASH动画不需要任何用户的交互,我们可以释放掉显示对象从不调用鼠标事件。在Flames类中,我们是通过设置mouseEnable属性值为false来实现的。我们也可以把它的所有子类mouseChildren的属性设置为false。把下面的代码行加在Flames的构造函数中:
- mouseEnabled = false;
- mouseChildren = false;
- 复制代码
步骤19:使用Graphics.drawPath()方法来绘制复杂形状
Graphics.drawPath()在绘制有很多直线和曲线的形状时可以优化性能。在Flames.drawGraph()方法中,CPU使用率和内存消耗的图形线是用Graphics.moveTo()和Graphics.lineTo()方法结合绘制的。
- for( vari = 0; i < memoryLogLength; i++ ){
- ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
- xpoint = (i / sampleSize) * graphWidth;
- if( i == 0){
- graphMC.graphics.moveTo( xpoint, -ypoint );
- continue;
- }
- graphMC.graphics.lineTo( xpoint, -ypoint );
- }
- 复制代码
我们用Graphics.drawPath()代替原始的绘图方式。修改后的代码有一个额外的好处就是我们也可以从循环中移除绘图命令。
- varcommands:Vector.<int> = newVector.<int>();
- vardata:Vector.<Number> = newVector.<Number>();
- for( vari = 0; i < memoryLogLength; i++ ){
- ypoint = ( memoryLog[i] - memoryMin ) / memoryRange * graphHeight;
- xpoint = (i / sampleSize) * graphWidth;
- if( i == 0){
- data[ data.length ] = xpoint;
- data[ data.length ] = -ypoint;
- commands[ commands.length ] = 1;
- }
- data[ data.length ] = xpoint;
- data[ data.length ] = -ypoint;
- commands[ commands.length ] = 2;
- }
- graphMC.graphics.drawPath( commands, data );
- 复制代码
步骤二十:把类设定为final型
Final属性指定不能被重写的方法或类不能被扩展。它也能使类更高效,因此我们可以把Flames和Particles类设置为final类型。

CPU使用率现在是33.3%,而内存占用量总共维持在4.8MB到5MB。我们从CPU使用率为41.7%和内存占用为9MB到现在花了这么久!这让我们在优化过程中得出的最重要的结果就是:知道什么时候停止。如果处理的太早,你的游戏或者应用就会在低端系统上表现效果很差,如果又太迟的话,你的代码会因为得到一大堆的模糊处理而很难维护。动画作为特定的应用程序看起来更加平稳流畅,同时CPU和内存也在控制之中,,所以我们会停在这里。