[译] 完全自定义的预加载器

Flex Application Bootstrapping - Totally Custom Preloader

[原文链接: Flex Application Bootstrapping - Totally Custom Preloader 作者: Vladimir Tsvetkov ]

[原创翻译链接: http://www.smithfox.com/?e=83, 转载请保留此声明, 谢谢 ]

这篇文章所写的不是说怎么继承DownloadProgressBar类, 而是说怎么用Flash组件实现一个完全单独的预加载器.

Flex程序最让客户抓狂的是开始漫长加载的过程, 简单地继承DownloadProgressBar只能做到将枯燥的进度条换得更好看一点, 没有解决实质问题. 让我们看一下还有什么好的办法能使Felx的加载体验变得更好.

这篇文章写于2年多前, 直到现在, 大家仍然对此感兴趣, 在过去的两年技术发生了许多的变化 - Flex 3 变成了 Flex 4, 等等.

所以我觉得, 这篇文章需要一个能运行的例子来演示这个方法仍然是有效的.

下载FLEX 4样例代码并且导入到FLASH BUILDER

我不准备定义和讨论什么是RIA, 不过我还是要在此强调一个富互联网应用(rich internet applications, 以下简称RIA)的非常重要的概念, 那就是节约时间. 时间就是金钱(或是开心和娱乐的机会), 拥有更多的时间那就拥有更多的金钱 (或是有更多的机会来开心娱乐), 那就是所谓 '互联网应用' 中 ''的来源. Flex程序总是在加载而让你白白地耐心地等, 却不提供有点价值的东西来补偿你一下, 就象这首歌. 这是为什么RIA对什么时候应该加载什么内容有非常具体的需求和严格的限制的原因之一. 功能和数据按时间被打散, 再按具体的应用程序的目的重新排序.

具体需求

BombaySapphire.com - 这是个典型的例子. 这个程序的加载过程的需求总结如下图, 灰色的bars描绘了Bombay程序的不同的可视阶段的生命周期, 红色部分表示加载不同阶段的数据所需的时间, 包括应用程序自身. 如果你想知道LDA是什么缩写, 它表示 "法定饮酒年龄验证" (译注:因为该网站是一个展示酒的网站, 所以网站开始就需要LDA). Schematic Application Timeline

不是让上图中的所有灰色bars部分成为铁板一块, Adobe的家伙们为我们提供了 "2侦模型", 第一侦完全是用于预加载, 而第二侦才是为了实际的应用程序. Flex Application Two Frames Model

在本文的其余部分, 我将描述如何设置的不仅仅是一个定制皮肤的预加载器, 而是一个完全自定义预加载器. 如果你对怎么为Flex默认的预加载器 - mx.preloaders.DownloadProgressBar 定制皮肤 - 你应该看看Jesse Warden同学的有关这方面的好文章. 但在开始实现IPreloaderDisplay接口前, 我们需要看一下Flex SystemManager, 了解一下这个类的职责和熟悉一些预加载器发布的事件.

The Flex SystemManager

  1. SystemManager 是Flex程序第一个创建的类.
  2. 它负责创建预加载器并且处理相关事件.
  3. SystemManager 还负责一旦应用程序的swf被完全加载后, 将程序推进到第二侦. 除了这些和预加载器直接相关的职责外, SystemManager 还负责处理下面事情:
  4. 管理应用程序窗口, 当窗口大小发生变化时负责发送事件.
  5. 维护tooltips, 光标和弹出窗口.
  6. 精确地捕获键盘和鼠标活动.
  7. 还有其它一些事情, 不过我没有时间去做更多的了解.

预加载器的最重要的事件

  1. ProgressEvent.PROGRESS 这是一个典型的进展性事件 - 它表示应用程序的某个部分已经加载完成.
  2. FlexEvent.INIT_PROGRESS 当应用程序完成了一个初始化阶段后会发布这个事件 - 一旦应用程序的swf完全被加载, SystemManager 将播放头推进到下一侦. 通常你需要控制这个特定时刻.在下面的例子中我将会展示实现控制的一种方法 - 基本上你需要捕获这个事件, 存储它, 使其pending, 并且暂停这类事件的分发 - 以后你再重新发布你所存储的这些pending的事件.
  3. FlexEvent.INIT_COMPLETE 这是预加载器发布的最后一个事件. 下一步预加载器将会发布Event.COMPLETE事件, 那表示SystemManager已经准备好要将预加载器删除, 并且将应用程序加入到显示列表中.

实现IPreloaderDisplay接口

我想此处是本文展现实现IPreloaderDisplay接口的例子的好地方. 让我们一起浏览一下这个接口的部分内容 - 一个背景和舞台相关的属性. 在IPreloaderDisplay中定义了一些get和set函数, 这允许我用直接声明public变量再加一个[Bindable]的方式来实现它们, 编译器会根据需要产生get和set函数.

[Bindable] public var backgroundAlpha : Number;
[Bindable] public var backgroundColor : uint;
[Bindable] public var backgroundImage : Object;
[Bindable] public var backgroundSize : String;
[Bindable] public var stageHeight : Number;
[Bindable] public var stageWidth : Number;

为预加载器增加事件监听的最方便的地方是接口的set preloader()方法.

private var _preloader : Sprite;
public function set preloader(value : Sprite) : void
{
    _preloader = value;
 
    _preloader.addEventListener(ProgressEvent.PROGRESS, onPreloaderProgress);
    _preloader.addEventListener(FlexEvent.INIT_PROGRESS, onPreloaderInitProgress,
        false, int.MAX_VALUE);
    _preloader.addEventListener(FlexEvent.INIT_COMPLETE, onPreloaderComplete);
}

最后一个我们需要实现的方法是initialize(). 该方法在预加载器作为孩子被加入后就会被调用. 这是一个配置你的预加载器的入口. 比如我用draw API来画一个背景, 用自定义图案填充它, 或是增加一些可视的子组件, 彻底定制预加载器的外观.

public function initialize() : void
{
    // e.g. draw a background
    ...
    // or add visual children to the totally custom preloader
    ...
}

让我们看一下我怎么处理上面所提到的FlexEvent.INIT_PROGRESS 事件:

private var readyToAdvanceToSecondFrame : Boolean = false;
private var pendingInitProgressEvent : FlexEvent;
public function onPreloaderInitProgress(e : FlexEvent) : void
{
    if (readyToAdvanceToSecondFrame)
    {
        pendingInitProgressEvent = e.clone();
        e.stopImmediatePropagation();
    }
}

我们需要注意我是怎么定阅FlexEvent.INIT_PROGRESS事件的:

_preloader.addEventListener(FlexEvent.INIT_PROGRESS, onPreloaderInitProgress,
    false, int.MAX_VALUE);

我将该事件的处理句柄的优先级设置成最大值, 事件一旦发布, 这个句柄就可以第一个处理这个事件. (译注:一个事件可以被监听多次, 每次都可以有不同的处理句柄, 优先级高的则先处理事件)

重复的二进制类定义

这可能需要单独的一篇文章来说, 但我还是要在此分享一下. 你最终很可能用一个Flash组件来作为你的预加载器. 这样方案的原因是清晰. 如果你的预加载器在主程序加载前需要处理许多事情, 最好用一个单独的组件. 如果你选择将其放在应用程序内, 首先会增加程序文件大小,其次你需要等待全部的程序加载完毕后才能使用它. 这另外带来一个问题: 用Flash还是Flex来实现它. 用Flex来实现的话, 你将会多了一个实际的预加载器的预加载器, 这是行不通的, 除非你能找到某种方法在Flex程序中去加载和使用另一个Flex程序. 如果你做到了, 请在此留言和大家共享一下. 另外还有一件我们不能仰赖的事情: Flex框架是否已经被加载 - 框架的大小可能比你的预加载器要大很多. 好的, 还是让我们假设最终我们还是用Flash来做预加载器. 当我做做这个时又遇到了一个非常微妙的问题 - 二进制类的重复定义. 下面的图片说明了这个问题:Flex Application Domains

这个Bombay程序域正是主程序代码所在的域. 如果这些代码持有了Flash组件的具体类的引用(这儿就是LDA域), 我们将会遇到麻烦, 因为可能上层的域定义了相同的类 - 在本例中有问题的重复就是Tweener类. 这样的重复会使程序行为变得奇怪和捉摸不定. 一种避免这个尴尬情况的方法是在编译Flash预加载器组件前用 exclude 命令. Flash 8 IDE 提供了这个功能, 但很不幸, 在新的Flash IDE中已经没有 exclude 命令了. 另外种方法是避免上面的域引用Flash组件的具体类. 你所要做的就是将预加载组件类封装在一个interface Facade之内, 于是Flash组件将实现这个Facade interface, 并且上层的域也无需知道这个Flash组件的具体类型了.

一个大概的实现

我只是写了一个大概的实现, 你可以在这个基础上改动.

IPreloaderContentFacade.as :

package preloader
{
import flash.events.IEventDispatcher;
 
public interface IPreloaderContentFacade extends IEventDispatcher
{
    function setApplicationProgress(bytesLoaded : Number, bytesTotal : Number) : void;
}
}

TotallyCustomPreloader.as:

package preloader
{
import flash.display.Loader;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.ProgressEvent;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
 
import mx.events.FlexEvent;
import mx.preloaders.IPreloaderDisplay;
 
public class TotallyCustomPreloader extends MovieClip implements IPreloaderDisplay
{
    public static const PRELOADER_CONTENT_URL : String = "preloader_content.swf";
 
    public var backgroundAlpha : Number;
    public var backgroundColor : uint;
    public var backgroundImage : Object;
    public var backgroundSize : String;
    public var stageHeight : Number;
    public var stageWidth : Number;
 
    private var _preloader : Sprite;
    public function set preloader(value : Sprite) : void
    {
        _preloader = value;
 
        _preloader.addEventListener(ProgressEvent.PROGRESS, onPreloaderProgress);
        _preloader.addEventListener(FlexEvent.INIT_PROGRESS, onPreloaderInitProgress,
            false, int.MAX_VALUE);
        _preloader.addEventListener(FlexEvent.INIT_COMPLETE, onPreloaderComplete);
    }
 
    private var preloaderContentFacade : IPreloaderContentFacade;
 
    public function TotallyCustomPreloader()
    {
        super();
        loadPreloaderContent();
    }
 
    public function initialize() : void
    {
    }
 
    private var loader : Loader;
    private function loadPreloaderContent() : void
    {
        loader = new Loader();
	loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderComplete);
 
	var loaderContext : LoaderContext = new LoaderContext(false, new ApplicationDomain());
	var request : URLRequest = new URLRequest(PRELOADER_CONTENT_URL);
	loader.load(request, loaderContext);
    }
 
    private function onLoaderComplete(e : Event) : void
    {
        preloaderContentFacade = IPreloaderContentFacade(loader.content);
        addChild(MovieClip(preloaderContentFacade));
    }
 
    private function onPreloaderProgress(e : ProgressEvent) : void
    {
        if (preloaderContentFacade != null)
        {
            preloaderContentFacade.setApplicationProgress(e.bytesLoaded, e.bytesTotal);
        }
    }
 
    private var readyToAdvanceToSecondFrame : Boolean = false;
    private var pendingInitProgressEvent : FlexEvent;
    private function onPreloaderInitProgress(e : FlexEvent) : void
    {
        if (!readyToAdvanceToSecondFrame)
        {
            pendingInitProgressEvent = e.clone();
            e.stopImmediatePropagation();
        }
    }
 
    private function onPreloaderComplete(e : FlexEvent) : void
    {
        readyToAdvanceToSecondFrame = true;
        if (pendingInitProgressEvent != null)
        {
            dispatchEvent(pendingInitProgressEvent);
        }
        dispatchEvent(new Event(Event.COMPLETE));
    }
}
}

这个大概实现省略了真正实现所需的许多事情 - 一切都视你应用程序的特定需求而定. 但我认为上面的代码已经足够作为一个具体实现的指南.

[原创翻译链接: http://www.smithfox.com/?e=83, 转载请保留此声明, 谢谢 ]

smithfox | Tuesday 22 February 2011 at 08:47 am | | UI        | Used tags: , , , ,

No comments

(optional field)
(optional field)
为阻止垃圾广告, 请在提交评论前, 回答一个简单问题(Please answer an simple question)
Remember personal info?
Notify
Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.