在网上找了好多关于flex性能优化的文章,这里我把找到的那些都整理一下,以备后用。
一 基本原则:
1. 从外部加载媒体(Media) Heider提到了一个常用的Flex最佳实践——限制嵌入到应用/SWF文件中的媒体的数量,如图像、影片及mp3等资源都可以从外部的SWF文件加载。 Flex框架可以直接将图片、mp3及字体等资源编译到SWF中。当你想让最终用户获得全部资源时,这种方式确实能派上用场,但是这会导致你的应用长时间停留在“Loading”阶段。 2. 在嵌入式字体中限制字符集Heider建议在嵌入式字体中限制字符集以降低SWF文件的总下载时间: 当你在Flex中嵌入一种字体时,你就会获得该字体的全部字符的支持。尽管这可能是你想要的,但你确信你需要全部字符么?例如,在一个只面向英文的应用中,你确信你真的想花时间下载中文字符数据么?3. 缓存框架 Heider回顾了Flex 3 support for runtime-shared-libraries (RSL)这篇文章:从Flex 3开始,你可以将Adobe签名的框架——RSLs缓存到Flash Player的cache中。这有两个好处。首先,缓存在Flash Player cache中的签名的框架RSLs可由所有配置好的Flex应用共享。换句话说,如果某人的应用已经下载了500k的签名的框架RSL,并且该RSL仍旧 在Flash Player cache中,那么你的应用就可以使用缓存下来的RSL。其次,即使某人清空了其浏览器缓存,对Flash Player cache也没有任何影响。4. 考虑模块化 Heider谈到了将Flex应用划分成模块的好处:减少字体加载时间的另一种方式就是将你的Flex应用划分成模块。使用模块的一个好处在于当加载和卸载模块时你能完全操控它。之所以要划分成模块的最后一个原因是他们更快,而且我能即时加载它们。换句话说,在启动时唯一需要加载的模块就是 Step1.swf模块。因此,在使用模块的情况下,最终用户节省了启动时间,但是当他从一个模块切换到另一个模块时却需要花更多时间,因为每个模块都需 要以JIT形式加载。在我的应用中,只有当用户首次在steps 1-5之间切换时需要花更多时间。 5. 推迟实例化 Heider围绕着Flex组件的“creationPolicy”属性及何时实例化应用的不同部分给出了很多建议。 如果你想减少从数据下载到用户真正可以使用的总时间,当务之急就是推迟实例化。这项技术背后的理念就是直到应用真正使用的时候才在内存中创建对象。 尽管推迟实例化技术会在应用的整个使用过程中导致少许——通常不那么明显——的延迟,但与长时间的启动延迟相比,它还是可接受的。推迟实例化的另一个好处在于内存使用的优化。内存释放优化原则1. 被删除对象在外部的所有引用一定要被删除干净才能被系统当成垃圾回收处理掉;2. 父对象内部的子对象被外部其他对象引用了,会导致此子对象不会被删除,子对象不会被删除又会导致了父对象不会被删除;3. 如果一个对象中引用了外部对象,当自己被删除或者不需要使用此引用对象时,一定要记得把此对象的引用设置为null;4. 本对象删除不了的原因不一定是自己被引用了,也有可能是自己的孩子被外部引用了,孩子删不掉导致父亲也删不掉;5. 除了引用需要删除外,系统组件或者全局工具、管理类如果提供了卸载方法的就一定要调用删除内部对象,否则有可能会造成内存泄露和性能损失;6. 父对象立刻被删除了不代表子对象就会被删除或立刻被删除,可能会在后期被系统自动删除或第二次移除操作时被删除;7. 如果父对象remove了子对象后没有清除对子对象的引用,子对象一样是不能被删除的,父对象也不能被删除;8. 注册的事件如果没有被移除不影响自定义的强行回收机制,但有可能会影响正常的回收机制,所以最好是做到注册的事件监听器都要记得移除干净。9. 父对象被删除了不代表其余子对象都删除了,找到一种状态的泄露代码不等于其他状态就没有泄露了,要各模块各状态逐个进行测试分析,直到测试任何状态下都能删除整个对象为止。内存泄露举例:1. 引用泄露:对子对象的引用,外部对本对象或子对象的引用都需要置null;2. 系统类泄露:使用了系统类而忘记做删除操作了,如BindingUtils.bindSetter(),ChangeWatcher.watch()函数 时候完毕后需要调用ChangeWatcher.unwatch()函数来清除引用 ,否则使用此函数的对象将不会被删除;类似的还有MUSIC,VIDEO,IMAGE,TIMER,EVENT,BINDING等。3. 效果泄露:当对组件应用效果Effect的时候,当本对象本删除时需要把本对象和子对象上的Effect动画停止掉,然后把Effect的target对象置null; 如果不停止掉动画直接把 Effect置null将不能正常移除对象。4. SWF泄露:要完全删除一个SWF要调用它的unload()方法并且把对象置null;5. 图片泄露:当Image对象使用完毕后要把source置null;(为测试);6. 声音、视频泄露: 当不需要一个音乐或视频是需要停止音乐,删除对象,引用置null;内存泄露解决方法:1. 在组件的REMOVED_FROM_STAGE事件回掉中做垃圾处理操作(移除所有对外引用(不管是VO还是组件的都需要删除),删除监听器,调用系统类的清除方法)先remove再置null, 确保被remove或者removeAll后的对象在外部的引用全部释放干净;2. 利用Flex的性能优化工具Profile来对项目进程进行监控,可知道历史创建过哪些对象,目前有哪些对象没有被删除,创建的数量,占用的内存比例和用量,创建过程等信息;总结:关键还是要做好清除工作,自己设置的引用自己要记得删除,自己用过的系统类要记得做好回收处理工作。 以上问题解决的好的话不需要自定义强制回收器也有可能被系统正常的自动回收掉。
二 内存优化:
1.在代码中及时销毁对象,不要将函数级的变量定义成类级的变量。在页面销毁的时候要把类级别的变量一并销毁。
2.简化对象间的关系,尽量不要出现类和类之间的双向引用,双向引用对象销毁时可能会发生内存溢出。
3.事件监听及时移除,或者变成弱应用。
4.使用profile工具跟踪内存,查找内存泄露。
三 flex优化技巧
1、当创建一个数组的时候避免用new操作符,用 var a:Array = [];而不用var a:Array = new Array();
2、快速的复制一个数组: var copy : Array = sourceArray.concat (); 3、设置一个数组的值是非常忙的: employees.push ( employee ); employees[2] = employee; 4、从一个数组中取得值的速度是设置一个数组值的两倍快: var employee : Employee = employees[2]; 5、当不需要一个类的实例的时候尽量用静态的属性或方法: StringUtils.trim( "text with space at end " ); Class definition: package { public final class StringUtils { public static function trim( s : String ) : String { var trimmed : String ; // implementation... return trimmed; } } } 6、在整个程序的生命周期中都不会改变的变量用const定义常量: public const APPLICATION_PUBLISHER : String = "Company, Inc. "; 7、当一个类不需要有子类的时候应该将该类声明为final类型的: public final class StringUtils 8、变量和方法的长度在As3中并不影响什么性能,但在别的语言中可能就有影响: someCrazyLongMethodNameDoesntReallyImpactPerformanceTooMuch(); 9、将语句写在一行上面并不会影响AS3程序的性能,但在别的语言中却有影响: var i=0; j=10; k=200; 10、在内存占用上面if语句和switch语句并没有什么区别: 语句: if ( condition ) { // handle condition } 和语句: switch ( condition ) { case "A ": // logic to handle case A break ; case "B ": // logic to handle case B break ; } 占用的内存是一样的. 11、当你的程序处理分支较多的时候,你应该适当的排列他们出现的顺序,可以参照以下的方式进行: if ( conditionThatHappensAlot ) { //处理经常发生的业务逻辑 } else if ( conditionThatHappensSomtimes ) { // 处理偶尔会发生的业务逻辑 } else { // 处理几乎不会发生的情况 } 12、Actionscript虚拟机(Flash Player)推荐在循环内部用int而不是number,但是flash Player10在flash Player9的基础上做了很多的改进,int,uint和number之间的转换不在像以前那么慢了。 13、每个变量都应该声明一个确定的类型,解决那些没有指定类型的警告信息活错误信息。 14、尽量少用unint,它可能会非常慢,但是Flashplayer10做了改进,速度不像以前那么慢了: var footerHex : uint = 0x00ccff; 15、循环遍历的时候用int类型: for (var i: int = 0; i < n; i++) 而不用: for (var i: Number = 0; i < n; i++) 16、在用decimal的时候用number而不用int: var decimal : Number = 14.654; 而不用: var decimal : int = 14.654; 17、用乘法代替除法: 用100*0.01代替100/100 18、在for和while循环中用到的计算应事先声明好,而不是在循环中重复声明. for (..){ a * 180 / Math .PI ; } 应该在循环的外部申明: toRadians = a*180/Math .PI ; 19、避免在循环中调用方法或计算: var len : int = myArray.lengh; for (var i=0;i<len;i++){} 而不要用: for (var i=0;i< myArray.lengh;i++){ } 20、用正则表达式进行字符串的校验,用String的方法进行字符串的查找: // postal code validation example using regular expressions private var regEx:RegExp = /^[A-Z][0-9][A-Z] [0-9][A-Z][0-9]$/i; private function validatePostal( event : Event ) : void { if ( regEx.test( zipTextInput.text ) ) { // handle invalid input case } } // search a string using String methods var string : String = "Search me "; var searchIndex : int = string.indexOf ( "me " ); var search : String = string.substring ( searchIndex, searchIndex + 2 ); 21、重复使用诸如DisplayObjects和URLLoaderReuse之类的物体,以保持"内存平稳". 22、使用组件或创建自定义组件时应遵循Flex的组件模型,如下面的方法为组件创建时应先后调用的方法. createChildren(); commitProperties(); updateDisplayList(); 23、尽量少用dataGrid这样的重量级的组件,除非你用一个常规的list无法实现你要的功能。 24、避免用Repeater 控件创建scrollable数据. 25、尽量避免使用setStyle()方法,这个方法在Flex框架里面是众多代价敖贵的方法之一。 26、当你用过多的容器嵌套的时候会较低应用程序的性能: <mx:Panel> <mx:VBox> <mx:HBox> <mx:Label text ="Label 1 " /> <mx:VBox> <mx:Label text ="Label 2 " /> </mx:VBox> <mx:HBox> <mx:Label text ="Label 3 " /> <mx:VBox> <mx:Label text ="Label 4 " /> </mx:VBox> </mx:HBox> </mx:HBox> </mx:VBox> </mx:Panel> 27、没有必要每次都用容器组件作为你自定义组件的父控件: <mx:Image xmlns:mx="http://www.adobe.com/2006/mxml " source="avatar.jpg " width ="200 " height ="200 " /> 28、减少不必要的容器嵌套 29、不要在Panel中vBox和HBox,用Panel的Layout属性就可以了 30、不要在application标签下用HBox,和Vbox,道理和29一样 31、设置recycleChildren为true来提高Repeater的性能 (重用已经创建过的children而不是重新创建一个新) <mx:Script> <![CDATA[ [Bindable] public var repeaterData : Array = ["data 1 ", "data 2 "]; ]]> </mx:Script> <mx:Repeater id="repeater " dataProvider="{repeaterData} "> <mx:Label text ="data item: {repeater.currentItem} "/> </mx:Repeater> 32、将应用程序的帧率设置为60fps或者更低: <?xml version ="1.0 " encoding="utf-8 "?> <mx:Application xmlns:mx=http://www.adobe.com/2006/mxml frameRate="45 "> </mx:Application > 33、避免每一帧进行过多的显示操作. 34、能用ENTER_FRAME事件就不用Timer事件. public function onEnterFrame ( event : Event ) : void { } private function init () : void { addEventListener( Event.ENTER_FRAME, onEnterFrame ); } 而不用: public function onTimerTick( event : Event ) : void { } private function init () : void { var timer : Timer = new Timer(); timer.start (); timer.addEventListener( TimerEvent.TIMER, onTimerTick ); } 35、在多帧上面通过以下方式延迟物体的创建. <mx:Container creationPolicy="queued "/> 36、Alpha = 0并不是visible = false (设置为invisible的物体会被忽略,不作任何处理) loginButton.visible = false ; 而不用: loginButton.alpha = 0;
四 自定义强制回收器----强制垃圾回收:(即著名的hack方式)
通过故意让SWF在运行时出错,然后throw出错误,而同时通过catch error来继续运行SWF文件。而垃圾回收机则会在SWF抛出错误的时候,被强制执行一次,以清除内存中无效的数据占用,减少资源的消耗。下面是我找到一个通过这种hack方式处理垃圾回收的代码:
package util{ import flash.net.LocalConnection; import flash.system.System; public class Memory {
public function Memory() { //TO DO } public static function gc() : void { try { new LocalConnection().connect( 'foo' ); new LocalConnection().connect( 'foo' ); } catch ( e : * ) {} } public static function get used() : Number { return System.totalMemory; } }}关于上面代码如何使用,目前大致上有两种使用方法:1、在项目开始的时候,建立一个timer,然后每个一分钟就执行一次Memory.gc();2、找一台配置一般的机器,然后运行你要的程序。然后在CPU、Memory占用很高的地方,记录一下当时的内存值,之后再自认为需要的地方(例如位图运算、Effect效果完成后等地方),执行Memory.gc();