理解Flex3的组件和框架的生命周期

时间:2022-04-25 17:21:54

说明

Adobe Flex 框架的SDK美中不足之处就是部分由良好的半黑盒子系统所创建;这就是说,在大多数情况下,我们这些开发者在项目周期中没有时间或精力去真正地深入到未知领域。从技术上来讲,Flex 框架的不是一个黑盒子,你可以阅读和查看它的所有源码。由于代码的复杂性及它是如何设计的,我们往往把框架看成是如何输入、得到什么样的输出。大多数开发人员,包括作者,往往在工作中学习Flex,通过验证、试验、研究或按照他人写的博客提到的观点来寻找新的技巧和技术。

Adobe已经做了非常了不起的工作是Flex框架的文档,它已被细分为两大类:用户指南和API(ASDoc)文档。然而,即使他们有大量的文档,用户指南和API文档之间的还是有一个很大的差距。用户向导涵盖了一系列的课题,从如何开始到使用比较复杂的功能;但都停止在这些不很复杂的功能上,比如:样式扩展的技巧,元数据可以做和不可以做的,当然也包括Flex框架和组件生命周期。有很高层次的概述,但这些都在用户能弄明白的能力范围之外。

从API文档中我们可以推断出很多功能和命令,有时它们是引导我们,但也有时关于一些信息会戏弄我们。通常,API文档假定你知道要查找的东西,它们只是解释一下如何去做;但不会告诉你何时或为什么去用它们。由于这种宏观的指导与微观的API之间的差距,优化开发成为了一个黑色艺术,需要程序员通过多年的经验在试验和错误中确定最佳的做法。加上这些与Flex只有5年左右的历史,它从最初的表现进化到了引人注目的阶段,而我们大多处于技术的初级阶段。

然而,作为第三方的开发者,并不是唯一留在外面的。多数Adobe的工程师也没有完全了解框架的细微部分。詹姆斯参加了Adobe 2008年MAX会议关于Flex组件的介绍和Deepa论坛关于Flex的生命周期评价。

她的报告一半是关于生命周期,甚至其中一半是关于在当前Flex3的生命周期,其余的是关于Flex4的变化。

随着她在高层次的深入概述,提到近期一个Flex架构师使她最终通过了整个生命周期。她对观众说希望能早点了解这些信息,这样能使她能成为一个更好的工程师。

我们的观点是Flex迷惑了我们许多人,甚至一些最受尊敬的工程师。这是一个复杂的系统设计得非常强大,易扩展,灵活。然而,这种灵活性推展和实现创造了许多可能性。这些可能性很多都不是最好的开发方式,甚至有可能不利于整体性能,稳定性和可扩展性的应用前景。

本文的目的是尝试照耀到整个生命周期,使我们一些启示,作为一个开发社区,用Flex能创造更好的应用程序和组件。值得提醒的是,以下信息大部分是从阅读源代码推断的,一些是好的文档,也有一些不是。如果你看到一些不太正确的,或许可以用一个更好的解决方式,欢迎随时联络DevelopmentArc我们(info@developmentarc.com),以便我们尽可能添加/更新此文档,使之在技术上正确的。


如何阅读

在整本书中我们所涉及许多的Flex框架源码,但为了简洁,我们不总是显示所指的代码。当你阅读这本书时,要求你打开Flex Builder,或能够访问Flex3框架的源码,跟随着我们所讨论源码是怎么工作及为什么这样做。

如果你跟着阅读源码,请注意,我们经常跳过功能或者具体的代码,以便我们可以对应当前的主题。这样能防止我们远离当前的主题,主要是讲解代码的微妙之处。这并不是说那些代码的作用不重要,而是那些代码处理特别的案例,防止潜在的错误或在生命周期的后面来处理,只是我们当前没有讨论它。

Flex 简史

在我们进入Flex框架细枝末节及如何利用它的讨论之前,我们应该退一步,看看整体的Flex大局,为什么我们要使用它。许多读者也许已有相关的Flex经验,也许你有很多的开发经验;但是为了了解Flex小组的决定,我们有必要去看一看Flex和Flash Player的历史。这一点很重要,因为Flex框架的许多部分对Flash Player相当较低。


都是关于帧

从根本上讲,Flex 就是 Flash. 在DevelopemntArc,当我们坐下教授初级开发者或客户时,关于Flex我们都会继续重复这一点。因为Flex最终生成的SWF文件和Adobe Flash Professional 工具开发生成的一样。这就意味着,Flex 和任何 Flash SWF都是一样地遵守相同的规定。一个最根本的规则就是所有的SWF文件都要转换成以Flash为基础框架。对于那些不熟悉Flash历史的人来说,玩家的最初目标就是创建动画。传统的动画是以细胞为基础,你画的图像在某个细胞被稍微修改的图像的细胞所替换,它在一定的速度下,看起来像有运动的变化。所以,Flash使用frame创建相同的动画效果来代替细胞。帧依然存在,所有的逻辑和屏幕的渲染依赖于帧改变和帧率定义的大小。

帧率告诉Flash每秒多少帧应该尝试处理; Flex的默认设置为每秒24帧。这并不能保证Flex 每秒都是24帧,只保证做的帧数不会超过24. 实际的帧率受很多因素的影响,比如:复杂的代码被执行后,造成数量的改变需要重新呈现在屏幕上;另外,当然包括机器处理FlashPlayer的表现能力。


可变跑道

Ted Patrick 写了一篇关于Flash 帧执行顺序的优秀文章并将它命名为可变跑道。关于Flash Player7的旧文章,从理论上讲对于今天的Flash Player 10仍然成立。

他的文章主要讲的是将Flash 帧分为两个阶段:代码执行和屏幕重绘/渲染阶段。代码执行是线性的,在当前帧要求所有的代码都执行完,并没有其它线程。在这个过程中,产生数据,计算数值和修改显示的列表。一旦所有的代码执行完,显示列表就会更新,接着Flash Player把改变的部分渲染到屏幕上。

随着Flash Player 9和Action Script 3的发布,开发组还创建了新的Action Script 虚拟机(AVM2) 支持新的语言及特点。他们还重新设计了Flash DOM 使新的Player能支持早期的开发。Sean Christmann, 运用Ted Patrick的可变跑道概念将EffectiveUI进行了更新,并在新的虚拟机中显示出来。

Sean给跑道添加了“The Marshal”这个概念。老的虚拟机(AVM1)和AVM2最主要的不同是AVM1只工作在一片帧的环境下。AVM2的Marshal主要负责为Flash Player分割时间片去执行;最重要的在前面声明这些时间片,与将帧率编译在一个swf文件中并不一回事;播放器最终从这些时间片合成一个帧率。

这些时间片被分为多个步骤来完成:播放器事件的派发,用户代码的执行,预渲染事件的派发,用户代码的代码和最终播放器的渲染。Sean 研究了这些步骤,什么时候执行和当被执行时帧率是如何被修改的。

我们强烈建议阅读本书前先查阅这两篇文章,因为我们的讨论将一直涉及到帧,及Flex框架在整个系统的重要性。


管理跑道

执行代码和渲染显示内容需要花费很多的时间去完成,代码越复杂或改变显示的内容越多,则渲染花费的时间更久。其中最常见令开发者比较头痛的是可变跑道在处理大数据量时,动画的表现总是滞后。

例如,在DevelopmentArc的一个客户端工程中,用Flex Tween 系统创建了一个下载的动画。单独运行它时看上去非常好,一旦我们去向服务端下载数据时,动画就变得非常波动。导致这处现象的原因是我们要求播放器向服务端请求一个大量的XML数据,同时试图以编程方式让动画在屏幕上进行移动。

我们已做的是超负荷的跑道(帧播放器代码方面的),播放器在 每个周期花费太多时间去计算,所以导致没有足够的时间去渲染。这就是说播放器的帧率没有完全满足动画显示的需求,因大多时间被计算所占去。Ted's 在跑道这篇文章的最后评语是:

正如前面说过很多次,‘只是等待一帧’。

这引导我们回到Flex 框架上来。由于Flex框架的复杂性,想象它将如何执行,如果要在一个单一的每一帧处理嵌套的布局计算(HBox,VBox),动画(特效,过渡),数据加载(远程对象,HTTP服务),用户交互(鼠标,键盘),所有的定制代码。这将使播放器屈服和Flex 完全无法使用。

框架大部分为我们所做的就是‘等待一帧’。它的架构设计就是以这种方式来创建阶段和步骤进行优化播放器该如何工作,你可以利用这一优点,采用和开发API 钩子。

Flex 应用程序的生命阶段

所有的生物生命周期我们都分为:出生,生长,成熟,死亡。正如生物的生命周期,Flex框架和它的组件也有相似的过程。我们将检验出生过程,看组件是如何被创建的,如何生长及成熟,到最后将从系统中它们移除时即为生命的终止。

在查看完组件后,我们将查看Flex应用程序框架的生命周期。我们将重新查看Flex框架是如何开始,及与Flash Player结合,创建它们的程序和它们的子组件。我们将会关注程序在生长阶段会发生什么现象及在成熟阶段当与用户交互时会发生什么。最后我们看一下应用程序的销毁阶段。

理解这些所有的因素和它们发生的顺序将会帮助你设计和开发出更好的组件和应用程序。一旦你完全了解它们的生命过程,利用它们的优点来为你的程序添加新的功能。

组件生命周期的介绍

在讨论Flex 应用程序如何开始和创建它们的孩子之前,我们就先花点时间看一看一般组件的生命周期。首先理解组件的生命周期是很重要的,因为Flex应用程序的生命周期是组件生命周期的扩展,再加上一些重要的修改来帮助性能和其它所需要的系统功能。

组件阶段:概述

一个Flex组件都经过七个不同的阶段,我们可以划分到4个生命阶段。由于组件整个生命周期主要有七个阶段,一些阶段是重复的出现,有的只出现一次。这七个阶段是构建,添加,初始化,失效,验证,更新和清除。

这并不是Adobe官方的条目,但我们把这些阶段组织在一起并分类,有利于帮我们定义组件什么时候做什么样的事件。我们的七个阶段按四个生命阶段来分。出生阶段由构建,添加,初始化组成。生长和成熟阶段由失效,验证,更新来组成。死亡就是清除阶段。来看一下第一个阶段:构建。

组件阶段:构建(出生)

构建阶段是组件的第一个阶段,定义为组件的构建。Adobe Flex 文档用Button 作为它们的示例控件,如下:

view plaincopy to clipboardprint?
var myButton:Button = new Button(); //这是我们的构建阶段  
var myButton:Button = new Button(); //这是我们的构建阶段

很明显的,对吧?什么是不很明显的,在这一阶段很少发生。在构建阶段会发生什么呢?

如果我们从Button's的构造器中开始,跟进super()方法链,先到UIComponent类,然后到FlexSprite,最后到Sprite(到这里为止,因为它的源码包含在playerglobal.swc中)。FlexSprite是在继承链中是足够的顶端了,可以从这里看清整个构建过程了,所以我们就从这里开始。

查看FlexSprite的构造器所做的工作仅是给name属性赋值了一个唯一的文本值。因为FlexSprite构造器所发生的情况已结束,我们再往下看UIComponent构造器。UIComponent在构造时做得稍微多一点,也不很多。设置焦点管理,开始跟踪自己的添加和删除事件,侦听焦点事件、键盘事件,定义自己的资源管理索引,保存当前组件自己的私有宽度和高度值,这些宽度和高度也许已在父类中定义。只做了这么多,没有布局、样式和子类的创建。

最后,我们回到Button 类。Button 的构造函数里所做的是注册侦听MouseEvents的事件来处理用户的交互。只有这么多。

关于在自定义组件开发中使用构造器,在后面的章节‘使用构造’最佳实践会有更详细的信息说明。以上是我们的构造阶段,下一阶段是什么?

组件阶段:添加(出生)

当你将组件添加上父组件时,就发生了‘添加’阶段:

view plaincopy to clipboardprint?
this.addChild(myButton);//这是添加阶段的开始  
this.addChild(myButton);//这是添加阶段的开始

将组件添加到父类这个过程中,父类要执行大量的功能,这也是生命周期进行更高层次的阶段的地方。让我们查看一下UIComponent的addChild()方法,看它是如何定义添加阶段。

addChild()方法被分解成3个子方法:addingChild(),$addChild()和childAdded().将一个阶段分解成几个子步骤来完成,这种模式在Flex框架中是很常见的。一个复杂的过程分解成几个小节来完成,这样做能更好地控制整个过程。我们在以后的章节中将会继续讲解这种模式的好处,另外在Flex 组件开发最佳实践章节中会有理详细的讲解。

当addChild()方法被调用时,先执行addingChild()方法。addingChild()方法为我们做了大量的工作,比如子类的父类引用,设置子类的document(父类 Application)引用,定义布局管理器(这对以后的阶段来说是很重要的),定义哪些字体是可用的,开始样式的传播及组件样式的管理。

一旦 addingChild()方法完成,addChild()方法将调用Flash Player 级别的将组件添加到显示列表的$addChild()方法,确保播放器的Flash 帧在渲染阶段将组件绘制在屏幕上。

最后调用的子方法是childAdded(),它决定着子类控件是否已初始化完成,如果没有初始化完成,则调用子类的initialize()方法,完成添加阶段后,进行初始化阶段。

组件的阶段:初始化(出生)

初始化阶段主要负责组件的子类的创建和为进行第一个失效和验证环节做准备。Initialize()方法是被父类的childAdded()方法所调用,它被分解成4个子方法:createChildren(),childrenCreated(),initializedAccessibility()和initializationComplete().

当initialize()方法被调用时,UIComponent先派发FlexEvent.PREINITIALIZE事件,然后调用createChildren()方法来确保相关的组件被创建,接着为自己添加子组件。

例如,Button组件重写了UIComponent的createChildren()方法来生成TextField的实例作为Button的标签文本,设置TextField实例的styleName属性,然后将TextField的实例做为孩子通过addChild()方法进行添加。

当createChildren()方法完成后,将调用childrenCreated()方法。这个方法主要是为进入失效-验证环节做准备,就是下一面我们要谈的内容。

Initialize()方法最后调用的两个方法是initializeAccessibility()和initializationComplete(). initializaeAccessibility()方法是用Flash Player的无障碍系统来设置组件的相关属性。initalizationComplete()方法是最后一个被调用的方法,给processedDescriptors 设置器进行赋值,从而派发FlexEvent.INITIALIZED事件。

尽管初始化已经结束,但在使用这个组件之前还有些任务要做。关于如何使用初始化,请参考后面的"使用初始化"章节。

组件状态:失效(生长/成熟)

失效是在组件生命中第一个可重复生命周期,也是生长和成熟的第一个阶段。在createChildren()方法被调用后,UIComponent类的childrenCreated()方法被调用。在这个方法里,在失效阶段3个子方法被调用:invalidateProperties(),invalidateSize()和invalidateDisplayList().

失效和生效环节

不讨论与生效阶段的交互和定义,我们就不能深入地了解失效阶段。

在前面我们提到了Flex框架在程序会使用‘等待下一帧’来管理性能。失效-生效环节是Flex框架在整个生命周期中第一次显示它权力的地方。

失效-生效周期给我们提供了一个解耦的过程。从值处理过程中单独地改变值。解耦这一过程,我们可以得到很多方便之处,比如推迟大数量的处理直到代码处理的最后阶段,还有就是阻止没必要的代码重复执行。 看下面的例子:

view plaincopy to clipboardprint?
myButton.width = 20;   
myButton.width = 25;  
myButton.width = 20;
myButton.width = 25;

在上面的代码中,让我们想像一下,设置myButton的width后立即计算更新控件的尺寸;这种改变将导致它的所有者的宽度需要重新布局,设置孩子组件的宽度将更新它们的布局,然后通知他们的父类的宽度改变,可能引起其它相关组件的宽度属性或重新计算。

现在,执行到下一条时,设置宽度后将把所有的改变再重新执行一遍。这样执行时间就会变成双倍的了,若设置宽度是多次的话,执行时间就会大幅度增长从而在没有必要的条件导致Flash 帧的延长。我们如何能让它只执行一次,而且只执行最后一次设置宽度的代码呢。

失效和生效周期使用标识和推迟执行所需的代码来解决这个问题。我们看一下UIComponent的width设置器:

 


view plaincopy to clipboardprint?
Public function set width(value:Number):void{   
if(explicitWidth != value){   
explicitWidth = value;   
invalidateSize();   
}   
//other code follows...  
Public function set width(value:Number):void{
if(explicitWidth != value){
explicitWidth = value;
invalidateSize();
}
//other code follows...

这个方法首先检查一下值是否发生了变化,若没则忽略本次更新。若有变化,存储新值并调用invalidateSize()。再看一下invalidateSize()方法:

view plaincopy to clipboardprint?
public function invalidateSize():void  
{   
if(!invalidateSizeFlag)   
{   
invalidateSizeFlag = true;   
if(parent && UIComponentGlobals.layoutManager)   
UIComponentGlobals.layoutManager.invalidateSize(this);   
}   
 
public function invalidateSize():void
{
if(!invalidateSizeFlag)
{
invalidateSizeFlag = true;
if(parent && UIComponentGlobals.layoutManager)
UIComponentGlobals.layoutManager.invalidateSize(this);
}
}

invalidateSize()这个方法是比较简单,但作用很强大。它首先使用invalidateSizeFlag这个值来检查是否已调用过invalidateSize()方法。若是第一次被调用,invalidateSizeFlag为false并设它为true,检查若有父类,然后将自己注册了LoayoutManager的实例。若我们在前面已调用过invalidateSize(),并没有进入生效阶段的话,我们不用担心无法注册layoutManager,因为我们已经在下一个生效阶段中注册了对size的生效。这时我们对Button的width在生效阶段到来之前进次N次的改变,也不会影响性能。

现在已把我们的组件注册到LayoutManager上了,一些独特的代码已经执行了。LayoutManager主要是做什么的呢,一旦stage派发Event.RENDER事件后,就进行生效阶段,这时它将调用一个隐藏的UIComponent实例的callLater()方法。如果你还记得Sean Christmann的文章,当RENDER事件派发后并在Flash Player往屏幕上绘制显示列表之前,是允许执行用户代码的。通过运行验证阶段向保证我们,最后的代码将在屏幕上呈现前执行。

LayoutManager作为一个排除系统,在下一个RENDER事件之前将跟踪所有已注册失效改变的组件。当LoayoutManager得到RENDER事件,它将检查所有在失效阶段注册他的组件,并在这些组件上开始执行生效阶段。若LayoutManager在一个组件的生效阶段过程中进行设置时,又有新组件的注册到LayoutManager上,这些新组件将在排除等到下一个RENDER事件。这有助于防止在一个单一的过程进入一个无限循环的更新。

失效和生效的过程是一个周期,是因为组件一个属性在任何时候被失效后;生效过程必须执行一次,它组件设置成可用的状态。

三种类型的失效

再回到Button例子,在第一个失效阶段我们调用三个失效方法:invalidateProperties(),invalidateSize(),和invalidateDisplayList().

重要的是要知道在失效阶段过程中有什么样的改变,会调用这些方法。当组件重新计算和布局前属性的改变,则会调用invalidateProperties()方法。一些(属性或操作)需要组件重新计算它们的尺寸或它们孩子的尺寸时,invalidateSize()方法被调用。最后,当一些改变需要组件更新显示列表时,调用invalidateDisplayList()方法。
 

组件阶段:生效(生长成熟)

当改变了一个属性,比如:data,它不需要组件改变大小或更新列表,它改变时,只需数据执行一些计算。生效阶段有4个子方法:commitProperties(),measure(),layoutChrome(),和updateDisplayList()。

不像失效阶段,生效阶段在执行这些方法时有一个预定的顺序:先commitProperties()方法,再执行layoutChrome(), 第3个是执行layoutChrome()方法(若有必要的话),最后执行updateDisplayList()方法。生效阶段有这种线性安排是非常重要的,因为我们的顺序验证生效(应用设置)。

例如,我们使用invalidateProperties()方法来表明一个或多个属性已改变,我们得确保在开始布局我们的控件之前使属性生效,因为在布局阶段时可能需要这些新的属性值。

每个失效的方法都有一个相应的生效方法:invalidateProperties()对应commitProperties(),invalidateSize()对应measure()和invalidateDisplayList()对应updateDisplayList()。当我们使一个组件失效时,LayoutManager会跟踪到组件的哪个失效方法,这样在生效阶段时,只调用相应的生效方法。

例如,假设我们的组件data属性改变了,若这个值没有影响到尺寸或组件的显示列表,然后,在失效阶段我们只需要调用invalidateProperties()方法就可以了。也就是说,当下一个RENDER事件被派发时,LayoutManager检查组件,只查看生效的属性,然后调用commitProperties()方法。

生效阶段有第四个方法,正如我们在图中看到的,叫layoutChrome()。layoutChrome()方法没在UIComponent中定义,而在Container类中定义了。当创建一个容器时,你会经常创建一个边框或给子组件设置padding样式,我们必须执行这个方法。layoutChrome()方法在measure()方法后执行,这样就明白子组件的尺寸了并应用'chrome'。

目前我们已定义了失效-生效阶段,让我回头看一下Button组件的创建过程。若能回忆起,在初始化阶段孩子被创建(标签的TextField),然后调用childrenCreated()方法中的3个失效阶段的方法。

这时候,组件正好有parent并访问LayoutManager,所以3个失效的方法用LayoutManager注册到我们新的Button,并等待下一个RENDER事件来处理这些改变。3个方法被调用,因为在组件的生命周期中这是第一个生效阶段。

在LayoutManager在组件上执行完所需的生效方法后,LayoutManager来检查组件是否被标识成已初始化,若没有,则将组件标识成已初始化。此时,我们的组件被认为已初始化和已更新。LayoutManager将派发FlexEvent.UPDATE_COMPLETE事件。


组件状态:更新(成熟)

一个组件在失效后,进入生效阶段中,任何时候都是更新阶段。一旦组件被生效,LayoutManager就会再次派发UPDATE_COMPLETE事件。更新周期会一直重复直到组件被移除。最重要的是要知道,更新阶段在组件生命周期中花费时间最多的。程序与用户的交互过程都被认为是更新阶段的改变。关于如果管理更新阶段更新的情况,请参考后面的章节(生效阶段和方法)。

组件的阶段:移除(死亡)

组件的最后一个阶段是移除阶段。当组件不再有父组件时这个阶段就发生了。在多数情况下,这种阶段发生在父组件调用removeChild()方法时,将组件作为参数进行传递。为什么说是大多数情况,因为组件的父类组件或更深的父类组件,当被从显示列表的堆栈中移动时可能会进行移除阶段,取决于这些组件在程序中是否有引用。关于这一点我们再多介绍一下:

当使用removeChild()来删除一个孩子时,例如:

view plaincopy to clipboardprint?
this.removeChild(myButton);  
this.removeChild(myButton);
 

父组件的removeChild()方法调用3个子方法,就像addChild()方法一样。第1个方法是removingChild()默认是不做任何事件的(UIComponent级别的控件)。它可以被重写,这样在组件从显示列表前做一些特定的功能。

接下来调用Flash Player级别的$removeChild()方法,在显示列表中做真正移除并检查该组件在内存中是否有引用存在。若该组件在内存中已没有引用,则将它标识成可供垃圾回收的。(例如其它对象指引该对象,事件的侦听者是强引用等,都表明存在引用)

最后,childRemoved()方法被调用,主要是清空该组件的parent和document属性。这样能确保访组件在需要时再次被指定父组件,并阻止该组件进行失效-生效的阶段。

此时,该组件处于在最后的阶段。若没有其它引用指向该组件的话,它将被垃圾回收器回收并从内存中删除。如该组件已被回收,则它的所有子类组件也被回收。也就是说,会发生在前面描述的那样,移除的发生并不直接通过removeChild()方法。

若父组件,祖父组件或任何父类被移除,并没有任何引用这组件在移除组件父类时,这是所有的组件都会被垃圾回收。

例如,我们有一个以Button作为孩子的HBox。如果我们要从显示堆栈中移除HBox时,HBox则进入移除阶段。这一过程中该HBox不会移除任何它的孩子,且它的任何孩子都不会进入移除阶段。但是,若这个HBox和这个Button都不存在引用时,当一定时候垃圾回收器会对这两个组件进行回收。

一个Flex应用程序的诞生

我们已经对组件的生命周期有了一个很好的理解,现在我们来看一下Flex应用程序的生命周期。Flex应用程序的生命周期和组件的生命周期很相似,但有一些非常重要的补充和修改,以提供一个支持管理完整的系统。我们将应用程序的生命周期分为:构造,初始化,预加载,创建子类,显示子类和销毁。


Flex应用程序阶段:构造阶段

Flex应用程序的构造阶段要比组件的构造阶段有一点深入,因为它是在编译好的SWF装载后,在任何类的实际构造之前开始的。

当一个SWF已装载入播放器时,SWF的第一帧已做好了准备,接着就开始执行。Flash Player的最强大的特点之一就是不需要把所有的内容都加载完才开始执行。播放器主要的职责是流媒体的内容和对已加载的进行执行。这种能力保证了较大的SWF的文件在播放内容的同时加载余下的部分。


Flex编译器的黑暗艺术

从我们目前的研究来看,第一个Flex框架的代码片断是关于SystemManager的创建和执行部分。至于SystemManager是如何与SWF连接和定义,并作为第一个类被创建和执行的,这些问题在目前有效的文档里并没有明确的描述。我们只知道Flex 编译器在它生成和输入SWF文件时定义了这个连接,并定义了应用程序的SystemManger类作为SWF根部的MovieClip类,这就告诉了Flash Player在加载时构造SystemManger实例。

不幸的是,关于Flex编辑器的实际过程和它是如何创建最初的帧栈的问题,有点超出了本文所讨论的话题。有希望的是,随着我们研究的深入,有可能更新本节的内容并给编译器关于创建第一帧的堆栈更详细的解释。目前为止,我们尽可能地解释清楚。所以现在,耐心与我们从SystemManager构造开始。


首要位置的SystemManager

每一个Flex应用程序(RIA或AIR)都有一个SystemManager实例来负责管理应用程序窗口的所有显示内容。SystemManager ASDoc 注释中是这样描述应用程序窗口的:应用程序中用来显示的一片可视的区域。它可以是操作系统的一个窗口或浏览器的一个区域。

这就表明一个SystemManager对应所有的窗口。当SystemManager被创建,它首先检查自己是否是当前窗口的第1个SystemManager。在本文中我们假定所创建的是第1个SystemManager,并且我们不研究子类的SystemManager和子系统之间如何管理的。

这个SystemManager是否是当前的应用程序窗口默认的SystemManger取决于,stage属性是否定义在构造方法中。

一个stage是所有显示对象的基类,包括SystemManager。正如Highlander,每个应用程序窗口只能有一个stage。第1个挂在stage上的DisplayObject是根对象,并且这个DisplayObject的root属性设置为本身。其它所有的显示对象都挂在根对象上,并将它们的root属性指引到根对象上。如下图,应用窗口的层级显示出它们的结构是如何组织的。

当一个DisplayObject被添加到舞台上,它的stage属性被定义并指向当前应用程序的stage。只有根DisplayObject的stage属性设置在构造方法中,其它显示对象的stage属性在该对象被添加到显示堆栈(如addChild(),Loader,等)时设置。所以当继承了MovieClip->DisplayObject的SystemManager在构造阶段,先检查stage属性是否已定义。

一旦SystemManager确定了它的root属性,它有权力来访问自身的loaderInfo属性。这个loaderInfo属性含有LoaderInfo类的对象,LoaderInfo类允许访问信息,比如源SWF的url,传给SWF的参数,域信息,内容,已下载的字节,总字节,等。

在典型的Flash/Flex开发中,当加载应用程序内容时,我们通常访问SWFLoader的loaderInfo详细的信息或其它以Loader为基类的信息。当应用程序正在加载目标内容时,利用loadInfo对象作为取得当前加载的信息。在这种情况下,SystemManager不会作为应用程序的子类去加载;它会被Flash Player自身去加载。根DisplayObject的是指引到Flash Player的loaderInfo对象,因此,我们基本上可以观看我们自己的SWF加载。

LoaderInfo 类其中一个特点就是派发Event.INIT事件,当被加载的SWF的所有属性和方法都可被访问,第1帧的子类对象已构造和所有第1帧的ActionScript代码执行完后,该事件才会派发。这是很重要的,因为SystemManager注册自身的loaderIfno属性,侦听Event.INIT事件的派发。当该事件被派发后,SystemManager就会知道初始化所使用到的重要信息和其它的配置信息已经加载完毕并可以使用了。

Flex 应用程序的阶段:初始化

一旦SystemManager的Event.INIT事件派发后,SysttemManger就开始应用程序的初始化阶段。SystemManager首先要做的是检查它的父类,看自己是否是根,注册Event.ENTER_FRAME事件(当每个新的Flash帧开始时派发),然后调用自己的initialization()方法。

在initialization()方法里,SystemManager设置自身的宽度和高度来封装应用的尺寸,然后创建Preloader类的实例。在我们开始创建应用程序之前,需要加载所有必要的辅助库和资源库来确保程序的功能。

外部资源管理

Flex3一个强大的特点就是使用远程共享库(RSL)r的外部资源管理和本地资源管理。为什么要使用外部的内容,有很多理由,比如说,下载字节的大小,共享资源,目标语言等。

这种技术的一个风险就是当程序需要它们时,要确保它们的已被加载并是可用的。所需的外部内容是委托给SystemManager,它使用Preloader来加载内容并把反馈信息显示给用户。

一旦SystemManager创建Preloader,它就注册Preloader的FlexEvent.INIT_PROGRESS事件和FlexEvent.PRELOADER_DOWN事件,当所需的外部资源所加载完成后就开始Application类的构造-初始化阶段。接着,Preloader实例对象作为子类添加到SystemManager上,但此时数据还没有开始加载。

SystemManager在使用Preloader开始加载内容之前,首先需要创建一个所需资源的列表。Preloader的第1个外部内容列表是RSL列表,它是应用程序所需要的。RSLs列表提供了一个info()方法来供外部访问,info()这个方法是编译器来完成的。

一旦RSLs列表定义完成后,SystemManger创建ResourceManager的实例化对象来提供Preloader来访问已编译的资源包。一个默认的资源包通常是编译在应用程序里的,Preloader可用使用它给用户显示本地的信息,或当应用程序在运行时报错时,能够显示本地语言的错误提示信息。若系统在编译时没有把资源包加入的话,它不会显示本地化的信息,因为Preloader没有加载额外的资源。

该SystemManager建立了基础的StyleManager使Preloader可以使用它,然后SystemManager解析任何通过SWF的flash vars的变量,以确定用该应用程序

使哪些区域设置。接下来,SystemManager建立外部资源列表通过定义在flash vars里的URL来加载,确定是否显示一个自定义的Preloader界面(编译器通过info()方法),然后Preloader开始传递外部资源列表里的资源来加载,显示类用来渲染Preloader。

Flex 应用程序:预加载

Preloader 采用外部资源列表,创建RSLListLoader的实例,并开始加载内容。随着RSLs和资源绑定的加载,进度事件被派发并通知Preloader来更新显示的进度条。一旦Preloader加载所有的RSLs,它将派发FlexEvent.INIT_PROGRESS事件来通知SystemManager表示所有所需要的外部资源已加载完。

Preloader负责处理两个主要的任务,首先是加载RSLs和资源绑定(就是刚才讨论的),另外的职责就是Flex应用程序在子类创建和子类显示阶段反馈给用户。随着我们进入更深的过程,将讨论Preloader完成任务后它什么时候会更新。

现在RSLs和包加载完,引起Preloader派发INIT_PROGRESS事件,SystemManager接受到这个事件后,通过nextFrame()方法前进一帧。在初始化阶段SystemManager为自己注册了ENTER_FRAME事件,该事件引发docFrameHandeler()方法。当我们调用nextFrame()方法时,SystemManager进入一帧并派发一个ENTER_FRAME事件。

docFrameHandler()方法开始定义许多单例的类在整个应用程序中,例如:BrowserManager,HistoryManager,CursorManager,LayoutManager等。一旦单例类被定义后,加载编译的资源,然后为应用程序设置ResourceManager的实例,使它们的内容在整个系统中可用。


Flex应用程序阶段:子类的创建

至此,我们已经获得外部资源的加载和系统准备,我们开始创建根类Application。所有这些信息对我们来说必须是可用的,因为它定义了框架和实现运行程序所需要的。现在,我们加载了所有信息,并在适当的时候开始创建*别的Application类。为开始创建*别的Application类,docFrameHandler()方法调用SystemManager的initializeTopLevelWindow()方法,它启动创建的过程。

首先,SystemManager为窗口注册MouseEvent.MOUSE_DOWN事件来处理焦点管理;注册stage的Event.RESIZE事件,当用户改变窗口的大小时,SystemManager为触发该事件,并将它传递给它的子类。这些注册信息完成后,Application实例已创建。

该类是为根Application所创建,根Appliation是在编译的过程中所定义。它可能是你扩展了Application类的实例或在AIR工程中扩展了WindowedApplication的实例。SystemManager利用工厂的方法调用create(),该方法info()["mainClassName"]参数决定什么样的类型去创建。

现在我们创建了Application的实例,SystemManager注册该实例的FlexEvent.CREATEION_COMPLETE事件,定义url和参数;然后更新Apllication和SystemManager的宽度和高度。

在Application更新完size后,SystemManager用Preloader注册Application实例。Application注册到Preloader,确保了Preloader注册不同生命周期生效阶段的事件("validatePropertiesComplete","validateSizeComplete",

CREATION_COMPLETE事件,等)。Preloader注册这些事件可以给用户提供Application的组件成长阶段的进度。


Flex 应用程序阶段:子类显示

因Application类继承了UIComponent,所以它必须遵循组件的生命周期。此时,Application已完成构造阶段,但并没有开始添加阶段,直到将它自己添加到父组件,如SystemManager。

如果SystemManager刚调用过addChild()方法并经过了三个子步骤(addingChild(),$addChild()和childAdded()),Flash Player将开始渲染Application的内容到屏幕上。尽管Application子类的创建过程已完成和运行了所有初始化的代码,也得花费许多时间和帧来得到最终的Application。

许多工作是否需要屏幕重绘,决定于Application它初始化和第一个失效-生效阶段的完成。阻止没有必须的重绘,SystemManager只调用处于在出生和生成阶段的组件的addingChild()和childAdded()方法。

在Appliction派发CREATION_COMPLETE事件后,SystemManager发送Application 通过多个失效的验证。Preloader还侦听了Application的CREATION_COMPLETE事件,然后派发PRELOADER_DOWN事件给SystemManager。

这时,SystemManager将要移除Preloader对象,因为它已完成了UI的任务。一旦SystemManager移除Preloader对象,将使用super.addChild()方法来添加Application对象;这时SytemManager和Application都会派发FlexEvent.APPLICATION_COMPLETE事件。

现在,应用程序启动,SystemManager自动降级到跟踪系统,并运行跟踪系统的鼠标事件,光标管理,对话管理和其他SystemManager职责。应用程序进入了更新阶段,这个阶段一直进行,直到用户关闭或退出系统。


Flex应用程序阶段:销毁

Flex应用程序的销毁阶段真正依赖于它是如何执行的。基于浏览器的程序,销毁阶段是很短暂的,并且没有任何通知;也就是说,应用程序在这一阶段你是没办法控制的。这也意味着,操作的确切顺序是很难弄清楚的。我们只是说当用户关闭了浏览器,它结束了SWF实例的进程。在这时,单纯地用ActionScript是没有什么可以控制的。

在基于AIR的应用程序中,使用WindowedApplication类可以有更多较好控制。AIR提供了关闭事件的多种处理者,你可以优雅地退出系统或阻止关闭系统。同样地,确切地操作顺序还是无从知晓,因为NativeWindow和NativeApplication都包含在playerglobal.swc里,我们不能访问它们的代码近一步地分析。

 

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组件和应用程序生命周期这块。正如前面提到的,这是我们从代码观点第一次审查和分析生命周期,结合我们的经验给出最佳的实践方法。如果你对这本书有问题或评论,请与我们联系,欢迎您的反馈信息。