本文来自:http://www.insideria.com/2009/09/debugging-the-flex-invalidatio.html
当我还是一个Flex初学者时,使用组件生命周期让我很困扰。不过我是幸运的,我被Flex官方杂志邀请发表一篇关于新手视角的文章。我从文章的反馈中收获颇丰。但是因为最近几个月我没有经常做与Flex有关的工作,因此我就遇到一个有关面板组件的问题,它让我注意到生命周期的失效部分的几点问题。有关生命周期的文章经常关注于如何使一些东西在一个完美的世界里运行,在这个世界里,开发者初步设定一切是完美的。但是我认为和大家分享一个过程的经验还是有用的,在这个过程里,我不得不进行分析,为那些一开始看起来是个让人困扰的BUG找到解决方案。
在开始之前,我想分享一下我个人编写自定义组件的经验。我将关注于如何利用失效周期,因为当你在为组件设置属性时(或者样式,为了简明起见,我将只关注属性),它可能在任何时间突然冒出来。组件生命周期的其他部分只发生一次,因此它们相对容易理解。
最初,我几乎经常为我的属性使用getter和setter函数,而不是变量。这促使我设置标记以确认属性是否发生改变以及失效周期是否已经出现。以下就是当我根据循环周期中何种属性改变以及如何改变,研究失效周期时采用的相关规则。
a. 如果属性需要改变子组件的一个属性时,我调用invalidateProperties(), 同时覆盖commitProperties();
b. 如果属性需要改变当前组件上的一些可见的内容时,我调用invalidateDisplayList()方法,同时覆盖updateDisplayList();
c. 如果属性影响了组件的大小,我会调用invalidateSize()方法,并覆盖measure()。
实践中,这通常会在属性改变时调用invalidatePropertiest()方法,同时这个逻辑又会决定是否需要运行updateDisplayList(),转而这又会决定是否需要运行measure()方法。
让我们看下我的自定义面板的起始逻辑点:
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script>
<![CDATA[
private var _panelTitle:String;
private var _panelTitleChanged:Boolean;
public function get panelTitle():String {
return _panelTitle;
}
public function set panelTitle(value:String):void {
if (value != _panelTitle) {
_panelTitle = value;
_panelTitleChanged = true;
invalidateProperties();
}
}
override protected function commitProperties():void {
super.commitProperties();
if (_panelTitleChanged) {
title = 'Panel' + _panelTitle;
_panelTitleChanged = false;
}
}
]]>
</mx:Script>
</mx:Panel>
如果你在这个应用程序里使用它,
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:view="com.magnoliamultimedia.view.*"
layout="vertical" width="100%" height="100%">
<view:TitlePlusPanel panelTitle="foo" width="300" height="200" id="myPanel" />
<mx:Button label="Change title" click="{myPanel.panelTitle='bar'}" />
</mx:Application>
你就会获得如下面板:
当你点击按钮时,它就会是这个样子:
显然,面板视觉呈现的改变往往滞后于我们的期望。所以让我们打开调试器,看看是不是能够查清为什么会出现这种情况。我会在为面板组件设置标题的那一行设置一个断点,然后跳过它。
看起来我所有的自定义变量都获得了正确的值。让我们点击'step over'按钮然后查看是否组件的标题属性被设定了正确的值.
你可以看到标题变量包含“Panel foo”,但是我希望你们能注意到Panel面板类的“titleTextField”的受保护属性(黄色图标表明它是受保护的)。如果我们将其展开就会发现它的text文本属性仍然是一个空的字符串。
titleTextField是面板组件的一个属性,但它同时还是Panel的一个子组件,一个UITextField。回顾我自己如何编写组件,我发现我在commitProperties中设置子组件属性。在编写逻辑的初稿时,我开始的前提就是我的逻辑应当在commitProperties()逻辑的后面。但是一旦我更深入了解组件是如何编写的话,我就可以洞察面板组件正是以我喜欢的组件编写方式编写的-子组件的属性是在commitProperties()中设置的。主要的问题是,我是在本应该改变组件属性的逻辑运行之后,提供所需信息改变组件属性的。
这是一个相对来说比较琐碎的范例,但我认为查看问题的潜在解决方法可以指导我们遇到不那么烦琐的问题时,寻求解决方案。
a. 现在不再需要使用setter方法以及commitProperties()方法手动设定标题了,而仅仅需要一个可绑定的公共变量, 将面板标题和'Panel'+panelTitle绑定。这可以将问题简化---这样你就可以忽略覆盖commitPropertiest()---但是当然,是用绑定也是需要做额外工作的。另外,有时候绑定某些事物是不可能的(或者至少是不容易的),例如一个对象的属性并没有被定义成bindable的情况。
b. 直接在setter函数中设置标题属性,同样不需要覆盖commitPropertiest()。因为标题属性需要的是成对的getter/setter函数,利用自身的失效周期,这看起来是个不错的选择。然而,这种方法有几个弊端:
l 如果你的组件在条目渲染器中被使用的话,当你试图通过调用setter函数设置子组件的属性时,这个组件可能并不存在。事实是,在这种情形下,使用面板组件失效周期会把你和问题隔离开来,但这并不是一个好的习惯。
l 有时候你需要查看一些自定义属性以便确定子组件的那个属性应当如何被设置。我们没有办法知道属性将会以什么顺序被设置。如果你拖延操作直到commitProperties(),可以肯定的说任何将要被设置的属性都已经被设置好了。
l 移动设置标题属性的逻辑以便于在super.commitPropertiest()之前开始运行。相对于其他方法,这种方法的优势就在于在更多的情况下都是可行的,但它不易被想到,尤其对于团队中的开发经验不是很丰富的成员来说更是如此。
你的选择呢?