FLEX module的使用(转载)

1 ModuleLoader组件概述
1.1Module技术简介
flex的Modules技术是可以被flex程序使用的一个swf文件,它不能脱离程序独立运行,但是多个程序之间可以共享它。
flex的Modules技术将应用程序分割成小块、模块,主程序动态的加载所需要的模块。主程序在启动时并不全部加载所有的模块。当用户和模块没有交互的时候它不需要加载模块,同时它在模块不需要的时候可以卸载模块并释放内存和资源。[1]

flex的Modules技术主要有如下的优点:
让swf文件初始下载尺寸更小
让加载时间更短
对应用程序更好的封装性

1.2ModuleLoader组件功能
ModuleLoader组件是一种可视化的组件,方便开发人员在运行期间动态地加载和卸载module。
注:加载:从服务器下载到浏览器中

1.3 ModuleLoader类的继承关系
包:mx.modules[2]
ModuleLoaderInheritanceVBoxInheritanceBoxInheritanceContainerInheritanceUIComponent
注:ModuleLoader类暂无spark组件

相关类:
* IModuleInfo – module接口,所有的module都实现了此接口,可以提供module信息,比如url 和 加载状态。
* Module – 基于MXML的module的基类
* ModuleBase –基于ActionScript的module基类
* ModuleManager –一个系统只有一个此类,用于管理所有动态加载的module
* ModuleEvent – module相关事件

1.4 相似组件
(1)SWFLoader
ModuleLoader与swfLoader组件功能相同
ModuleLoader要求加载的SWF文件必须实现接口IFlexModuleFactory,这样,ModuleLoader通过工厂模式可以根据需要创建多个实例。
swfLoaderr组件对于SWF文件没有任何要求。

(2)ViewStack
ModuleLoader与ViewStack的作用比较类似,都用于加载多个不同模块。
ModuleLoader只能加载module,而且一次只能从服务器加载一个。ModuleLoader不必在初始化时携带加载所有的孩子组件,节省了下载量。
ViewStack一次加载多个容器,比如panel,cavas,但是可以延时初始化,以提高速度。ViewStack必须下载所有孩子组件后,只对当前使用的孩子组件进行初始化,加快显示速度。

(3)ModuleManager
ModuleLoader组件提供了可视化的加载和卸载module的操作。
ModuleManager类提供了低层次的处理Module的装载卸载以及事件响应等方法。这种方式比起纯粹的 ModuleLoader方式稍微复杂一点,但是ModuleManager提供了比ModuleLoader更加强大的能力来管理Module模块的加 载过程。

(4)RSL
RSL和module都是代码独立和复用机制[3]
Modules被编译到SWF文件内,可以加载和卸载
类库被编译到SWC文件内,可以在编译期间使用并编译到application中,并在application运行前加载

参考文献
[1]Modularapplicationsoverview.http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf69084-799a.html
[2]ModuleLoader类.http://www.adobe.com/devnet/flex/tourdeflex/langref/mx/modules/ModuleLoader.html
[3] Flex Modules . http://www.flexafterdark.com/docs/Flex-Modules

2 ModuleLoader常用属性和方法
@font-face { font-family: “宋体”; }@font-face { font-family: “@宋体”; }p.MsoNormal, li.MsoNormal, div.MsoNormal { margin: 0cm 0cm 0.0001pt; text-align: justify; font-size: 10.5pt; font-family: “Times New Roman”; }div.Section1 { page: Section1; }
名称
分类
说明
applicationDomain
属性
ApplicationDomain类,将你的模块加载到哪个应用域,详见例3.9
url
属性
String类型。要加载的外部MXML module 的url.,详见例3.1和例3.2
loadModule
方法
格式:loadModule (url:String = null, bytes:ByteArray = null):void
加载module,详见例3.2
unloadModule
方法
格式:unloadModule():void
卸载module,释放内存,详见例3.2
error
事件
当module抛出错误时派发,参见例3.3
loading
事件
当ModuleLoader开始根据URL加载module时派发
progress
事件
Module加载过程中以一定的间隔周期派发,参见例3.3
ready
事件
当module完成加载时派发,参见例3.3
setup
事件
当module已下载(通过info()函数访问),但module并没有加载完成时(not ready)派发
unload
事件
当module卸载完成时派发,参见例3.3
urlChanged
事件
当ModuleLoader有了一个新的URL时派发

3 ModuleLoader使用方法
3.1 通过URL自动加载module和Alert使用举例
功能说明
通过loadModule加载外部的WelcomeModule,点击module中的alert,显示欢迎

关键代码
ModuleLoaderExam1.mxml


说明:
(1)如果在module中使用Alert,必须声明PopUpManager,并且实例化,原因未知。
(2) creationComplete事件表示,当加载完成时,将当前应用程序的域赋给module1,这个语句可以不加,为防万一,最好加上。

ModuleExam1.mxml
protected function openAlert_clickHandler(event:MouseEvent):void
{
Alert.show(“欢迎”, “欢迎点击Alert”, Alert.OK, this);
}

代码详见:ModuleLoaderExam1.mxml和ModuleExam1.mxml

3.2手工加载和卸载module举例
功能说明
通过设置URL属性,手工加载和卸载module
函数说明:loadModule (url:String = null, bytes:ByteArray = null):void
url:默认为null,表示当前要加载的module的URL,如果ModuleLoader的URL不为null,则该方法无效
bytes:module的SWF文件的字节数,可以通过URLLoader类获取

关键代码
ModuleLoaderExam2.mxml
public function loadModule21():void
{
if(mod_loader.url==null)
mod_loader.url = “example/ModuleExam21.swf”;
}
public function loadModule22():void
{
mod_loader.loadModule(“example/ModuleExam22.swf”);
}
public function unload():void
{
mod_loader.unloadModule();
mod_loader.url = null;//注意,卸载完成后必须设置URL为null,否则内存泄露
}

说明:
(1)mod_loader.url的值被修改后,直接触发重新加载module,不必再调用loadModule方法
(2) mod_loader.url = null;必须在卸载完成后加上,否则内存泄露

代码详见:ModuleLoaderExam2.mxml、ModuleExam21.mxml和ModuleExam22.mxml

3.3 module常用事件举例
功能说明
演示常用事件的处理,module的常用事件包括
Error:加载错误时引发
Ready:加载完成后引发
Progress:加载过程中定时引发
Unload:卸载完成后引发

关键代码
ModuleLoaderExam3.mxml

protected function mod_loader_errorHandler(event:ModuleEvent):void
{
lbRecord.text=lbRecord.text+”n error:”+event.module.url;
}
protected function mod_loader_readyHandler(event:ModuleEvent):void
{
lbRecord.text=lbRecord.text+”n ready:”+event.bytesLoaded+”bytes:”+event.bytesTotal;
}
protected function mod_loader_progressHandler(event:ModuleEvent):void
{
lbRecord.text=lbRecord.text+”n progress:”+event.bytesLoaded+”bytes:”+event.bytesTotal;
}
protected function mod_loader_unloadHandler(event:ModuleEvent):void
{
lbRecord.text=lbRecord.text+”n unload:”+event.bytesLoaded+”bytes:”+event.bytesTotal;
}
代码详见:ModuleLoaderExam3.mxml、ModuleExam21.mxml和ModuleExam22.mxml

3.4 Application调用module中的方法举例
功能说明
演示Application程序如何调用module中的方法
Application并不能直接访问module提供的方法,而是通过moduleLoader中的属性child来完成的。

关键代码
ModuleLoaderExam4.mxml

private function getTitle():void {
s = (m1.child as ModuleExam4).getModTitle();
}

ModuleExam4.mxml
public function getModTitle():String {
return “Child Module 1”;
}
说明:
(m1.child as ModuleExam4).getModTitle(),表示将m1中的属性child对应的对象强制转换为ModuleExam4类,再调用这个对象的方法。

代码详见:ModuleLoaderExam4.mxml和ModuleExam4.mxml

3.5 通过ModuleLoader的URL向module传递参数举例
功能说明
通过URL属性向module传递参数
GET参数基本上是这种格式,url=module1.swf?param1=value1&param2=value2

关键代码
ModuleLoaderExam5.mxml
public function submitToModule():void {
var s:String = “example/ModuleExam5.swf?”
+ “firstName=” +ti1.text + “&lastName=” + ti2.text;
m1.url = s;
}

ModuleExam5.mxml
//对参数进行过滤
var myPattern:RegExp = /.*?/;
var s:String = this.loaderInfo.url.toString();
s = s.replace(myPattern, “”);
// 通过&拆分成数组name=value,保存为String类型数据
var params:Array = s.split(“&”);
// 显示数组中各个元素的值
var keyStr:String;
var valueStr:String;
var paramObj:Object = params;
for (keyStr in paramObj) {
valueStr = String(paramObj[keyStr]);
ta1.text += keyStr + “:” + valueStr + “n”; //keyStr的值为0。1.2…
}
// 通过=拆分各个元素,并保存到变量salutation中
for (var i:int = 0; i < params.length; i++) { var tempA:Array = params[i].split("="); if (tempA[0] == "firstName") { o.firstName = tempA[1]; } if (tempA[0] == "lastName") { o.lastName = tempA[1]; } } 代码详见:ModuleLoaderExam5.mxml和ModuleExam5.mxml 3.6利用ActionScript接口实现Module与Application数据交互举例 功能说明 对于Module模块和Application对象间的通信,可以定义一个ActionScript接口,Module模块对象实现了这个接口中定义的方 法和属性,那么Application就可以访问这个接口中定义的属性和方法。接口中定义了Module模块对象和Application需要共享的数据 和方法,是两者间共同的一个契约,同时也实现了接口和实现的分离,达到了松耦合的目的。 接口类IModuleInterface.as: public interface IModuleInterface extends IEventDispatcher { function getModuleName():String; function setAdjusterID(s:String):void; function setBackgroundColor(n:Number):void; } ModuleExam6.mxml 模块必须实现以上接口,代码如下
public function setAdjusterID(s:String):void {
adjuster = s;
}
public function setBackgroundColor(n:Number):void {
bgcolor = n;
}
public function getModuleName():String {
return “module Insurance”;
}
说明:
这个module实现了接口中的三个方法。

ModuleLoaderExam6.mxml
private function applyModuleSettings(e:Event):void {
// m1.child 是接口IModuleInterface的具体实现对象
//var ichild:* = mod.child as IModuleInterface;
var ichild:IModuleInterface = m1.child as IModuleInterface;
if (m1.child != null) {
ichild.setAdjusterID(myId.text);
ichild.setBackgroundColor(myColor.selectedColor);
}
//从接口获取模块的名称
currentModuleName = ichild.getModuleName();
}
说明:
Application通过接口中的方法setAdjusterID和setBackgroundColor向module传递数据,
Module通过接口中的方法getModuleName向Application传递数据
代码详见:ModuleLoaderExam6.mxml、ModuleExam6.mxml和IModuleInterface.as

3.8 Module与Module之间数据交互举例
功能说明
一个Application如果具有两个Module,这两个Module可以通过Application传递数据。原理如下:




在module1中,你可以通过以下方法访问module2的属性和方法
parentApplication.module2.child.someProperty;
parentApplication.module2.child.someMethod();

关键代码
ModuleLoaderExam7.mxml

ModuleExam71.mxml
[Bindable]
public var m1:String=”module1″;
public function getProperty(event:MouseEvent):void {
lbmodule2.text=parentApplication.module2.child.m2;
}
public function getMethod(event:MouseEvent):void {
lbmodule2.text=parentApplication.module2.child.getModuleName();
}



说明
getProperty函数功能:直接访问module2中的属性m2
getMethod函数功能:通过调用module2中的函数getModuleName获取文本输入框的值

ModuleExam72.mxml
[Bindable]
public var m2:String=”module2″;
public function getModuleName():String {
return lbmodule2.text;
}

说明
m2与lbmodule2之间的绑定是单向的,修改m2的值,则改变lbmodule2的text,修改lbmodule2的值并不影响m2的值
代码详见:ModuleLoaderExam7.mxml、ModuleExam71.mxml和ModuleExam72.mxml

参考文献

Flex Modules . http://www.flexafterdark.com/docs/Flex-Modules

3.9 applicationDomain属性举例
功能说明
ApplicationDomain 类是一个容器,用于管理不同swf的安全域和类之间的关系。它们允许同一个类存在不同的ApplicationDomain。
ApplicationDomain是一个树形结构,Application所在的域(以下简称主域)就是它唯一的子域,使用Loader类加载swf时可以通过指定ApplicationDomain 参数将swf加载到不同的域(Domain)。
ApplicationDomain最根部的是系统域(system domain),主程序所在的域(以下简称主域)就是它唯一的子域。SWF 文件中的所有代码被定义为存在于应用程序域(ApplicationDomain)中。主应用程序在”当前域”中运行。”系统域”中包含所有应用程序域 (包括当前域),也就是,它包含所有 Flash Player 类。
所有应用程序域(除系统域外)都有关联的父域。主应用程序的应用程序域的父域是系统域。已加载的类仅在其父级中没有相关定义时才进行定义。不能用较新的定义覆盖已加载类的定义。

有两种方式可以访问 ApplicationDomain :
(1)ApplicationDomain.currentDomain
currentDomain是ApplicationDomain的静态变量,表示当前代码所在的域。例如:该变量在主程序里指向主域,在加载到子域的模块里和currentDomain则指向该模块所在的子域。
参见例3.1,通过URL自动加载module和Alert使用举例

虽然 ApplicationDomain有个 parentDomain 属性,但子域已经自动获得了父域的类定义,所以通过 ApplicationDomain.currentDomain 就可以获取父域定义了——包括主程序和加载到主域的共享库。(注:系统域不可直接访问,主域和所有新域即系统域子域的parentDomain属性为 null)
参见例3.8,Module与Module之间数据交互举例

(2)LoaderInfo类的applicationDomain属性
此方式可以访问任何方式加载的swf的 ApplicationDomain。对于主程序来说,加载到同域的库定义已经存在于 ApplicationDomain.currentDomain ,而模块的类主程序一般用不到。所以这种方式个人不推荐使用。
使用Loader类加载swf时可以通过指定 ApplicationDomain参数将swf加载到不同的域(Domain):
var loader : Loader = new Loader();
var context : LoaderContext = new LoaderContext();
/* 加载到子域(模块) */
context.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
/* 加载到同域(共享库) */
context.applicationDomain = ApplicationDomain.currentDomain;
/* 加载到新域(独立运行的程序或模块) */
context.applicationDomain = new ApplicationDomain();
loader.load(new URLRequest(“loaded.swf”), context);

(1)加载到子域(module)—参见图中的用法C
类似于“继承”,子域可以直接获得父域所有的类定义,反之父域得不到子域的。和继承关系不同的是,如果子域中有和父域同名的类,子域定义的类会被忽略而使用父域的定义的类。最大特点是可以动态的加载和移除。便于垃圾回收。
context.applicationDomain = new
ApplicationDomain(ApplicationDomain.currentDomain);
说明创建一个新的域,并变成当前域的子域

(2)加载到同域(RSL) —参见图中的用法B
就是在当前的ApplicationDomain添加新的类定义。类似集合里的合并关系。被加载swf里的所有类定义被合并到当前域中可以直接使用。和加载到子域相同,和当前域同名的定义也会被忽略。
context.applicationDomain = ApplicationDomain.currentDomain;

(3)加载到新域(独立运行的程序或模块) —参见图中的用法A
用的不是很多,实际上是单独的创建一个新的域和当前应用程序域平级。作为系统域的真子集。
swf载入指定域之前,先要检查该域及其父域中是否存在同名类,重复定义一概忽略。如果加载别人写的程序,或者使用旧版本的主程序加载新版本的模块,为避免类名冲突就要加载到新域独立运行以使用自己的类。
context.applicationDomain = new ApplicationDomain();
loader.load(new URLRequest(“loaded.swf”), context);
说明:创建了一个新的域,并加载到独立的程序loaded.swf中。

模块加载到同域不是一样可以吗?为何要加载到子域呢?
好处就在于,卸载一个加载到子域的模块时,只要确保清除所有到该模块的引用,模块的所有类定义将被垃圾回收(Garbage Collection)。

关键代码
ApplicationDomain中的currentDomain属性使用参见例3.1
ApplicationDomain中的parrentDomain属性使用参见例3.8

参考文献
1. Flex 中ApplicationDomain. http://www.wedoswf.com/questions/935
2. flash 中的 ApplicationDomain . http://chaimzane.javaeye.com/blog/470987
3. AS3应用程序模块化开发与ApplicationDomain . http://hereson.javaeye.com/blog/192337

3.10 module中使用PopUpManager和DragManager异常说明
通常将module加载为主域的一个子域,module里面的类都不属于主域的。比如第一个module载入了类PopUpManager,那么整合到 Application中,它就成了PopUpManager的拥有者,因为像这种manager都是单例的,如果另外一个模块稍后要使用这个 PopUpManager,就会引发运行时异常。
解决办法就是确保这些managers,比如PopUpManager和DragManager或者其他一些共享的服务是在application中定义的,这样就能确保所有模块都能够使用,代码如下:
import mx.managers.PopUpManager;
import mx.managers.DragManager;
private var popUpManager:PopUpManager;
private var dragManager:DragManager;

这项技术同时也被应用到组件中,当module第一次使用组件时,将在它自己的域中拥有这些组件的类定义。如果别的module试图使用这些已经被另一个 module使用的组件,它的定义将会不能匹配到现存的定义中。因此,为了避免组件的定义不匹配,在主应用程序中创建组件的实例,让所有的module去 引用。
但是这个坏处很明显,这些声明看起来莫名其妙,成为了一个个”木偶变量”。另一个解决方法是借助 ApplicationDomain 来共享这些代码和资源。在ModuleLoader 的creationComplete方法中加入moduleLoader.applicationDomain = ApplicationDomain.currentDomain; 表示将其加载到运行时库。对于使用ModuleManager,则可以在IModuleInfo的load方法里指定域。

参见:
flash.system.ApplicationDomain
flash.system.SecurityDomain

When to new your model?

http://joelhooks.com/2011/03/12/an-introduction-to-robotlegs-as3-part-2-models/

you dun need actually
public class AuthorModel extends Actor
{
private var _list:Array;

public function get list():Array
{
if(!_list)
initializeList();
return _list;
}

protected function initializeList():void
{
var twain:Author = new Author(“Twain”);
var poe:Author = new Author(“Poe”);
var plato:Author = new Author(“Plato”);
var fowler:Author = new Author(“Fowler”);

twain.quote = “Why, I have known clergymen, good men, kind-hearted, liberal, sincere” +
“, and all that, who did not know the meaning of a ‘flush.’ It is enough ” +
“to make one ashamed of one’s species.”;
fowler.quote = “Any fool can write code that a computer can understand. ” +
“Good programmers write code that humans can understand.”;
poe.quote = “Deep into that darkness peering, long I stood there, wondering, ” +
“fearing, doubting, dreaming dreams no mortal ever dared to dream before.”;
plato.quote = “All things will be produced in superior quantity and quality, and with greater ease, ” +
“when each man works at a single occupation, in accordance with his natural gifts, ” +
“and at the right moment, without meddling with anything else. “;

_list = [twain,fowler,poe,plato];
}
}

got it ?

solve module load in flex, getStyle problem

Flex App 直接引用外部 Module 的問題
最近同事遇到一個 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










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





隨著 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();








robotlegs in module, 3 種方法解決

1. http://joelhooks.com/2010/05/02/modular-robotlegs/

裝呢個 addon, 共用同一個 injector,
但不能使用 module loader

min code of this method
http://blog.yoz.sk/examples/ModularRobotlegs/srcview/

2. http://labs.riamore.com/content/robotlegs/examples/dynmodules

可以 dynamic 了,但其實是將 module 的 applicationDomain hardcore 同 mainApp 一樣

3. http://ticore.blogspot.com/2011/10/robotlegs-module-issue.html

一人一個獨立 robotlegs,但要 hack 少少 code 先做到,其實都係 override 下 je 🙂