1.FLEX与Flash Flex 程序的根 mx.managers.SystemManager 就是 Flash 类 flash.display.MovieClip 的扩展。所以,Flex 也可以说成是基于时间轴的,只不过他在时间轴上的帧只有两帧:第一帧是预加载,由 Flex 框架控制,也就是我们在 Application 运行之初看到的进度条,被称之为 Preloader( 如图 1-1 所示 );第二帧,就是我们真正的应用程序。
图 1-1. Flex 程序第一帧—— Preloader
了解这点对于我们理解 flex 组件的生命周期至关重要,因为帧的执行模式有一个重要的特点,那就是
在一帧中会先执行脚本后渲染屏幕,即通常称为的 Elastic Racetrack 模型(如图 1-2 所示)。 这个模型预示着过长的代码执行会影响屏幕渲染的用户体验。Flex 组件构建在帧模型基础上的,需要同样遵行帧的这种执行模式。
图 1-2. Elastic Racetrack 帧加载模型
FLEX核心的类体系结构:
图 1-3. Flex 的核心类体系结构
最顶层的类是 DisplayObject,Flex 将它添加到 Flash Player 的 Stage 对象或通用显示列表。
InteractiveObject 处理用户交互,包括键盘和鼠标事件。
DisplayObjectContainer 允许您添加子组件,并在特定范围内移动它们。
Sprite 和 FlexSprite 是不需要时间线的基类,您可以通过扩展它们编写定制类。不过,这个类集合中的主角是 UIComponent 类,它为您提供编写定制组件所需的基础框架。您通常通过扩展这个类来编写定制代码。
Flex 将您创建的所有组件作为最终由 Flash Player 处理的通用显示列表的子组件。Flex
的体系结构隐藏了这些细节,但它提供连接到事件监听器的钩子,让您能够在高级阶段处理事件。Flash Player
的事件分发机制非常健壮,能够处理上万个事件。
2.FLEX组件的生命周期概述 下面从 Flex 组件的基类 UIComponent 出发创建一个简单的图片查看器 ImageViewer(如下图 2-1 所示), 然后以此为例分别讲解各个时期里,Flex 框架对该组建都做了什么。
图 2-1. 一个简单的 Flex 组件 ImageViewer
Flex 组件的生命周期可以总结为以下几个阶段
2.1 调用组件的构造函数--初始阶段 当使用 new 操作符(如代码2-2所示)或者在 mxml 里声明一个组件的时候,构造函数就被调用了。通过调用构造函数,一个组件类的实例被创建在了内存里。但是仅此而已。因为他是组件生命周期的最早部分,此时
组件中的子元素还没有被创建,属性值也不明,所以,通常我们在这一阶段里所做的事情很少;但是他也是一个为组件添加时间监听器的好地方,因为一个组件的构造函数会且只会被调用一次。
代码2-2 构造函数:
- private var myImageView : ImageViewer = new ImageViewer();
2.2 设置组件的属性--配置阶段 组件的构造函数与set函数之间的执行顺序如代码2-4 所示,假设我们把 ImageViewer组件放到一个 Panel 容器里
(),此时代码的执行顺序如下:
代码2-4 配置阶段的执行顺序
- <mx:Panel width="100">
-
<sample:ImageViewer imageHeight="150"/>
-
</mx:Panel>
输出顺序:
- Panel : constructor
-
Panel.with : setter
-
ImageViewer Calendar : constructor
-
ImageViewer.imageHeight :setter
注意:在属性设置阶段,由于
组件的子元素仍然没有被创建,所以如果组件的某个属性的 set
方法里涉及到了对子元素或者其属性的操作的话,将提示对象为null。
为了解决以上问题,Adobe 建议开发人员在本阶段只缓存特性值,而把业务逻辑推迟到到以后的验证阶段(参见代码2-5)。这就是之前提到的验证 -
提交模式(invitation-validation pattern),关于这一概念我们会在下面的章节做详细说明。
我们可以通过使用组件的 set 方法是缓存特性值。将真正的业务逻辑推迟到提交阶段。使用set,get设置组件属性,常在set方法内监控一个布尔变量来实现失效机制。
代码2-5:例子 ImageViewer 的 set imageHeight() 方法
- public function set imageHeight(value:Number): void {
-
if (_imageHeight != value) {
-
_imageHeight = value; //缓存属性值,等到commitProperties()时再给组件属性赋值
-
imageHeightChanged = true ; //表示标志位,imageHeight属性存在更新
- this.invalidateProperties(); //表示要调用更新属性函数commitProperties()
-
this .invalidateDisplayList(); //表示要调用更新视图函数updateDisplayList()
-
dispatchEvent( new Event( "imageHeightChanged" ));
-
}
-
}
2.3 调用addChild()方法将该组件添加到父组件--装配阶段 组件只有通过 addChild 或者 addChildAt 方法被添加到父组件上,才会依次进入到以下的生命周期时期,否则得话,以后的步骤和方法都不会被调用。为此我们可以做这样一个实验。我们在组件代码里添加
trace(代码2-6),然后再分别执行应用程序 Test1.mxml(代码2-7)和 Test2.mxml(代码2-8),再来 对两个输出。
代码2-6 验证装配阶段 ImageViewer 组件的执行顺序
- package example
-
{
-
import mx.core.UIComponent;
-
-
public class ImageViewer extends UIComponent {
-
public function ImageViewer() {
-
trace( "constructor" );
-
super();
-
}
-
override protected function createChildren(): void {
trace
( "createChildren" ); -
super.createChildren()
-
}
- /**负责处理属性的设置*/
- override protected function commitProperties(): void {
-
trace( "commitProperties" );
-
super.commitProperties()
-
}
-
override protected function measure(): void {
-
trace( "measure" );
-
super.createChildren()
-
}
-
override protected function updateDisplayList(w:Number, h:Number): void {
trace
( "updateDisplayList" ); -
super.updateDisplayList(w,h)
-
}
-
}
-
}
代码2-7 应用程序 Test1.mxml
- <?xml version="1.0" encoding="utf-8"?>
-
<s:Application xmlns:fx=""
-
xmlns:s="library://ns.adobe.com/flex/spark"
-
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
-
<fx:Script>
-
<![CDATA[
-
import example.ImageViewer;
-
private var myImageView : ImageViewer = new ImageViewer();
-
]]>
-
</fx:Script>
-
</s:Application>
输出:
代码2-8应用程序 Test2.mxml
- <?xml version="1.0" encoding="utf-8"?>
-
<s:Application xmlns:fx=""
-
xmlns:s="library://ns.adobe.com/flex/spark"
-
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
-
-
<fx:Script>
-
<![CDATA[
-
import example.ImageViewer;
-
private var myImageView : ImageViewer = new ImageViewer();
-
-
override protected function createChildren():void{
-
super.createChildren();
-
this.addElement(myImageView);
-
}
-
]]>
-
</fx:Script>
-
</s:Application>
输出:
- constructor
-
createChildren
-
commitProperties
-
measure
-
updateDisplayList
从以上二个例子可以看出,如果不将组件添加到父组件上,只能执行构造函数,后面的阶段都不会执行。因为只有将组件添加到父组件上,FLEX将自动调用createChildren(),invalidateProperties(),invalidateSize(),invalidateDisplayList()。只有将组件添加到父容器中,FLEX才能确定它的大小(size),设置它所继承样式(style)属性,或者在屏幕上画出它。
注意: 覆盖 createChildren 方法来添加子节点,此方法会且只会被执行一次。 尽量把创建动态形式和数据驱动(data-driven)形式的子元素推迟到 CommitProperties() 方法里添加或者执行。
2.4 组件的parent 属性设置为对父容器的引用
2.5 样式(style)设置
2.6 派发事件Preinitialize 派发 Preinitialize 事件。这个阶段意味着组件已经被添加到了显示列表(DisplayList)上。
2.7 调用createChildren()
调用 createChildren() 来生成子元素。在这个阶段里,您可以覆盖这个方法添加需要的子元素。
2.8 调用invalidateProperties(),invalidateSize(),和invalidateDisplayList()方法 调用invalidateProperties(),触发延迟到render期的调用commitProperties();
调用invalidateSize(),触发延迟到render期的调用measure(),当用户设置组件的height和width时,Flex不调用measure();
调用invalidateDisplayList(),触发延迟到render期的调用updateDisplayList();
2.9 在组件上派发事件initialize事件 这时,当前组件的所有子对象被初始化,但当前组件还未为布局而被尺寸化或处理,你能在它被布局前使用这个事件来执行这个组件附加的处理。
2.10 在父容器上派发childAdd事件2.11 在父容器上派发initialize事件2.12 Render渲染阶段 顺序调用方法:commitProperties(),measure()、layoutChrome()
和 updateDisplayList(),在当前组件上分发updateComplete事件。
commitProperties()方法主要负责属性值的计算和生效,因此首先执行,因为这些自己算过的值可能用于布局,也可能用于显示。这里也可以用来移除不需要的子节点,但是您需要通过标志位来判断子节点是否存在。
measure()方法用于根据组件的内容大小计算组件尺寸。由于尺寸的计算是自下而上的,所组件子元素的大小改变都会隐试的触发此方法。
UpdatedisplayList() 方法用来更新显示列表(Display
List)的布局以及渲染。组件的布局是一个自上而下的过程,所以在这个方法里您需要对不仅仅是组件设置布局属性,而且也要对子元素进行相应的调整。这里
也是使用 Flash 绘图 API 的好地方。
这里,我们所要做的就是覆盖(override)这几个方法。
总结以上个步骤,我们用图 4 来标注组件出生时期的流程图。
图 . 组件出生时前交互流程
3 验证 - 提交阶段(invitation-validation)
Flex
的生命周期是建立的帧模型基础之上的,所以同样遵循着先执行脚本后渲染屏幕的规则。为了达到流畅的视觉体验,我们通常期望能够尽量减少代码执行的消耗,而
给画面渲染留下充足的时间,为了实现这点 Flex 使用了 Invalidation – Validation 模式,来实现资源的有效利用。
图 . Invalidation-Validation 模式
Invalidation – Validation 模式即验证提交 - 模式提供了一个低耦合的处理过程,将数据的改变(旧数据的实效)和数据的处理(对新数据的重新使用)区分开来,这样做的好处是:
-
只在有屏幕刷新需求的时候的时候进行相应数据操作和计算;
-
并且避免了不必要的重复渲染。
以代码3-1为例,通过 Invalidation – Validation 模式,第一行的代码的结果永远不会生效,即不会也从来没有显示到屏幕上。
代码3-1 相同属性被多次赋值的例子
- myImageView.height = 50;
-
myImageView.height = 500;
让我们来看一下 Flex 是如何将这一模式应用到组件上的。
首先我们来看一下 Flex 可视化组件基类 UIComponent 是如和处理 set width() 方法的。
代码3-2 UIComponent 的 set width ()方法
- public function set width(value:Number):void {
-
if(explicitWidth != value) {
-
explicitWidth = value;
-
invalidateSize();
-
}
-
// 其他代码省略
- }
从上面我们可以看出,UIComponent 在首先会去判断属性赋值是否改变,从而避免重复赋值带来的消耗,然后调用 protected 方法 invalidateSize()。
invalidateSize()是 UIComponent 提供的验证方法之一,它用来校验与组件尺寸大小相关的属性,当此方法被调用后,Flex
组件引擎 LayoutManager 会首先检查该属性的更新请求是否已经调用过
invalidateSize()方法,如果调用过,说明此类型的更改请求已经记录在案,无需赘述。如果没有,则会将此次请求记录到
LayoutManager 校验队列上进行等待。那么对 width 属性值的更新什么时候生效呢? LayoutManager 会在监听到下一次
RENDER 事件派发的时候,将组件推送到提交阶段。
在提交阶段,LayoutManager 得到更新请求队列,调用 commitProperties()方法使得属性 width
的最新值(假设记录在了 _width 私有变量上)得以生效。Flex
就是通过这种属性值延迟生效的方法来保证每次用于渲染画面的请求都是最新的,从而避免了不必要的资源浪费。
因此,在我们的例子 ImageViewer 里,我们也使用了类似机制。(如代码3-3所示),只不过这里我们调用的是
invalidateProterties()方法。与 invalidateSize()类似,组件的 scale 属性值会在提交阶段通过调用
代码3-3 ImageViewer 的 set scale 方法
- public function set scale(value : Number): void {
-
if (_scale != value) {
-
_scale = value;
-
scaleChanged = true ;
-
invalidateProperties();
-
dispatchEvent( new Event( "scaleChanged" ));
-
}
-
}
4 交互阶段(Interaction) 交互严格地说组件生命周期中的某种行为,他是促使组件更新,推动验证 - 提交循环的动力。
Flex 使用事件驱动的交互模式,来实现一种完全松耦合的体系结构。简单地说,任何组件如果想要告诉外界当前发生了什么事或者即将发什么事的话,他可以派发一个事件,那么在该类事件可及范围内的任何组件都可以通过注册该事件的监听器的方式来对此类事件进行相应。
一般来说,组件的交互行为有以下几种:
.用户同组件的交互,比如说:输入数据,缩放大小等等。
.通过派发和响应事件。
.应用(Application)或者父节点(parent)与组件的交互。比如说 ItemRenderer 实例和宿主对象之间的 data 传递。
5.销毁时期 任何事物都会有一个归宿,Flex 组件也不例外。当某个组件不再需要的时候,我们需要把他销毁。
5.1 拆卸阶段(removeChild) 销毁组件的一种方法是通过调用组件父亲节点(parent)的 removeChild()方法,该方法会将他从显示列表(Display List)上拆卸掉,并且检查是否存在对此组件的引用,如果没有的话,组件会被标记为“可以回收”,这预示着组件进入到了回收阶段,并且可以被 AVM 垃圾回收。
5.2 回收阶段(GC) 刚才我么提到了通过 removeChild()方法将组建拆卸掉以后,组件可以被垃圾回收。这意味着该组件的实例会被从内存中完全删除掉,并且释放资源。但是请注意,垃圾回收并不一定在此刻马上发生,AVM 有着自己的垃圾回收时间。因此这个打了标签的组件需要等待回收时刻的到来。
拆卸阶段并不是组件销毁的必经阶段。当某个组件被拆卸掉之后,如果该组件包含了子组件,而他们都不存在外界引用的话,所有的元素都会被标记为“可以回收”,也就是说该系统中的子组件并不需要进入到拆卸阶段,也可以在回收时刻到来的时候被 AVM 回收掉。那么开发人员所需要注意的就是,在这个时刻发生之前,将引用去除掉。
参考文献1.http://www.ibm.com/developerworks/cn/web/1011_simq_flexlifecycle/index.html?ca=drs-
2.http://www.ibm.com/developerworks/cn/web/wa-flexrendering/?ca=drs-tp4608
3. FLEX组件的生命周期. http://blog.csdn.net/stonywang/article/details/2667551