理解Flex3的组件和框架的生命周期(四)
作者:李学锟 类型:编译 来源:Flex爱好者专栏
理解Flex3的组件和框架的生命周期(三)
理解Flex3的组件和框架的生命周期(四)
Flex 组件开发最佳实践
我们已对Flex组件和应用程序的生命周期进行了完全探索,现在我们开始利用这些知识实现更好的组件功能。我们将检验不同的特点和如何改变它们使应用程序或组件表现出更好的性能。
使用Construction
对Flex开发过程中的误解之一是,利用构造函数来配置您的应用程序是一个好方法。从理解上讲,在构造函数中设置属性值是有道理的,但在研究了生命周期后,对构造函数依赖存在的潜在隐患变得更加明显了。
比如有这样一个例子,将样式属性设置在你的组件的构造函数中,这样存在潜在的危险。从技术上讲,这个例子是工作的;setStyle()方法存储着该值,当在添加阶段时这个值被应用。这种方法的问题是,样式可能是继承的链,可能不是一个有效的样式,可能在createChildren()方法后重写,等。这样就可能导致这种方法有潜在的意外问题。
另一个问题是,如果开发人员不完全了解生命周期,他/她可能决定尝试和访问一个并不存在的属性值。许多属性值,特别是子类的属性值并不存在,直到进入后来的生命周期中。有时,这种明显的错误第一次就能出现;但在其他情况下可能不会出现,直到一个特定的操作顺序发生。
JIT and the Constructor
不在构造函数中设置属性的另外一个理由是,因为类的构造函数总是有Flash Payer来解释。这意味着,实时编译器(JIT)进程启示不会存储构造函数的代码作为预编译代码供将来所使用。由于构造函数不会从JIT中获益,如果你必须在构造时计算,你应该将这段逻辑提取出一个单独的方法。
view plaincopy to clipboardprint?
public class MyCustomClass extends DisplayObject
{
public function MyCustomClass() {
this.construct();
}
public function construct():void {
this.addEventListener(FlexEvent.PREINITIALIZE, preinit)
this.addEventListener(FlexEvent.INITIALIZE, init);
}
}
public class MyCustomClass extends DisplayObject
{
public function MyCustomClass() {
this.construct();
}
public function construct():void {
this.addEventListener(FlexEvent.PREINITIALIZE, preinit)
this.addEventListener(FlexEvent.INITIALIZE, init);
}
}
将这段代码移到一个单独的方法中,这样使Flash Player有可能在JIT进程执行该计算。不幸的是,我并不能保证JIT都能执行你的方法,但将它从构造函数中移出来,对JIT的性能表现方面会有提升。
总之,构造阶段只是为组件的将来可供使用做准备,在这个阶段中只有少量的实例可配置或可用。我们建议你在这个周期内,执行尽可能少的工作或计算,并尝试将设置代码移动到组件的后期阶段。
使用Initialization
类似于构造阶段,初始化阶段是Flex开发者最可能被滥用和过度使用的阶段之一。对我们来说,最开眼界地研究这篇文章认识到我们不适当地使用initialize()方法。坦白地讲,作为开发者,使用initialize()方法做很多配置和设置是最糟糕的事件,特别是在使用视图源码分离的模式下。
我们采用initialize()方法的原因是,期望在所有的子类都已创建、样式已设置并可访问和多个组件已可使用的情况下,重新对它们的属性进行设置。对于我们许多人来说,Flex文档中第一个真正的灰色地带是寻找最佳位置以执行这些设置。
要确定这些配置的最佳位置,我们必须通过试验和一点动态地研究去发现它。通过这一点,我们认识了初始化阶段,它在当时像一个符合逻辑地方做这样的设置。一旦发现,我们就停止深入地研究,因为它已完成了相应的工作。令人遗憾的是,这种解决办法不总是一直工作。大多数情况下,它是没问题的;但有时或一段时间会有一个奇怪的现象出现,或在整合测试的过程中出现一些错误。
现在我们已对Flex的生命周期有了一个全面的研究,我们得到的结论是你不能重写initialize().若你查看了大多Flex UI组件,会发现它们决不重写initialize()。重写initialize()带来的问题是,在某一个层次中你期望更改的属性可能还不能用。
Flex组件的架构将初始化阶段分解为多个子步骤,这些子方法中可供我们重写。具体要重写哪个子方法,决定于你要做什么样的事件。如果你想在子类创建后访问它们的话,重写childrenCreated()这个方法比较合适。
如果你想等所有配置已完成和初始化已完成,就重写initializeComplete()方法吧。这个方法是当所有其它的初始化子方法调用完成后才调用。通过重写initialize(),你其实可以打破这种秩序,并且有可能进行更新后调用initializeComplete(),它应该永远是最后调用的方法。
若你必须重写Initialize...
可能有一种情况,虽然高度质疑,这时你必须重写initialize()方法。若真是这种情况,下面有一些需要你特点注意的:
首先,你必须确定代码先调用或后调用super.initialize()。当你调用super时,它会到达UIComponent级别的initialize()方法派发PREINITIALIZE事件,通知系统创建相关的子类。这个事件必须在组件子类创建前派发。
接下来,这个方法调用createChildren(),childrenCreated()方法随后,最后调用initializeComplete()。若你的代码调用在super之前,需要注意子类上下层次的链还不存在。若你的代码在super后面,你应该将代码执行在initializeComplete()方法后,这个方法打破了Flex组件定义的调用顺序。
正如告诉你的那样,重写initialize()是一个99.9%会失败的办法。所以,我们再交建议你不要选择这种方法,应该选择其它合适的位置来设置。
失效-生效周期和方法
理解失效-生效周期对开发Flex组件和应用程序是很重要的。许多工作都是在这个周期内做的,并且在整个应用程序的生命周期中该周期是不断发生的。在这一章节,我们研究一些技术来改变这个周期,并查看应用这些技术能带来哪些好处。
分离这个阶段
也许你能回忆起,Event.RENDER事件将失效-生效周期分为了两个完全不同的阶段。这样做的原因是,为了确保有能力执行那么需要生效的代码,阻止执行哪些没有必要的代码。
我们将举例说明如何写代码才能利用失效-生效阶段的优点。首先,我们来看一个不正确的代码,然后检查是如何改变整个周期的。
在我们的例子中,有一个组件,它有一个dataValue的属性,并且值是数字型数组。当我们的dataValue属性改变时,首先遍历整个数组计算出总值。一旦总值更新后,我们需要使用绘图的API把该总值画到图表上,并显示到屏幕上。
不知道失效-生效周期的开发者,也许会写成下面的代码:
view plaincopy to clipboardprint?
[Bindable] public var total:Number;
private var __dataValue:Array;
public function get dataValue():Array {
return __dataValue;
}
public function set dataValue(value:Array):void {
__dataValue = value;
// calculate the total value
var len:int = value.length;
total = 0; // reset total
for(var i:uint=0; i < len; i++) {
total += Number(value[i]);
}
// draw the data to screen
drawChart();
}
[Bindable] public var total:Number;
private var __dataValue:Array;
public function get dataValue():Array {
return __dataValue;
}
public function set dataValue(value:Array):void {
__dataValue = value;
// calculate the total value
var len:int = value.length;
total = 0; // reset total
for(var i:uint=0; i < len; i++) {
total += Number(value[i]);
}
// draw the data to screen
drawChart();
}
从根本上讲,上面的代码是工作的,但是每次值改变都要执行一个潜在的大过程。回头查看一下可变跑道的章节,dataValue值在下一次屏幕显示前可能会改变多次。
这意味着dataValue值每次改变我们都更新和计算total的值,这也踢开了绑定事件,因为我们在改变total的值。我们还调用drawChart()方法来更新UI的显示。因为在每次值的改变时,我们可能不会每次都执行屏幕的重绘;我们也没有必要每次都高用绘图的API和占用没必要的周期。
所以如何解决上面代码产生的问题呢?我们必须做两件事,首先我们得准备让dataValue值准备渲染到屏幕前,允许它改变多次。第二,我们需要将计算和绘图移出setter()方法,并添加到生效方法中去,这样当值改变了它只执行一次绘图。
使用标识
第一步我们解决这个过程引入标识这个概念。标识是一个布尔值,当一个值改变时标为true。我们检查标识的状态,若为true,我们知道相应的值改变了。来看一下我们如何改写代码实现标识的功能。
view plaincopy to clipboardprint?
[Bindable] public var total:Number;
private var __dataValue:Array;
private var _dataValueDirty:Boolean = false;
public function get dataValue():Array {
return __dataValue;
}
public function set dataValue(value:Array):void {
if(__dataValue != value) {
__dataValue = value;
_dataValueDirty = true;
invalidateProperties();
invalidateDisplayList();
}
}
[Bindable] public var total:Number;
private var __dataValue:Array;
private var _dataValueDirty:Boolean = false;
public function get dataValue():Array {
return __dataValue;
}
public function set dataValue(value:Array):void {
if(__dataValue != value) {
__dataValue = value;
_dataValueDirty = true;
invalidateProperties();
invalidateDisplayList();
}
}
Figure 13 Using Dirty Flags Example
在 Figure13中,我们添加了一个新变量叫_dataValueDirty。这个布尔属性就是我们的标识。当dataValue改变时,我们首先检查并确保值是发生改变的,然后将新值赋给__dataValue,并将标识设为true,然后调用我们的失效方法。
如果你还记得,曾经在失效-生效周期章节中我们讲过,如何通过LayoutManager来标识组件的失效方法,并在生效阶段中进行验证。设置标识符和调用失效的方法,并将失效阶段和生效阶段的dataValue属性分开。这意味着,dataValue 的setter方法在失效阶段可能会被调用多次,其它的代码不会执行直到进入生效过程。这也保证了内容绘制到屏幕之前,我们的绘图API调用将只发生一次。
实现生效方法
现在我们要生效我们的组件,在下一个生效阶段到来时我们如何应用计算和绘图到UI呢?来回顾一下组件阶段:生效(生长成熟),我们重写commitProperties()和updateDisplayList()方法:
view plaincopy to clipboardprint?
private var _chartDirty:Boolean = false;
override protected function commitProperties():void {
super.commitProperties();
if(_dataValueDirty) {
_dataValueDirty = false;
// calculate the total
var len:int = __dataValue.length;
var _total:Number = 0;
for(var i:uint=0; i < len; i++) {
_total += Number(__dataValue[i]);
}
// set the total
total = _total;
_chartDirty= true;
}
}
override protected function updateDisplayList(
unscaledWidth:Number,
unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(_chartDirty) {
_chartDirty= false;
// draw the data
drawChart();
}
}
private var _chartDirty:Boolean = false;
override protected function commitProperties():void {
super.commitProperties();
if(_dataValueDirty) {
_dataValueDirty = false;
// calculate the total
var len:int = __dataValue.length;
var _total:Number = 0;
for(var i:uint=0; i < len; i++) {
_total += Number(__dataValue[i]);
}
// set the total
total = _total;
_chartDirty= true;
}
}
override protected function updateDisplayList(
unscaledWidth:Number,
unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(_chartDirty) {
_chartDirty= false;
// draw the data
drawChart();
}
}
Figure 14 Overriding the Validation Methods Example
生效方法调用的明确的顺序:commitProperties(),measure(),layoutChrome()和最后updateDisplayList()。正如我们以前提及的,这个顺序很重要因为计算和属性设置在commitProperties()方法中,这些可能会影响尺寸和显示列表。
看一下Figure 14 Overriding the Validation Methods Example的代码,我们绘图时需要total的值或更新显示列表,所以应该先计算。这就是我们为什么使用失效方法的原因。因为我们需要计算total值并绘制图表,所我们需要确保invalidateProperties()和invalidateDisplayList()都在 Figure 13 ‐ Using Dirty Flags Example 的setter方法中调用,这样在生效阶段就会调用commitProperties()和updateDisplayList()方法。
但commitProperties()方法被调用,我们首先看_dataValueDirty是否被设成ture。做这个检查是确定值是否改变,若没改变则计算和重绘的代码都不要执行。如果我们的值改变,接着计算total总值,设置total新值,并将_chartDirty设为true;这样在updateDisplayList()方法中就可以重绘图表了。
一旦commitProperties()方法完成,layoutManager则调用updateDisplayList()方法,在该方法检查chart 标识是否为true。若chart标识为true,则调用drawChart()。
乍一看,似乎我们为了完成这个简单的功能添加了许多代码,但将代码分开放在各自的周期阶段中,使我们免除了以后的潜在危险和保证了程序的良好性能。
使用和访问样式
最后一个最佳实践我们来讨论Flex组件样式的访问和设置。这是一个简短的样式调查,因为样式系统取决于将来和提供功能的复杂性。在本章节,我们只是简单地看一看什么时候和什么地方可以访问它们的属性。
设置样式可以在任何时候,因为setStyle()方法是很灵活并能够保存该值的。然而,仅仅因为你可以在任何地方设置一个样式,并不意味着它是一个很好的做法。
当配置一个组件的默认样式时,较好的做法是放在Addition阶段,这样在定义默认属性前,它可以检查样式是否已被提前设置。其中我们建议的位置是放在childrenCreated()方法中。这样允许地在刚创建的子类上访问定义和设置样式。
访问样式是另外不同的一个问题。样式直到UIComponent类调用addingChild()时才定义。这个方法的职责是通过主题和继承来寻找应用的样式。如果在这之前,想通过getStyle()方法来获取样式的话,你将得到不可预知的结果。这也意味着,在定义样式之前访问样式的话,你的组件必须有一个父类。
应用样式
重要的是要知道样式在生效阶段才被应用。当开发自定义组件,子类或绘制UI,我们必须等到updateDisplayList()方法被调用后,这样样式的属性才能被访问和将样式应用到UI上。
帮助Flex组件框架在这一过程的提高,有一个 叫styleChanged()方法可供重写,来在生效阶段改变样式。
继承在我们的dataValue组件例子上讨论,在该组件里,有一个名字为chartLineColor样式用在drawChart()方法中,该样式是用来定义线条的颜色。
view plaincopy to clipboardprint?
[Style(name="chartLineColor",type="uint",format="Color",inherit="no")]
[Style(name="chartLineColor",type="uint",format="Color",inherit="no")]
Figure 15 Style Metadata Example
保证当样式改变时,我们的图表重绘并当新的颜色。来重写一下styleChanged()方法:
view plaincopy to clipboardprint?
override public function styleChanged(styleProp:String):void {
super.styleChanged(styleProp);
switch(styleProp) {
case "chartLineColor":
_chartDirty = true;
invalidateDisplayList();
break;
}
}
override public function styleChanged(styleProp:String):void {
super.styleChanged(styleProp);
switch(styleProp) {
case "chartLineColor":
_chartDirty = true;
invalidateDisplayList();
break;
}
}
Figure 16 Style Changed Example
当样式的属性更改后,styleChanged()方法被调用并调用更改样式的名字传递过来。在我们例子代码里,查看chartLineColor样式是否是改变的,若是,将_chartDirty标为true并失效显示列表。当下一个生效阶段出现时,我们将设_chartDirty为false并调用drawChart()方法。
在我们的应用程序开发中用失效-生效周期,这只是一个简单的例子。有很多方式我们可以使用这个阶段来改变组件的外观和感受。
总结
我们希望本书能给你在开发中带来一些帮助,特别是在Flex组件和应用程序生命周期这块。正如前面提到的,这是我们从代码观点第一次审查和分析生命周期,结合我们的经验给出最佳的实践方法。如果你对这本书有问题或评论,请与我们联系,欢迎您的反馈信息。
- 上一篇:
- 理解Flex3的组件和框架的生命周期(三
- 相关教材
- 理解Flex3的组件和框架的生命周期(三
- 最近更新
