如果你沒有看過
Roger Gonzalez的Blog中關於模組(Module)的文章,那麼你應該去那裡瞭解一下Flex 2這個特性背後的細節和想法。這裡我不想過多地探討為什麼要這樣,而是想要給大家展示一個使用了模組(Module)的簡單的Flex程式,你可以從中獲得啟示。
樣本源碼
你可以下載關於這個例子的壓縮檔:點擊這裡下載。
模組(Modules)
模組(Module)是建立大型Flex應用程式的一個解決方案,它允許你將你的使用者介面分割成許多分散的有各自用途的小塊。例如(下面出自Flex 2的文檔),一個保險公司可能有數百個表單——針對於各個領域的,針對各種請求類型,以及針對各種應用等等。建立一個包含所有這些表單的Flex應用程式將會產生一個巨大SWF檔案,還會有不少問題:
•應用程式越大開發過程越複雜;
•應用程式越大測試過程越複雜;
•應用程式越大部署過程越複雜;
•SWF檔案越大載入時間越長
我的樣本程式基於Flex 2文檔中的一個程式,但是我將它做了一些更改來說明幾個常見的問題。這個例子展示了一個主程式和其它三個共用公有資料的模組(Module)。
其中一個設計要素是一個介面的使用,這個介面實質上是介面實現者和使用者之間的一個契約。這個例子將會說明我所說的意思。模組(Module)的介面部分雖然不是必須的但是卻可以大大簡化以後的開發和維護。比如,如果開發人員有一個小組負責報告部分,另一個小組負責圖表部分,如果它們一開始用了介面,那麼只要有需要,介面的實現就可以做足夠多的變形而不會影響到工程結果。介面在模組(Module)中還扮演另外一個角色,我在下文中將會揭示這點。
模組(Module)是以<mx:Module>代替<mx:Application>作為根標籤的MXML檔案(或ActionScript檔案)。你可以將帶有<mx:Module>標籤的作為一個程式來看,但是它不能運行。
這個樣本有一個主程式檔案以及帶有一個介面的兩個模組。開啟主程式檔案你會看到:
程式碼<mx:Panel x="10" y="41" width="169" height="500" layout="absolute" title="Modules">
<mx:Text x="10" y="24" text="Check a module to load it; uncheck to unload it" width="129"/>
<mx:RadioButton x="10" y="97" label="None" selected="true"
click="removeModule()"/>
<mx:RadioButton x="10" y="123" label="Chart"
click="removeModule();loadModule('ChartModule.swf')"/>
<mx:RadioButton x="10" y="175" label="Table"
click="removeModule();loadModule('GridModule.swf')"/>
</mx:Panel>
<mx:Panel x="187" y="41" width="500" height="500" layout="absolute" title="Module: {moduleName}">
<mx:ModuleLoader id="currentModule" ready="readyModule(event)"
width="100%" height="100%" />
</mx:Panel>
第一個Panel包含了控制樣本中模組(Module)載入和卸載的RadioButtons。第二個Panel是使用<mx:ModuleLoader>標籤載入模組(Module)的地方。注意那個id為currentModule的ModuleLoader,它有一個關於ready事件的事件處理器。當模組SWF檔案載入了足夠多可以開始使用的時候,ModuleLoader 就會指派ready事件(或者說ModuleEvent.READY)。
這裡有一個readyModule函數,它在<mx:Script>塊中:
程式碼private function readyModule( event:ModuleEvent ) : void
{
var ml:ModuleLoader = event.target as ModuleLoader;
var ichild:IExpenseReport = ml.child as IExpenseReport;
if( ichild != null ) {
ichild.expenseReport = expenses;
}
}
注意ModuleLoader的child屬性是如何轉換為IExpenseReport類的。IExpenseReport是一個所有模組(Module)都實現了的介面。只要每個模組都實現了這個介面,它就可以很容易適應於應用程式。換句話說,想象一下當你需要建立另一個表單或者報告的時候它的用途。並不需要更改主程式為新模組添加IF語句,你只要在新模組中實現IExpenseReport介面它就可以在程式中完美地運行。
IExpenseReport介面是:
程式碼public interface IExpenseReport
{
function set expenseReport( ac:ArrayCollection ) : void;
}
每個模組(Module)都實現這個介面,定義各自的名為expenseReport的set函數。下面是ChartModule的根標籤和介面IExpenseReport的實現:
程式碼<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" implements="IExpenseReport"
layout="vertical"
percentWidth="100" percentHeight="100" >
<mx:Script><![CDATA[
import mx.collections.ArrayCollection;
[Bindable] public var expenses:ArrayCollection;
public function set expenseReport( ac:ArrayCollection ) : void
{
expenses = ac;
}
]]></mx:Script>
...
</mx:Module>
讓我們回到主程式,RadioButton的click事件會卸載任何當前已載入的模組然後載入一個新的模組。下面是ChartModule的RadioButton標籤:
程式碼<mx:RadioButton x="10" y="123" label="Chart" click="readyModule('ChartModule.swf')"/>
這個click事件會調用上面列出的readyModule事件。
編譯並運行程式
如果你使用了Flex Builder 2,請確定更改了項目的Properties將模組(Module)作為"Applications"包含進來。這樣Flex Builder 2回將它們編譯進SWF檔案並且放進bin檔案夾中。
Flex Builder注意:要建立一個使用模組(Module)的工程,請使用工程的Properties將模組檔案作為"Applications"。這會使得他們被編譯進SWF檔案。
一旦SWF檔案被建立你就可以運行主程式並點擊RadioButtons在模組(Module)之間切換。
Flex Builder注意:Flex Builder並不會儲存任何關於模組(Module)和主程式的從屬資訊。只要你對一個模組(Module)作了更改,你就可能需要重新編譯主程式或其它從屬的模組(Module)。
將SWF檔案最佳化
如果你查看一下主程式的SWF檔案和模組(Module)的SWF檔案的話,你會發現它們的大小差不多。這就說明,模組的SWF和主程式SWF中有很多同樣的組件定義。
Flash Player並不會儲存元件(symbol)的副本。例如,如果主程式有一個Button組件而一個模組(Module)也有一個Button組件,Flash Player就不會從模組中載入Button了,因為它已經在主程式中有定義了。
使用-link-report=report.xml編譯主程式,這樣會建立一個連結到主程式的包含所有元件資訊的檔案。然後在編譯模組(Module)的時候會使用那個report.xml檔案。
程式碼mxmlc -load-externs=report.xml ChartModule.mxml
當ChartModule被編譯的時候,所有在report.xml檔案中列出的元件將會在它的SWF中省略。當我不使用report.xml檔案編譯ChartModule.swf的時候,它的大小是202K。而當我使用report.xml檔案的時候,SWF的大小隻有68K。這大大減少了模組(Module)的載入時間。
在文章的開始將到模組(Module)的時候,我提過介面有另一個作用。假設你沒有使用介面而是在主程式中引用模組的類。當你運行link-report的時候,你的模組類將會出現在report.xml中。當你使用使用link-report編譯模組(Module)的時候你的模組並不會包含在它自己的SWF中!起初這並不會成為一個問題,儘管主程式由於包含了模組的定義而變得很大。然而,當你更改你的模組的時候發生了什麼才是重要的。如果你沒有重新編譯主程式,你主程式的SWF檔案將會包含模組(Module)舊的定義——而不是你已經更改過的。
程式碼mxmlc -link-report=report.xml Main.mxml
mxmlc -load-externs=report.xml ChartModule.mxml
// etc.
如果你決定使用這個技術來減小模組(Module)的大小,那麼就使用介面來確保終端使用者使用的總是模組(Module)的最新版本。
Flex Builder注意:Flex Builder在一個工程裡沒有辦法做到這些。如果你確定你將要建立一個使用模組(Module)的工程,可以考慮一下將公用的類和介面(包括event類)放到一個SWC(Flex Library Project)中然後將模組(Module)分離到它們各自的工程裡。
或者,你可以將所有東西建立為一個單一的Flex工程,然後將最佳化作為一個產品化前或測試前的部署步驟在Flex Builder之外進行。
總結
•將程式分割成多塊並不是每個人都會使用模組(Module)。使用這種方法主程式會比平常小,而且多數使用者只使用整個程式的一部分。
•使用介面來使主程式或模組(Module)來與載入的模組通訊。這使維護變得容易。
•使用-link-report命令參數編譯主程式來產生一個使用元件列表。
•在主程式中使用-load-externs和report.xml編譯模組(Module),這樣可以使它們變得更小。