30 November 2011
本文要求基本了解 Adobe Flex编程知识。 本文适合那些希望编写自己第一款Flex移动应用程序并且正在寻求基本性能指南的初级和高级Flex用户。
中级
本文将讨论你能够制定的用来保持你的Flex移动应用程序运行流畅的性能决策。 你能够采取用来确保获得卓越性能的唯一最重要的步骤是使用许多与Flex 4.6一起发行的高度优化的移动组件、皮肤以及条目渲染器(item renderer)并且以它们为基础建立其它组件。 使用这些组件并遵循本文中概述的一套最佳方法,你会在使用单一Flex编码基的 iOS、Android以及QNX设备上体验到高帧速率和快速加载时间带来的乐趣。
一个移动应用程序可能会包括数十个条目渲染器,并且当你的应用程序的确包括这么多条目渲染器时,对它们进行优化就变得很重要。 优化条目渲染器将会使你的应用程序在滚动过程中保持高的帧速率,这将给你的用户带来流畅、响应快速的体验。 Flex团队推荐你在ActionScript中编写条目渲染器以便于获得最佳的性能。 ActionScript条目渲染器与MXML条目渲染器相比编写的难度较大,然而相应的性能增益非常显著。 尽管MXML条目渲染器能够提供许多便利,其中包括状态语法(state syntax)、动态布局(dynamic layout)以及绑定表达式(binding expressions),然而ActionScript条目渲染器为了换取速度牺牲了这些便利。
你可以通过利用ActionScript编写你的条目渲染器并建立高度优化的Flex LabelItemRenderer或者IconItemRenderer来改进相应的性能。 为了实现你的LabelItemRenderer或者IconItemRenderer子类,你可以覆盖条目渲染器的数据setter函数和createChildren、measure、drawBackground以及layoutContents方法。
为了开始创建一个能够扩展LabelItemRenderer以及覆盖这些方法的ActionScript模版,请遵循下面的这些步骤:
相应的生成代码将包括一些注解,它们用来解释在每一个被覆盖的方法中需要做些什么。 如需获得每一个被覆盖的方法的更多详细的代码范例,请务必钻研各种相应的超级类(superclass)的方法。
如果你熟悉Flex组件生命周期,那么你可能注意到一个新的惯例:updateDisplayList已被分割成两个新的方法:drawBackground和layoutContents。 在LabelItemRenderer和IconItemRenderer中,updateDisplayList先调用drawBackground,其次再调用layoutContents。
图1显示了一个简单的显示股票行情符号以及它们值的变化的ActionScript条目渲染器的范例。
StockRenderer类能够扩展LabelItemRenderer,它使用一个自定义背景,并创建一个额外的StyleableTextField以便用于显示股票值的变化。 下面是相应的代码:
public class StockRenderer extends LabelItemRenderer
{
private const PADDING:Number = 20;
private const GAP:Number = 40;
private var change:Number;
private var changeLabel:StyleableTextField;
public function StockRenderer()
{
super();
}
override public function set data(value:Object):void
{
super.data = value;
change = (data ? data.change : 0.0);
changeLabel.text = (change > 0 ? "+" : "") + change.toString();
invalidateDisplayList();
}
override protected function createChildren():void
{
super.createChildren();
if (!changeLabel)
{
// Create a StyleTextField to display the stock's change value, and add it to the display list.
changeLabel = StyleableTextField(createInFontContext(StyleableTextField));
changeLabel.styleName = this;
changeLabel.editable = false;
changeLabel.selectable = false;
changeLabel.multiline = false;
changeLabel.wordWrap = false;
addChild(changeLabel);
}
}
override protected function measure():void
{
// Determine the item renderer's desired width and height.
measuredWidth = PADDING + labelDisplay.getPreferredBoundsWidth() + GAP + changeLabel.getPreferredBoundsWidth() + PADDING;
measuredHeight = PADDING + Math.max(labelDisplay.getPreferredBoundsHeight(), changeLabel.getPreferredBoundsHeight()) + PADDING;
measuredMinWidth = measuredWidth;
measuredMinHeight = measuredHeight;
}
override protected function drawBackground(unscaledWidth:Number,
unscaledHeight:Number):void
{
// Choose green or red for the background color based on the stock's change value.
var backgroundColors:Array = (change >= 0 ? [0x00CC00, 0x009900] : [0xCC0000, 0x990000]);
// Create a matrix to rotate the background gradient 90 degrees.
var matrix:Matrix = new Matrix();
matrix.createGradientBox(unscaledWidth, unscaledHeight, Math.PI / 2, 0, 0);
// Draw the gradient background.
graphics.beginGradientFill(GradientType.LINEAR, backgroundColors, [1.0, 1.0], [0, 255], matrix);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.endFill();
}
override protected function layoutContents(unscaledWidth:Number,
unscaledHeight:Number):void
{
// Position labelDisplay against the left edge, and vertically align it.
var labelDisplayX:Number = PADDING;
var labelDisplayY:Number = (unscaledHeight - labelDisplay.getPreferredBoundsHeight()) / 2;
setElementPosition(labelDisplay, labelDisplayX, labelDisplayY);
// Position changeLabel against the right edge, and vertically align it.
var changeLabelX:Number = unscaledWidth - changeLabel.getPreferredBoundsWidth() - PADDING;
var changeLabelY:Number = (unscaledHeight - changeLabel.getPreferredBoundsHeight()) / 2;
setElementPosition(changeLabel, changeLabelX, changeLabelY);
}
}
在内部,IconItemRenderer使用Flex ContentCache类来高速缓存外部下载的图标图像。 这意味着当用户向下滚动一个包含IconItemRenderers的列表时,从服务器加载外部的图像将会耗费一点时间,然而当用户反向向上滚动时,图像会立即出现,因为它直接来自IconItemRenderer缓存器。
StyleableTextField在渲染文本方面与Label或者RichText相比速度更快,因此在ActionScript条目渲染器中一直都是使用它来显示文本。 注意,StyleableTextField只能在ActionScript中使用,因此你不能在MXML条目渲染器或者MXML视图中使用它。
在ActionScript中编写条目渲染器将会获得最佳的性能。 但是,你仍可以使用MXML条目渲染器来获得相似的性能水平,然而你需要小心地使用下面的优化措施来调整它们。
cacheAsBitmap属性能够对性能产生积极或消极影响,这取决于它的使用方式。 如果一个条目渲染器的内部外观不经常改变,那么仅仅将cacheAsBitmap设置为true即可。 当条目渲染器的外观保持不变并且只有它的位置发生变化时,运行时能够快速地在它的新位置中重新绘制条目渲染器的缓存位图。 但是,条目渲染器的内部外观每发生一次改变,运行时都必须重新创建一张位图,这将会花费一些时间并对性能造成损害。
相同的情形也会发生在所有的组件上,而不仅仅只发生在条目渲染器上。 例如,千万不要在List上设置cacheAsBitmap。 在滚动时每一帧的List组件的内部外观均会改变,这将要求每一帧的运行时均重新产生许多多余的位图,从而显著降低帧速率。
如需了解更多关于cacheAsBitmap的信息,请查看Glenn Ruehle在他的47分钟的演说建立Flex Tablet应用程序的最佳方法(Best Practices for Building Flex Tablet Applications)中对正确使用cacheAsBitmap的解释。
不像cacheAsBitmap那样,如果不当使用将容易损害性能,而opaqueBackground却只会改进性能。 但是,你的条目渲染器必须是矩形形状并且不透明以便于正确地进行渲染。
在系统内部,opaqueBackground属性能够指示运行时使用一个高度优化并且能够忽略透明度计算(transparency calculation)的渲染路径。
为了使用opaqueBackground属性,你需要在一个条目渲染器上将它设置为一个特定的颜色。 即使你为opaqueBackground设置一个单一颜色,你仍然能够使用另外一种自定义不透明的背景完全地覆盖住该颜色并且获得性能上的优势。
下面是一个简单条目渲染器,它能够设置opaqueBackground以便获得性能增益,并且在一个梯度填充(gradient-filled )的Rect的纯色背景中完美地进行绘画操作:
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
cacheAsBitmap="true"
opaqueBackground="0xFF0000"
autoDrawBackground="false">
<s:Rect left="0" right="0" top="0" bottom="0">
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="#FFFFFF" ratio="0"/>
<s:GradientEntry color="#DDDDDD" ratio=".33"/>
<s:GradientEntry color="#999999" ratio=".66"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:Label id="labelDisplay" left="5" right="5" top="15" bottom="15"/>
</s:ItemRenderer>
如果你按照之前代码范例中使用的方式在你的条目渲染器中定义一个背景Rect,那么你应该关闭autoDrawBackground。 这一操作将会指示Flex不要浪费时间来绘制一个你无论如何都会覆盖的默认背景。
虽然StyleableTextField是显示文本最快速的组件,但是你仍然不能在MXML中使用它。 下一个你可以在MXML中使用的最快速组件是Label。 最为重要的是,你应该一直避免在你的应用程序中使用RichText,因为它不是用来优化移动设备的。
在系统内部,绑定表达式(binding expression)能够调度事件并且运行额外的ActionScript代码。 复合绑定表达式(complex binding expression)和双向绑定表达式(two way binding expression)甚至更加冗长。 幸运的是,你可以方便地通过在一个已被覆盖的数据调 setter函数中更新条目渲染器的显示,以避免在条目渲染器中使用绑定表达式(binding expression)。
例如,你可以按照下面的方式覆盖条目渲染器的数据setter函数:
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
cacheAsBitmap="true"
opaqueBackground="0xFF0000"
autoDrawBackground="false">
<fx:Script>
<![CDATA[
override public function set data(value:Object):void
{
super.data = value;
if (data)
{
nameLabel.text = data.name;
phoneNumberLabel.text = data.phoneNumber;
}
else
{
nameLabel.text = "";
phoneNumberLabel.text = "";
}
}
]]>
</fx:Script>
<s:Rect id="background" left="0" right="0" top="0" bottom="0" >
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="#FFFFFF" ratio="0"/>
<s:GradientEntry color="#DDDDDD" ratio=".33"/>
<s:GradientEntry color="#999999" ratio=".66"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:Label id="nameLabel" left="5" right="5" top="15" bottom="15" fontSize="12"/>
<s:Label id="phoneNumberLabel" left="5" right="5" top="30" bottom="15" fontSize="10"/>
</s:ItemRenderer>
The previous method is much faster than using binding in an item renderer like this:
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
cacheAsBitmap="true"
opaqueBackground="0xFF0000"
autoDrawBackground="false">
<s:Rect id="background" left="0" right="0" top="0" bottom="0" >
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="#FFFFFF" ratio="0"/>
<s:GradientEntry color="#DDDDDD" ratio=".33"/>
<s:GradientEntry color="#999999" ratio=".66"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:Label id="nameLabel" value="{data.name}" left="5" right="5" top="15" bottom="15" fontSize="12"/>
<s:Label id="phoneNumberLabel" value="{data.phoneNumber}" left="5" right="5" top="30" bottom="15" fontSize="10"/>
</s:ItemRenderer>
前面的方法与在条目渲染器中使用绑定的方法相比速度要快得多,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
cacheAsBitmap="true"
opaqueBackground="0xFF0000"
autoDrawBackground="false">
<s:Rect id="background" left="0" right="0" top="0" bottom="0" >
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="#FFFFFF" ratio="0"/>
<s:GradientEntry color="#DDDDDD" ratio=".33"/>
<s:GradientEntry color="#999999" ratio=".66"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:Label id="nameLabel" value="{data.name}" left="5" right="5" top="15" bottom="15" fontSize="12"/>
<s:Label id="phoneNumberLabel" value="{data.phoneNumber}" left="5" right="5" top="30" bottom="15" fontSize="10"/>
</s:ItemRenderer>
当在你的条目渲染器中显示外部图像时,使用共享的ContentCache
如果你需要将外部图像下载到条目渲染器中,那么你应该不会希望每次当数据条目在视图中滚进滚出时都重新下载它们。 正如之前提到的那样,ActionScript IconItemRenderer使用一个 ContentCache来使得你的应用程序不必重新下载相应的组件。 通过声明一个静态的ContentCache实例并给它设置任一BitmapImage组件的contentLoader属性,你可以在你的MXML条目渲染器中进行相似的优化。 然后,该类型的所有条目渲染器将共享这个ContentCache实例,它将自动地加载并缓存你的图像。 这比它看起来还要简单一些,因此请参阅下面的代码范例:
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
cacheAsBitmap="true"
opaqueBackground="0xFF0000"
autoDrawBackground="false"
initialize="initializeHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
import spark.core.ContentCache;
static private const iconCache:ContentCache = new ContentCache();
private function initializeHandler(event:FlexEvent):void
{
icon.contentLoader = iconCache;
}
override public function set data(value:Object):void
{
super.data = value;
if (data)
{
nameLabel.text = data.name;
phoneNumberLabel.text = data.phoneNumber;
icon.source = data.imageUrl;
}
else
{
nameLabel.text = "";
phoneNumberLabel.text = "";
icon.source = null;
}
}
]]>
</fx:Script>
<s:Rect id="background" left="0" right="0" top="0" bottom="0" >
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="#FFFFFF" ratio="0"/>
<s:GradientEntry color="#DDDDDD" ratio=".33"/>
<s:GradientEntry color="#999999" ratio=".66"/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:BitmapImage id="icon" top="10" left="5" width="32" height="32" />
<s:Label id="nameLabel" left="40" right="5" top="15" bottom="15" fontSize="12"/>
<s:Label id="phoneNumberLabel" left="40" right="5" top="30" bottom="15" fontSize="10"/>
</s:ItemRenderer>
你应该保持显示层级越浅越好。 将Group容器与VerticalLayout和HorizontalLayout的组合一起进行嵌套来创建复合布局将会产生许多不必要的布局代码。 相反,你可以考虑使用ConstraintLayout,或者通过使用诸如left、right、top以及bottom等BasicLayout约束条件来明确地指定组件的位置。 你可能已注意到,在代码范例中,我已经明确地指定了位置,而非使用嵌套的Groups或者动态布局。
假定用下面的三种方式来对ContactRenderer(参见图2)进行布局,它是一个MXML条目渲染器,用来显示个人的姓名和电话号码。
如下所示,你可以使用BasicLayout对ContactRenderer进行布局:
<s:BitmapImage id="icon" top="10" left="5" width="32" height="32" />
<s:Label id="nameLabel" left="40" right="5" top="15" bottom="15" fontSize="12"/>
<s:Label id="phoneNumberLabel" left="40" right="5" top="30" bottom="15" fontSize="10"/>
你还能够以下面的方式使用ConstraintLayout:
<s:layout>
<s:ConstraintLayout>
<s:constraintColumns>
<s:ConstraintColumn id="leftPaddingColumn" width="5"/>
<s:ConstraintColumn id="iconColumn" width="32"/>
<s:ConstraintColumn id="middleGapColumn" width="3"/>
<s:ConstraintColumn id="textColumn" width="100%"/>
<s:ConstraintColumn id="rightPaddingColumn" width="5"/>
</s:constraintColumns>
<s:constraintRows>
<s:ConstraintRow id="topPaddingRow" height="10"/>
<s:ConstraintRow id="firstRow" height="16"/>
<s:ConstraintRow id="secondRow" height="16"/>
<s:ConstraintRow id="bottomPaddingRow" height="10"/>
</s:constraintRows>
</s:ConstraintLayout>
</s:layout>
<s:BitmapImage id="icon"
left="iconColumn:0" top="firstRow:0"
width="32" height="32"/>
<s:Label id="nameLabel"
left="textColumn:0" right="textColumn:0" top="firstRow:5"
fontSize="12"/>
<s:Label id="phoneNumberLabel"
left="textColumn:0" right="textColumn:0" bottom="secondRow:2"
fontSize="10"/>
如下所示,最好通过嵌套Group组件来避免定位相应的元素:
<s:Group>
<s:layout>
<s:HorizontalLayout paddingLeft="5" paddingTop="10" paddingBottom="10" gap="3"/>
</s:layout>
<s:BitmapImage id="icon" width="32" height="32" />
<s:Group>
<s:layout>
<s:VerticalLayout gap="3" paddingTop="5" paddingBottom="5"/>
</s:layout>
<s:Label id="nameLabel" fontSize="12"/>
<s:Label id="phoneNumberLabel" fontSize="10"/>
</s:Group>
BorderContainer是一个用来绘制具有边界和背景的容器的适宜组件,然而它并不适用于移动设备。 取而代之的是,你应该使用包含一个Rect的Group来获得相同的视觉效果。
如下所示,使用一个包含一个Rect的Group:
<s:Group width="200" height="200">
<s:Rect left="0" right="0" top="0" bottom="0">
<s:fill>
<s:SolidColor color="#CCCCCC"/>
</s:fill>
<s:stroke>
<s:SolidColorStroke color="#999999"/>
</s:stroke>
</s:Rect>
<!-- Define child components here -->
</s:Group>
如下所示,避免使用 BorderContainer:
<s:BorderContainer width="200" height="200"
backgroundColor="#CCCCCC" borderColor="#999999">
<!-- Define child components here -->
</s:BorderContainer>
移动视图应该利用MXML编写。 不像条目渲染器那样,通常在某一时刻只有一个视图可见,因此,MXML的轻松使用要胜于ActionScript的性能优势。 无论如何,当你在不同的视图间进行切换时,你需要许多优化措施来确保你的应用程序运行顺畅并且响应快速。
不要在creationComplete处理程序中初始化View的外观,因为这将会导致该视图再处理,这将推迟相应的视图转换。 取而代之的是,你需要在已被覆盖的数据setter函数中改变你的视图外观。 与使用条目渲染器的情形一样,覆盖相应的数据setter函数能够消除多余的绑定表达式(binding expression)。
服务器调用等异步操作可以在视图转换过程中返回,并且需要额外的运行代码,这可能会导致视图转换产生停顿现象。 最好在视图被转换到屏幕上之后,在viewActivate处理程序中启动异步操作。 你还应该考虑一下在切换视图前清除viewDeactivate处理程序中的一些未完成的异步调用。
在开始进行视图转换之前,Flex需要创建并布局视图的所有子视图。 在开始阶段你应该尽可能保持视图的轻量级状态,以便最大程度地降低用户互动和视图转换开始时刻之间的延时。 如果可能,你应该在视图被转换到屏幕上之后逐渐地创建组件,但这一操作应该在viewActivate事件之后进行。 你必须避免创建在初始状态的视图中不可见的组件。
如果可能,使用includeIn和excludeFrom将不应该可见的组件从显示列表中删除。 这样能够避免对它们进行处理。 相反,如果你将 visible设置为false,那么组件将保留在显示列表上,这样需要执行不必要的布局操作和渲染过程。
如果用户经常切换回视图模式,那么你可能希望通过在内存中保留它以避免再一次创建它产生的成本。 但是,请记住在移动设备上,内存是稀缺的资源! 如果你放松对设备的RAM的限制,那么在内存中保留各种复杂的视图(或甚至保留太多简单的视图)将会降低其性能。
如下所示,为了通过使用更多内存来改善处理时间,你可以在View上将destructionPolicy设置为"never":
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
title="My View"
destructionPolicy="never">
<!-- define child components here -->
</s:View>
下面的优化措施会帮助你在你的应用程序中加速处理各种类型的图形,而不管这些图形是由条目渲染器、视图或者其它组件显示的。
使用平滑处理技术来缩放图像看起来很好,然而这却要求提供很强的处理能力。 你应该以不同的尺寸创建多个版本的图像组件,而不是创建一个大的图像组件,然后将它根据不同的屏幕分辨率和DPI级别进行缩小。 查看Flex计算机科学家Jason San Jose的文章Flex移动皮肤—第二部分:处理不同的像素密度( Flex Mobile Skins – Part 2: Handling Different Pixel Densities),以便于了解提供各种最佳方法的详细教程。
像大多数高质量的图像处理运算那样,Spark过滤器在移动设备上的计算量过于冗长,因此你应该避免使用它们,而选择其它技术。 在大多数情形下,你可以直接在你的图像组件中绘制一个阴影。 当处理文本时,你可以复制该文本并对它进行偏移以便创建一个阴影效果。 如果你希望获得一个落在矩形组件上的阴影,那么Flex能够为 DropShadowFilter提供一个名称为RectangularDropShadow的非常有效的替代品。 RectangularDropShadow不是过滤器,而是组件,你可以基本上像使用Rect一样使用它。
如下所示,使用RectangularDropShadow:
<s:Group width="200" height="200">
<!-- drop shadow -->
<s:RectangularDropShadow left="0" right="0" top="0" bottom="0"
distance="3" alpha="0.5"/>
<!-- background and border -->
<s:Rect left="0" right="0" top="0" bottom="0">
<s:fill>
<s:SolidColor color="#DDDDDD"/>
</s:fill>
<s:stroke>
<s:SolidColorStroke color="#999999"/>
</s:stroke>
</s:Rect>
<!-- define child components here -->
</s:Group>
如下所示,避免使用DropShadowFilter:
<s:Group width="200" height="200">
<!—- drop shadow -->
<s:filters>
<s:DropShadowFilter distance="3" alpha="0.5"/>
</s:filters>
<!-- background and border -->
<s:Rect left="0" right="0" top="0" bottom="0">
<s:fill>
<s:SolidColor color="#DDDDDD"/>
</s:fill>
<s:stroke>
<s:SolidColorStroke color="#999999"/>
</s:stroke>
</s:Rect>
<!-- define child components here -->
</s:Group>
BitmapImage组件是一个轻量级版本的Image组件。 这两个组件都能够显示嵌入式的图像组件。 两者之间最大的差异是Image 能够即装即用加载外部图像,而BitmapImage需要一些设置才能加载外部图像。
与BitmapImage不同,Image的皮肤也是可换的并且支持丢失图像指示器。 对于嵌入图像,你不需要所有这些额外的功能,恰恰相反,你应该使用轻量级的BitmapImage组件。
如下所示,使用BitmapImage组件:
<s:BitmapImage source="@Embed('assets/icon.jpg')"/>如下所示,避免使用Image组件:
<s:Image source="@Embed('assets/icon.jpg')"/>运行时在对PNG文件格式进行解码时速度要快得多,因此无论在什么情况下,只要你有选择权,那么你都应该使用PNG来代替GIF和JPEG图像。
为方便起见,你应该使用MXML原语来绘制背景矩形等简单的矢量图像。 但是,对于更复杂的图形和矢量插图来说,你应该在Adobe Illustrator中或者相似的设计工具中创建FXG文件。 FXG文件由一个预编译的绘图指令序列组成,与大量在MXML中声明的原语来相比,FXG文件具有更快的执行速度并占用较少的内存。 如需了解更多关于 FXG与MXML图形对比的信息,请查看相应的FXG和MXML Graphics参考文章。
Flex能够提供Resize或Fade等效果,它们能够方便地为用户界面在响应触摸互动操作时提供动画效果。 最好应该限制一下在同一时刻运行的效果数量,这是因为就处理成本而言,它们会叠加在一起。
通过使用CSS样式,你可以充分利用高度优化的默认Flex移动皮肤。 在大多数情形下,完全可以使用样式来定制你的UI组件的外观。 如需获取一些CSS样式的范例,请查看Holly Schinsky的文章设计你的Flex 4.5移动应用程序标签和工具栏的式样(Styling your Flex 4.5 Mobile Application Tabs and ActionBar)。
如果CSS样式的数量不足,那么你可能会决定为某个组件编写一个自定义的移动皮肤。 你应该利用ActionScript 编写该皮肤并扩展相应的默认Flex移动皮肤以便充分利用它们的潜在的性能优化优势。 查看Jason San Jose关于移动植皮的系列文章,它们能够指导你了解相应的过程。
在你的应用程序开发过程的每一个步骤中,你都需要确保完成你的性能目标。 不要让你自己指望其它功能,直到你满意你的应用程序在其当前阶段的性能。 声称“我稍后来修复它”可能是自负特征的第一个警告信号或者是一个不良架构选择。 解决这两个问题都应该宜早不宜迟。
在这篇检查列表中的许多优化措施都直接地来源于先前和现在的Flex SDK团队成员的精彩演讲,其中包括Evtim Georgiev、Steven Shongrunden、Glenn Ruehle以及Ryan Frishberg等。 我强烈建议对这些主题的更多细节感兴趣的每一个人都去看一下Adobe TV上的演讲: