最近同事遇到一個 Flex 問題,只要 Module 內放了其它組件
執行就會出現各種奇怪 Error
後來發現是因為在 Main Application 直接引用編譯到 Module Class
然後又企圖用 ModuleLoader 再載入一次相同的 Module SWF
當然這樣做是錯誤的範例,Flash Builder 也會給予警告

Warning: Mod is a module or application that is directly referenced.
This will cause Mod and all of its dependencies to be linked in with MainApp.
Using an interface is the recommended practice to avoid this.

不要明確引用就正常了
不過還是覺得有點不合理,至少應該能正常執行吧

譬如以下的例子 MainApp.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
     xmlns:s="library://ns.adobe.com/flex/spark" 
     xmlns:mx="library://ns.adobe.com/flex/mx">
 <fx:Script>
  <![CDATA[
   Mod;
  ]]>
 </fx:Script>
 <s:layout>
  <s:VerticalLayout verticalAlign="middle" horizontalAlign="center" />
 </s:layout>
 <s:ModuleLoader id="modLdr" url="Mod.swf" width="50%" height="50%"/>
</s:Application>

直接引用到外部 Module,又企圖載入一次同一個外部 Module – Mod.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Module xmlns:fx="http://ns.adobe.com/mxml/2009"
  xmlns:s="library://ns.adobe.com/flex/spark"
  xmlns:mx="library://ns.adobe.com/flex/mx"
  width="100%" height="100%">
 <s:Panel width="100%" height="100%" title="Module" />
</s:Module>

隨著 Module 內放置組件不同,可能會得到以下各種錯誤訊息

Main Thread (Suspended: ArgumentError: Error #2004: 有一個參數無效。)
flash.display::Graphics/drawRect [no source]
spark.components.supportClasses::TextBase/updateDisplayList
mx.core::UIComponent/validateDisplayList
mx.managers::LayoutManager/validateDisplayList
mx.managers::LayoutManager/doPhasedInstantiation
mx.managers::LayoutManager/doPhasedInstantiationCallback

Main Thread (Suspended: TypeError: Error #1009: 無法存取 Null 物件參考的屬性或方法。)
mx.core::UIComponent/getStyle
mx.core::UIComponent/getConstraintValue
mx.core::UIComponent/get horizontalCenter
spark.layouts::BasicLayout/measure
spark.components.supportClasses::GroupBase/measure
mx.core::UIComponent/measureSizes
mx.core::UIComponent/validateSize
spark.components::Group/validateSize
mx.managers::LayoutManager/validateSize
mx.managers::LayoutManager/doPhasedInstantiation
mx.managers::LayoutManager/doPhasedInstantiationCallback

Flex ModuleLoader 預設載入外部 Module 時
是會從目前 ApplicationDomain 建立一個 child ApplicationDomain 作為載入之用
也就是說假如 Main App 已經包含一份 Module 定義
再載入同名類別,就會被前面的類別定義覆蓋
實際上,被 new 出來的實體其實是 Main App 內定義的

假如兩份定義完全一樣,應該也是要能正常執行吧
問題就是出在這裡了,用 ASV 分別去觀察兩個 SWF 內的 Module 類別
發現是不一樣的!

當 Flex Module 編譯為獨立 SWF 時
MXMLC Compiler 會塞入一些額外的 Metadata Tag, Code… 做初始化
可是 MainApp 內的 Module 定義少了這些動作,導致無法正常執行

解決的方式不難,ModuleLoader Ready 時
自己手動執行一下關鍵的初始動作就好了 – styleManager.initProtoChainRoots();

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
     xmlns:s="library://ns.adobe.com/flex/spark" 
     xmlns:mx="library://ns.adobe.com/flex/mx">
 <fx:Script>
  <![CDATA[
   Mod;
  ]]>
 </fx:Script>
 <s:layout>
  <s:VerticalLayout verticalAlign="middle" horizontalAlign="center" />
 </s:layout>
 <s:ModuleLoader id="modLdr" url="Mod.swf" width="50%" height="50%"
   ready="event.target.child.styleManager.initProtoChainRoots();" />
</s:Application>