有一定开发经验的程序员都习惯把一些可配置的信息存放在配置文件中,当需要改变配置内容时,只需要修改配置文件即可,不需要重新编译发布程序。在.NET、Java等语言中实现这种逻辑很简单,通过构造静态类或单例模式,在构造函数中读取配置文件内容,并公开其内容属性使其他代码可以直接访问属性即可。但在Flex中实现这种逻辑却遇到麻烦。
首先写好读取配置信息的方法:
01 |
private function loadConfig(): void |
03 |
var urlLoader:URLLoader = new URLLoader(); |
04 |
urlLoader.addEventListener(Event.COMPLETE, completeHandler); |
05 |
urlLoader.load( new URLRequest( "config.xml" )); |
08 |
private function completeHandler(e:Event): void |
10 |
var urlLoader:URLLoader = e.target as URLLoader; |
11 |
Config.getInstance().setConfig( new XML(urlLoader.data)); |
方案1:在Application的creationComplete事件中读取配置信息
1 |
protected function creationCompleteHandler(event:FlexEvent): void |
4 |
this .title = Config.getInstance().appTitle; |
我在《Flex中实现模块顺序执行》文中指出了Flex的异步处理机制,上面的代码看上去好像是顺序执行,实际上设置title属性时不能保证loadConfig已经执行完毕,特别地当loadConfig方法是读取远程服务器的配置信息,实际运行时会报错。
方案2:在Application的initialize事件中读取配置信息
1 |
protected function initializeHandler(event:FlexEvent): void |
6 |
protected function creationCompleteHandler(event:FlexEvent): void |
8 |
this .title = Config.getInstance().appTitle; |
虽然看上去很美,initialize事件比creationComplete事件先触发,但跟方案1一样,保证不了loadConfig方法执行完毕后获取Config内容。不过如果代码中所有调用Config内容的地方均不是在initialize、creationComplete等系统初始化的事件中指定,而是通过用户操作触发的,则可以勉强适用。
方案3:在Preloader的initComplete事件中读取配置信息
代码参考《flex 初始化完成前加载自定义XML数据》,我这里就不贴了。
这种也是我最初采取的方法,但发现偶尔会报错。后来经过调试,我在一些组件的initialize事件中调用了Config内容,而preloader的initComplete事件是在所有组件initialize完毕后才派发。当然我把调用Config内容从initialize事件移到applicationComplete事件总可以了吧?嗯~~报错是少了些,但问题还是有,终究问题还是跟上面的一样。
到这里难道就得改变调用Config内容的习惯了?经过分析Flex的启动过程,改良方案3,终于找到解决的方案。
方案4:截获preloader的PRELOADER_DOC_FRAME_READY事件后读取配置信息
01 |
import com.adobe.cairngorm.task.SequenceTask; |
02 |
import com.adobe.cairngorm.task.TaskEvent; |
03 |
import com.sdgis.external.assets.ConfigTask; |
05 |
import flash.display.Sprite; |
07 |
import mx.events.FlexEvent; |
08 |
import mx.preloaders.SparkDownloadProgressBar; |
10 |
public class SparkPreloader extends SparkDownloadProgressBar |
12 |
private var _preloader:Sprite; |
13 |
private var suspend: Boolean ; |
15 |
public function SparkPreloader() |
20 |
override public function set preloader(value:Sprite): void |
23 |
super .preloader = value; |
24 |
value.addEventListener(FlexEvent.PRELOADER_DOC_FRAME_READY, preloaderDocFrameReadyHandler, false , int .MAX_VALUE); |
27 |
private function preloaderDocFrameReadyHandler(event:FlexEvent): void |
30 |
event.stopImmediatePropagation(); |
38 |
private function startConfiguation(): void |
40 |
var task:SequenceTask = new SequenceTask(); |
41 |
task.addChild( new ConfigTask()); |
42 |
task.addEventListener(TaskEvent.TASK_COMPLETE, taskCompleteHandler); |
46 |
private function taskCompleteHandler(event:TaskEvent): void |
48 |
_preloader.removeEventListener(FlexEvent.PRELOADER_DOC_FRAME_READY, preloaderDocFrameReadyHandler); |
49 |
_preloader.dispatchEvent( new FlexEvent(FlexEvent.PRELOADER_DOC_FRAME_READY)); |
Flex的启动过程参考《浅析Flex启动过程》
简单说就是Flex归根结底还是swf,跟flash一样有时间轴和帧,只是Flex通常情况下只有两帧, 平时我们看到的loading界面就是第一帧的preloader,当下载完成后跳转到第二帧初始化application,并派发出PRELOADER_DOC_FRAME_READY事件。因为SystemManager中就是监听到PRELOADER_DOC_FRAME_READY事件后执行第二帧的初始化工作,而且SystemManager我们很难修改。
第一帧是preloader: Stop();创建preloader;加载RTLs;RTLs加载完毕,preloader派发FlexEvent.PRELOADER_DOC_FRAME_READY事件;创建Timer,在100ms 后preloader再派发Event.COMPLETE事件;执行nextFrame()函数。为了能在application初始化前读取配置内容,必须监听PRELOADER_DOC_FRAME_READY事件,并且必须是最高级别的监听,且event.stopImmediatePropagation();再来加载配置,直到配置读取完成后,移除监听器并主动派发PRELOADER_DOC_FRAME_READY事件事件,让SystemManager继续工作。也可以override protected function initCompleteHandler(event:Event):void 推迟派发Event.COMPLETE事件
第二帧才是application。
代码中我借用了我《Flex中实现模块顺序执行》文中方法,如果有多个配置需要读取,或者还有其他初始化工作,则在通过addChild添加。因为PRELOADER_DOC_FRAME_READY事件是不断派发出来的,故我加上suspend标识防止重复处理,