posts - 431,  comments - 344,  trackbacks - 0
本文对如何开发高效的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服务器的总体架构
图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 一个通讯录的应用
图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






参考资料

posted on 2007-01-30 23:02 周锐 阅读(410) 评论(0)  编辑  收藏 所属分类: RIA

只有注册用户登录后才能发表评论。


网站导航: