本文对如何开发高效的OpenLaszlo应用展开了深入的讨论。结合官方的开发指南,笔者对如何获得满意的性能给出了自己的最佳实践。这些建议主要涵盖了延缓初始化时间、惰性复制数据、缓存数据等三方面内容。
作为当今主流的Rich Internet Application应用平台,OpenLaszlo为用户界面开发人员提供了强大的API来创建基于Flash的富客户端程序。虽然OpenLaszlo拥有简洁、快速的开发方式,但是我们仍然需要投入大量精力来关注数据驱动的OpenLaszlo应用的性能。本文对如何开发高效的OpenLaszlo应用展开了深入的讨论。结合官方的开发指南,笔者对如何获得满意的性能给出了自己的最佳实践。这些建议主要涵盖了延缓初始化时间、惰性复制数据、缓存数据等三方面内容,并以具体的试验数据来说明它们的有效性。
1. OpenLaszlo简介
在Web应用越来越关注用户体验的今天,传统的HTML应用已经不能满足开发人员和终端用户的需求。OpenLaszlo作为当今主流的RIA(Rich Internet Application)的应用平台,在进入开源社区之后显示出了更大的活力。Laszlo的技术基于XML和JavaScript来构建RIA程序,为Web开发人员提供了一种简洁快速的编程模式,并且给终端用户以更加动态的交互式体验。
OpenLaszlo的SDK(Standard Development Kit)由一个用Java编写的编译器、一个运行时的JavaScript库和一个可选的Java Servlet构成,如图1.1所示。开发OpenLaszlo的步骤非常简单:编辑、保存和刷新源文件即可。开发人员可以使用任何文本编辑器来编辑源文件,并且将其对应的URL键入浏览器。OpenLaszlo服务器自动地将文件编译成一个Flash文件,然后浏览器将其展示出来。
图1.1 Laszlo服务器的总体架构
OpenLaszlo应用由LZX的文件组成。LZX是一种标准驱动的XML和JavaScript描述性的语言,它使得上述声明式的(declarative)、基于文本的开发流程变为可能。列表1.2展示了一段简单的OpenLaszlo程序代码,它定义了一个按钮,用户在按下之后该按钮的位置会向右移动一段固定的距离。
列表1.2 一个简单的OpenLaszlo应用
<canvas height="30"> <button onclick="animate('x', 100, 1000, true)"> Move me </button> </canvas> |
在下一章,我们将根据OpenLaszlo官方的开发指南,构建一个数据驱动的应用程序,并对其进行深入的分析和调试。
|
|
2. 开发数据驱动的应用程序
2.1 一个典型的应用场景
为了更加清楚地说明我们的主题,我们根据OpenLaszlo的官方软件开发指南[1],扩展了其中通讯录的示例。这个应用提供了一个功能齐全的联系人列表,它列出了所有的联系人,并允许用户对联系人添加、删除和更新。唯一不同的是,我们的程序为用户提供了更多的选项,包含了更多的数据。图2.1展示了该程序的界面。
图2.1 一个通讯录的应用
由于联系人的功能和教程上的基本相同,我们使用了和OpenLaszlo指南中相同的方法来实现它。列表2.2描述了应用中一个类contactview的部分代码。
列表2.2 联系人视图(contactview)的源代码
<class name="contactview" extends="view" visible="false" x="20" height="180"> <simplelayout axis="y" spacing="5" /> <view layout="axis:x; spacing: 10"> <text y="10">First Name:</text> <edittext name="firstName" datapath="firstName/text()" y="10"/> <text y="10">Last Name:</text> <edittext name="lastname" datapath="lastName/text()" y="10"/> </view> <view> <text x="0" y="10">Gender:</text> <radiogroup layout="axis: x" x="80" y="12" datapath="gender" > <attribute name="genderv" type="string" value="$path{'text()'}" /> <method event="ondata"> this.selectItem(this.getAttribute('genderv')); </method> <radiobutton value="'M'" text="Male" /> <radiobutton value="'F'" text="Female"/> </radiogroup> <text x="195" y="10">Country:</text> <mycombobox x="275" y="10" width="105" datapath="country/text()"> <textlistitem value="${this.text}" datapath="timedata:/time/day/item/text()"/> </mycombobox> </view> </class> |
此外,虽然应用的数据存储在本地机器的XML文件中,但如列表2.3所示,我们对数据集的type和request属性做了设置,使应用程序在运行时才能够得到数据。在这种情况下,我们能够模拟OpenLaszlo的客户端从远程的服务器中获得数据的情景。
列表2.3 数据集的设置
<dataset name="countrydata" request="true" type="http" src="countries.xml"/> |
2.2 系统瓶颈
完成上述应用之后,性能问题迅速地显现出来。系统主要在以下三方面表现出了性能的瓶颈:
1. 程序初始化的时间很长。当列表达到30个联系人条目的规模时,程序就会花费很长时间来显示整个列表。在程序装载的过程中,这些条目逐条缓慢地显示出来。而在此期间,用户几乎不可能进行任何操作。
2. 下拉框(combobox)的初始化时间长度令人难以接受。当下拉框的选项非常多时,由于控件会等待列表初始化完毕后才变为可见,这将会耗费大量的时间和内存。
3. 删除功能的表现也不尽如人意,尤其在用户尝试删除位于顶端的条目。当一个条目被删除时,在其之下的条目将会被完全地刷新。这样的重画界面也引发了严重的性能问题。鉴于以上的性能问题,我们将逐步讨论如何解决这些问题。在第三章中,我们会阐述一些能够提高性能的普遍原则,并应用到本章所演示的程序里。进一步的,第四章将描述本文所涉及的实验方法,并且向读者展现比较实验的结果。
|
|
3. 优化OpenLaszlo程序的原则
3.1 延缓初始化时间
在默认情况下,OpenLaszlo会在加载页面时对所有的元素全部进行初始化。无论这些元素是可见或不可见的,它们都会被实例化。同时,OpenLaszlo提供了initstage属性来控制何时执行节点的init方法以及发送oninit事件的时机。initstage是LzNode的属性,换言之,它几乎可以被所有的元素继承并使用,它有五种可选的属性值[2]:
- immediate
除非该实例的所有子节点被创建,否则其他代码都不得运行。也就是说,初始化是实例化的最后一个阶段。 - early
在视图(view)和它的子节点被创建后,立即调用init方法。 - normal
系统缺省值。Init方法在初始化父节点的工作完成后被调用。 - late
在系统空闲时(idle)初始化节点。用户可以通过检查isinited属性来确认节点是否被初始化完毕。如果想在某一时刻强制节点初始化,可以调用completeInstantiation方法。 - defer
在该设置下,除非用户显式地调用completeInstantiation,否则节点将不会被初始化。
不难看出,使用late和defer的节点,在执行init方法以及发送oninit事件时,并不一定被初始化完毕。因此,它们非常适用于延缓OpenLaszlo节点的初始化时间。对一些在页面初次加载时不可见的节点,我们可以使用initstage = defer来抑制初始化,在触发其可见的事件上按需调用completeInstantiation。这样既可以减少页面初始化的时间,又可以减少很多不必要的初始化。因为对用户而言,很多不可见的元素是不需要被加载进系统的。
具体到我们的通讯录应用中,显示联系人细节的视图(contactview)完全符合上述的条件。于是,我们将其initstage的属性设为defer,并且重写了其父节点的onclick事件,使得只有在用户点击了条目后,细节视图才会占用系统的资源,按需地被初始化。列表3.1描述了具体的代码。
列表3.1延缓初始化时间的代码
<view id="newEntryView"> <text text="New Entry..." > <method event="onclick"> parent.newContactView.setVisible(!parent.newContactView.visible); //在点击时完成初始化 parent.newContactView.completeInstantiation(); </method> </text> <contactview name="newContactView" datapath="newcontact:/contact" initstage="defer"> <!--在页面初次加载时不初始化contactview--> <button width="80" x="300" text="Add" /> </contactview> |
3.2 惰性复制数据
用户界面中,经常存在实际条目比展示给用户的条目多的列表。OpenLaszlo为这种情况在baselist、basecombobox等节点中定义了dataoption属性。当dataoption取lazy值时,列表的条目(listitem)将会使用惰性复制,即只复制需要显示的条目。通讯录中表示国家的下拉框有近200个选项,就属于这种情况。在列表3.2中,我们自定义了一个下拉框类,并附上了它的使用示例。这样,即使有200个数据项,系统也只复制了5个textlistitem的视图。
列表3.2 使用惰性复制的下拉框
<class name="mycombobox" extends="combobox" editable="false" shownitems="5" dataoption="lazy" /> <mycombobox id="country" x="275" width="105" datapath="country/text()"> <textlistitem datapath="countrydata:/countries/country/text()" value="${this.text}" /> </mycombobox> |
值得一提的是,我们在<textlistitem>中直接定义了datapath属性,因此它继承了父节点dataopiton = lazy的特征。如果在<textlistitem>下单独定义datapath元素,那么我们还必须将其replication属性设为lazy,如表3.3所示。
列表3.3 单独声明datapath的textlistitem
<mycombobox > <textlistitem value="${this.text}" > <datapath xpath="countrydata:/countries/country/text()" replication = "lazy" /> </textlistitem> </mycombobox> |
事实上,OpenLaszlo使用惰性复制的列表使用LzLazyReplicationManager而不是缺省的LzReplicationManager来控制视图。后者在视图的datapath和多个datanode匹配时,为每一个匹配都创建一个视图;而前者出于显示数据的考虑,只创建足够数量的视图。本文的例子中,通讯录的条目过多时(如超过100条),也可以考虑使用lazy的属性使初始化时间变快。
3.3 缓存数据
假使OpenLaszlo应用使用了数据复制,而且这些数据在运行时改变的话,默认的LzReplicationManager会将数据改变所对应的视图销毁,然后重新创建它们。显而易见,如果数据集非常大,这样的调整策略会导致用户界面严重的反应迟缓。如第二章所述,我们的通讯录应用在删除位于列表顶端的记录时,就会出现明显的延迟。
为了解决上述问题,我们可以将匹配多个数据节点的datapath中的pooling属性设为true。设置之后,LzLazyReplicationManager只是将改变了的数据重新指向已经被创建的视图。这样用户界面所反映出的数据更新速度会明显加快,具体的应用见列表3.4。
列表3.4 使用pooling属性
<view> <datapath xpath="dset:/phonebook/contact" pooling="true" /> <simplelayout axis="y" /> <view name="list" > <!-- more... --> </view> </view> |
|
4. 性能比较和分析
4.1 测试方法和模型
最后,我们将对本文提及的实验进行性能的监测和调试。根据上文的原则,我们采用OpenLaszlo 3.3.3,对性能优化前后的结果进行比较。表4.1展示了实验的具体配置。
表4.1 实验环境的配置
CPU | 内存大小 | Total paging space | 浏览器类型 |
1698MHz | 512MB | 1.22GB | Mozilla Firefox 1.5.0.4 |
测量OpenLaszlo代码的性能分为两种:加载时间和响应时间。前者说明了OpenLaszlo的应用在多久之内被初始化,而后者则是衡量程序能够响应用户动作的敏捷程度,比如在通讯录应用中完成"删除"功能的时间长度。对于这两种时间,我们都在程序开始和结尾记录时间,然后将两者相减,得到最后的结果。列表4.2展示了这种方法的实现代码。
列表4.2 测量响应时间
<button width="80" text="Delete"> <method event="onclick"> var pre = (new Date).getTime(); //记录开始执行时间 parent.parent.parent.datapath.deleteNode(); var cur = (new Date).getTime(); //记录结束时间 debug.write(cur - pre); //输出 </method> </button> |
4.2 测试结果
我们分别对通讯录中有3条、9条和30条记录的情况做了比较。我们逐项应用上述的原则:所有表示时间的实验数据单位为毫秒(ms)。最后的结果由10次操作的平均时间得到。首先,我们将 "initstage = defer"前后的初始化时间的比较列在表4.3。可以看到,在联系人的细节视图不被加载的时候,初始化的时间有了明显的加快。
表4.3使用defer的性能比较
记录数目 | initstage = defer(ms) | initstage = normal(ms) | 降低幅度(%) |
3 | 268.4 | 3277.8 | 91.82 |
9 | 433.6 | 10816.8 | 95.99 |
30 | 955.2 | 54734.7 | 98.25 |
其次,表4.4展示了在使用"pooling"前后的响应时间。
表4.4 使用pooling的响应时间比较
记录数目 | pooling = true(ms) | pooling = false(ms) | 降低幅度(%) |
3 | 28.00 | 136.14 | 79.43 |
9 | 77.00 | 728.00 | 89.42 |
30 | 467.67 | 3079.25 | 84.81 |
最后,本文描述"lazy"选项的效果,如表4.5所示。不难看出,"lazy"选项与上述的选项不同,没有随着记录的增多而对性能有显著的改善。显然,这是因为"lazy"针对的是单个更新视图的初始化时间,和总体记录的条数没有明显的关系。
表4.5 使用lazy的性能比较
记录数目 | dataoption = true(ms) | dataoption = none(ms) | 降低幅度(%) |
3 | 838 | 7213.6 | 88.38 |
9 | 852 | 7344.5 | 88.40 |
30 | 961.4 | 8587.1 | 88.80 |
|
|
参考资料
- Software Engineer's Guide to Developing Laszlo Applications, Chapter 33: Building Data-Driven Applications.
- Software Engineer's Guide to Developing Laszlo Applications, Appendix A. Understanding Instantiation.
- developerWorks 中国网站 XML 专区:http://www.ibm.com/developerworks/cn/xml