Flash應用效率優化啓示錄Ⅰ

比起其他語言,Flash ActionScript3的上手過程要簡單許多,對於很多人我想大多都是被這一點吸引進了咱們的圈子,很快就能看到很cool的效果,很好玩。不過實現一個效果容易,想對一個不論是簡單還是複雜的應用做到運行時一直保持高效率地運轉就是一個比較考驗Flash開發人員的事情了。
       “爲什麼我的應用越運行越卡?”這個問題有非常多的原因啦,我們一個個來看,對於效率優化也是一個很長的話題,這就是爲什麼我的標題裏要寫上一個一,以後想到一點加一點吧,希望對列位愛卿有所幫助,come on,let`s go! Enjoy!

●避免創建過多實例
     這個問題是很基礎的一點,一般新手比較容易犯,有一些老手在不注意的時候也容易出現實例過多造成的內存溢出情況。我們一起來看一個相當簡單的例子,我們想要在點擊舞臺時產生一個隨機顏色的小球,但是舞臺上一次只能存在一個小球,第二次點擊舞臺時,第一次點擊產生的小球會消失。來看看下面這種寫法:
我們需要的球類:

  1. public class Ball extends Shape
  2. {
  3.         private var _radius:Number;
  4.         
  5.         public function Ball( radius:Number = 5, color:uint = 0xff0000 )
  6.         {
  7.                 _radius = radius;
  8.                 var g:Graphics = this.graphics;
  9.                 g.clear();
  10.                 g.beginFill( color );
  11.                 g.drawCircle(0, 0, _radius);
  12.                 g.endFill();
  13.         }
  14. }
複製代碼
接下來開始測試:
  1. public class OptimizeYourFlashApp extends Sprite
  2.         {
  3.                 private var ball:Ball;
  4.                 
  5.                 public function OptimizeYourFlashApp()
  6.                 {
  7.                         stage.addEventListener(MouseEvent.CLICK, onClick);        
  8.                 }
  9.                 
  10.                 private function onClick( e:MouseEvent ):void
  11.                 {
  12.                         
  13.                         if( this.numChildren > 0 )
  14.                         {
  15.                                 for(var i:int=0; i<this.numChildren; i++)
  16.                                 {
  17.                                         this.removeChildAt(i);
  18.                                         i--;
  19.                                 }
  20.                         }

  21.                         ball = new Ball(30, Math.random() * 0xffffff);

  22.                         addChild( ball );
  23.                         ball.x = mouseX;
  24.                         ball.y = mouseY;
  25.                 }
  26.         }
複製代碼
很一般的做法對不對?好,讓我們用Flash Builder的profile來看一下內存測試結果:
1.jpg 
我們看到Ball類的實例數instances有兩百多個,這是當然的,因爲每次點擊後都會new一個新的Ball實例出來,這直接導致的結果就像我們上圖所示,佔有了47000多的內存,佔全應用內存使用量的99%以上,當前內存使用量(current memory)高達65K……如此簡單的一個應用就能在我點擊兩百次鼠標後達到65K的內存佔用。
雖然把沒有用的實例置爲空(如 ball = null )可以消除實例釋放內存,但是最好的辦法還是優化你的算法!
事實上我們可以改用別的方式來避免創建太多的實例,因爲我們要實現的功能是讓場景裏只存在一個球,而且每次點擊的顏色不同,那麼我們僅需每次點擊後改變原先的球的顏色即可。於是我們爲Ball類增加一個改變顏色的接口:
  1. class Ball extends Shape
  2. {
  3.         private var _radius:Number;
  4.         
  5.         public function Ball( radius:Number = 5, color:uint = 0xff0000 )
  6.         {
  7.                 _radius = radius;
  8.                 this.color = color;
  9.         }
  10.         
  11.         public function set color( value:uint ):void
  12.         {
  13.                 var g:Graphics = this.graphics;
  14.                 g.clear();
  15.                 g.beginFill( value );
  16.                 g.drawCircle(0, 0, _radius);
  17.                 g.endFill();
  18.         }
  19. }
複製代碼
這樣就不必每次都new一個實例了。在文檔類裏面應用起來:
  1. public class OptimizeYourFlashApp extends Sprite
  2.         {
  3.                 private var ball:Ball;
  4.                 
  5.                 public function OptimizeYourFlashApp()
  6.                 {
  7.                         stage.addEventListener(MouseEvent.CLICK, onClick);
  8.                         
  9.                 }
  10.                 
  11.                 private function onClick( e:MouseEvent ):void
  12.                 {
  13.                         
  14.                         if( this.numChildren > 0 )
  15.                         {
  16.                                 for(var i:int=0; i<this.numChildren; i++)
  17.                                 {
  18.                                         this.removeChildAt(i);
  19.                                         i--;
  20.                                 }
  21.                         }
  22.                         if(ball == null)
  23.                         {
  24.                                 ball = new Ball(30, Math.random() * 0xffffff);
  25.                         }
  26.                         else
  27.                         {
  28.                                 ball.color = Math.random() * 0xffffff;
  29.                         }
  30.                         addChild( ball );
  31.                         ball.x = mouseX;
  32.                         ball.y = mouseY;
  33.                 }
  34.         }
複製代碼
最後我們在profile裏看看內存使用情況:
2.jpg 
你懂的!

對於一些老鳥來說也會出現莫名其妙的內存溢出問題,有時候這種“偷襲”會讓我們不知所措,此時可能只有Flash Builder的profile能告訴我們是哪個狗東西搞得鬼。我們敬愛的導師MoonSpirit在使用Tweenlite時就有過內存溢出的經驗,Tweenlite是一個Flash動畫補間引擎,在之前的案例裏面我有提到過,它最最常用的方法是TweenLite.to()方法,我們一般都是直接使用這個靜態方法,而忽略了該方法會返回一個TweenLite的實例,因此我們每次調用TweenLite.to()方法時都會新聲明一個新的TweenLite實例,待實例一多就會出現內存溢出的危險,因此一個好的習慣是每次用TweenLite.to()方法後都及時把它生成的實例給幹掉,我們可以使用TweenLite實例它自帶的kill方法來做到:
  1. var tween:TweenLite = TweenLite.to(ball, 1, 
  2. {x:mouseX, y:mouseY, onComplete: function(){ tween.kill() } });
複製代碼
這樣就可以在每一次補間動畫播放完畢後讓to方法新生成的TweenLite實例停止播放並標記爲“回收”狀態,在下一次Flash Player垃圾回收階段會回收這些無用的實例,釋放內存。

●及時清理事件偵聽
這也是個老生常談的問題,很多前輩們都已經多次提到,我這裏只簡言帶過:一個實例中若是存在事件偵聽,那麼該實例及時用不到它它也會佔用內存,不能被垃圾回收。因此我們必須及時地移除事件偵聽,保持內存不會堆積廢物。被廣泛使用的一種設計模式是重載addEventListener方法使每次添加事件偵聽時都能把它保存到一個數組中,在該實例要下崗之時把數組中保存的事件偵聽一一移除,這樣就乾淨了……
  1. private var listeners:Array = new Array();
  2.                 
  3.                 override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
  4.                 {
  5.                         super.addEventListener(type, listener, useCapture, priority, useWeakReference);
  6.                         var newListener:Object = {lType:type, lListener:listener};
  7.                         listeners.push( newListener );
  8.                 }
  9.                 
  10.                 //調用此方法以清除全部已添加了的事件偵聽器
  11.                 public function clear():void
  12.                 {
  13.                         for each( var listener:Object in listeners)
  14.                         {
  15.                                 this.removeEventListener( listener.lType, listener.lListener );
  16.                         }
  17.                         listeners = [];
  18.                 }
複製代碼
有的時候,我們希望一個實例中的事件偵聽器長期偵聽事件,但是這個事件又觸發得不是很頻繁(通常是一些自定義的事件),那麼我們希望在事件一段時間內一直沒有觸發過的情況下不讓這個事件偵聽器妨礙垃圾回收,讓FP能順利釋放該實例所在內存。這時我們可以使用事件偵聽器的“弱引用”來做到在不移除事件偵聽器的情況下順利實現實例的垃圾回收。將一個事件偵聽器設置爲“弱引用”的方式及其簡單,只需設置addEventListener的第5個參數“useWeakReference”爲true即可。如下:
  1. addEventListener(MouseEvent.CLICK, onClick, false,  0, true);
複製代碼
我們保持第3,4個參數爲默認值,設置第五個參數爲true即可設置該事件偵聽爲弱引用,若一個實例中存在若引用的事件偵聽器那麼該實例在閒置時依然會被順利地垃圾回收並釋放內存,但是事件觸發時依然能夠準確地偵聽到事件。爲事件偵聽設置弱引用能夠極大地緩解Flash應用的“疲勞”,它在大型Flash應用的開發中已被廣泛使用。
      另外,由於事件偵聽器會影響實例的垃圾回收,所以建議儘可能少地添加偵聽器。在addEventListener方法中存在多個參數,我們剛纔提到的“弱引用設置”是最後一個參數,那麼還有兩個參數是比較陌生的,這裏談談第三個參數useCapture(使用捕獲),我們知道Flash事件機制中每一個事件的觸發都是從捕獲階段開始的,這個階段是從最外層容器向內部子容器遍歷以搜索事件發生者。那麼我們如果在一個父容器中設置useCapture=true,那麼該容器中所有子容器在發生事件時父容器都能偵聽到,這樣就可以避免爲每個子容器添加事件偵聽器:
  1. public function Test()
  2. {

  3.     for(var i:int=0; i<100; i++)
  4.    {
  5.         var child:CustomClass = new CustomClass ()//某個自定義類
  6.         addChild( child );
  7.     }
  8.     this.addEventListener( MouseEvent.CLICK, onClick, true)
  9. }

  10. private function onClick( event:Event ):void
  11. {
  12.      var c:CustomClass  = event.target as CustomClass;
  13.      trace( c );
  14. }
複製代碼
另外,對於帶多個動畫的Flash應用,建議不要爲每個動畫元件中添加ENTER_FRAME或者TIMER事件偵聽,而是建立一個總的時間管理者,在這個管理者中添加ENTER_FRAME或者TIMER事件偵聽並在事件處理函數中對所有動畫元件進行統一調度。

●避免過多運算
     AS語言的計算效率不必C語言等底層語言,我們在計算時應儘可能避免過多過頻繁地計算。比如下列一個循環:
  1. for( var i:int=0; i< array.length; i++ )//arrary是一個長度很長的數組
  2. {
  3.      //do something
  4. }
複製代碼
對於這個循環,每次循環開始時都會計算array的長度,如果array長度比較短還好,長度很長,循環次數很多時就會做很多冗餘的計算,好的習慣是事先計算好數組的長度再開始循環:
  1. var len:int = array.length;
  2. for( var i:int=0; i< len; i++ )//arrary是一個長度很長的數組
  3. {
  4.      //do something
  5. }
複製代碼
這裏注意一下數組長度會改變的情況,需要動態地更改長度纔行:
  1. var len:int = array.length;
  2. for( var i:int=0; i< len; i++ )//arrary是一個長度很長的數組
  3. {
  4.     arrary.pop();
  5.     len--;
  6. }
複製代碼
在《動畫高級教程》中的第一章:高級碰撞檢測中也提到過類似問題,在做大量物體的碰撞檢測時若是在每一幀都對每個對象進行遍歷,讓一個對象與其他對象依次進行碰撞檢測的話會進行大量的運算,對於N個對象,每幀將會進行(N^2-N)/2次測試,100個對象在每一幀都會進行4950次碰撞檢測,這將會拖垮CPU,因此我們必須採用一些好的算法來避免大量運算。

(轉載於天地會論壇)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章