使用过flex的人都知道,它很耗内存,所以优化flex应用程序时非常重要的工作,也是必经之路,以下是我的笔记:
1, 合理使用布局
a) 避免多层嵌套容器:嵌套容器时,每个容器实例都会在其子对象上运行度量和缩放算法(某些子对象本身又是容器,这样度量过程将是递归的)
b) 使用绝对定位和缩放:默认使用的是相当布局,这样每个容器及其其子对象大小和位置的计算会占用大量资源,以下两个技巧有助于减少这个计算
i. 将对象位置固定坐标:运行时就不需要计算对象的位置,但是如果想采用这个技巧,可使用画布容器,其他类型的容器(如盒子容器)无法使用绝对位置,使用画布容器时,必须显示地声明所有Canvas子对象的x和y属性,如果没有设置,他们将在默认的x,y(0,0)处相互重叠,如果想应用程序随着浏览器窗口一起缩放,绝对定位就无能为力,建议先使用相对布局容器,然后再改为画布容器
ii. 将对象宽度和高度固定坐标:运行时就不需要计算对象的大小,这技术适合所有容器和控件
c) 导航式容器推迟实例化:ViewStack,Accordin,TabNavigator…..在使用容器时,可以使用creationPolicy 属性来控制子视图的创建,它的属性有:auto,all,none
Auto:不会立即创建所有的后代对象
All:立即创建所有的后代对象
Queued:按照队列的顺序依次创建后代对象,以creationIndex设定创建先后顺序
None:不会实例化所有的后代对象,直到实例化方法被调用,可以用createComponentsFromDescriptors()方法来显示地实例化视图
2. 使用动态样式
在初始化过程中尽早设定样式,可以避免不必要的样式通知和查找,在使用样式时,第一次为对象设定样式不要用setStyle()方法,应使用<mx:style>标签或者作为MXML标签的一个属性,通过外部css样式或作为全局样式来设定。如果某些对象实例化过程必须调用setStyle()方法时,可在实例化阶段较早调用setStyle方法,即从组件或应用程序的initialize()事件开始设定样式,而不是从createComplete或其他事件开始设定样式
3. 减少swf的体积
Mxml文件在显示时总是要编译swf文件在flash player中显示的,你要看一个mxml文件都是下载到本地后才会看到,打开的速度由以下两个因素决定
1) 网速速度
2) Swf的大小
i. 在编译mxml文件的时候使用-optimize编译参数
ii. 禁止调试,可以减去一些专门记录调试环境的数据
iii. 避免免入一些不必要的包和类,不要将包内所有的类都添加
iv. 减少内置资源的加载,例如使用[embeded].可以使用外部文件,在运行时载入,而不是在编译时载入
一基本原则:
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、当不需要一个类的实例的时候尽量用静态的属性或方法:
1
2
3
4
5
6
7
8
9
|
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;
}
}
}
|
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语句并没有什么区别:
语句:
1
2
3
|
if
( condition ) { // handle condition }
|
1
2
3
4
|
switch
( condition ) { case
"A " : // logic to handle case A break ;
case
"B " : // logic to handle case B break ; }
|
11、当你的程序处理分支较多的时候,你应该适当的排列他们出现的顺序,可以参照以下的方式进行:
1
2
3
4
5
6
7
|
if
( conditionThatHappensAlot ) { //处理经常发生的业务逻辑 } else
if
( conditionThatHappensSomtimes ) { // 处理偶尔会发生的业务逻辑 } else
{ // 处理几乎不会发生的情况 }
|
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的方法进行字符串的查找:
1
2
3
4
5
6
7
8
9
10
11
|
// 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、当你用过多的容器嵌套的时候会较低应用程序的性能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
< 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 >
|
1
|
< mx:Image
xmlns:mx = "http://www.adobe.com/2006/mxml "
source = "avatar.jpg "
width
= "200 "
height
= "200 "
/>
|
29、不要在Panel中vBox和HBox,用Panel的Layout属性就可以了
30、不要在application标签下用HBox,和Vbox,道理和29一样
31、设置recycleChildren为true来提高Repeater的性能 (重用已经创建过的children而不是重新创建一个新)
1
2
3
4
5
6
7
8
9
|
<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>
|
1
2
3
|
<?xml version = "1.0 "
encoding= "utf-8 " ?>
<mx:Application xmlns:mx=http: //www.adobe.com/2006/mxml frameRate="45 "> </mx:Application >
|
34、能用ENTER_FRAME事件就不用Timer事件.
1
2
3
4
|
public
function
onEnterFrame ( event : Event ) : void
{ } private
function
init () : void
{
addEventListener( Event.ENTER_FRAME, onEnterFrame );
}
|
1
2
3
4
5
6
|
public
function
onTimerTick( event : Event ) : void
{ } private
function
init () : void
{ var
timer : Timer = new
Timer();
timer.start ();
timer.addEventListener( TimerEvent.TIMER, onTimerTick );
}
|
1
|
<mx:Container creationPolicy= "queued " />
|
1
|
loginButton.visible = false
;
|
1
|
loginButton.alpha = 0 ;
|
四 自定义强制回收器----强制垃圾回收:(即著名的hack方式)
通过故意让SWF在运行时出错,然后throw出错误,而同时通过catch error来继续运行SWF文件。而垃圾回收机则会在SWF抛出错误的时候,被强制执行一次,以清除内存中无效的数据占用,减少资源的消耗。
下面是我找到一个通过这种hack方式处理垃圾回收的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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();