内容


不可见的 Flash

通过使用不可见的 Flash Player 增强 Web 应用程序

Comments

让我们开始吧

如前所述,您将在本文中学习如何使用 Flash 为 Web 应用程序增添额外的功能。熟悉 JavaScript 是必需的技能,有 ActionScript 使用经验将会很方便。有很多方法编译 ActionScript,其中一些依赖于 Adobe 的商业工具。然而,也可以使用 Adobe 的开源 Flex SDK。本文使用的是 Flex SDK 4.0.0.10485 (Beta 2)。为了运行示例程序,需要 Flash Player 的第 10 版或更高版本。本文中使用的是 10.0.42.34 版本。参考资料 中有下载链接。

Flash 本地存储

许多 Web 应用程序需要在客户端存储状态。有时候这仅仅是某种类型的会话 ID,它可以用来从内存或数据库检索服务器端状态。然而,很多 Web 应用程序出于可伸缩性的考虑故意避免服务器端状态。因此,所有的状态都要存储在客户端。另外,通常希望状态在当前回话结束后仍然保持。

长期以来都使用 HTTP cookies 作为默认的处理方式。然而,HTTP cookies 存在缺陷。在开发人员看来,它难以运用,因为它实际是只是一个 HTTP 头部。更重要的是,存在安全隐患。对于每个请求,HTTP cookies 都会在客户端和服务器端来回传递,因此其中的所有数据都可能被截取。还有,跨站点脚本/伪造会利用这一特定 “窃取 cookie”。如果 cookies 被窃取了,您的相应账号很可能被破坏甚至被他人利用。

但是,HTTP cookies 最常见的缺陷就是大小限制。不同浏览器为每个域分配的 HTTP cookies 的最大容量是不同的。HTTP 规范将其限制为 4 KB,这就是您所能利用的所有空间。如果想在客户端存储大于 4 KB 的数据怎么办呢?如果您考虑过这些问题,您只好无奈地用 Java™Script 编写一个 gip 式的压缩算法,这是一种方法。或者,可以使用其他替代方法,例如,Flash。

本地共享对象

Flash Player 为 Flash 应用程序提供了本地存储空间。默认情况下,每个域有 100 KB 的限制。这样很好:可获得是 HTTP cookies 的 25 倍的空间。还有其他一些重要的差异。其一,Flash 中存储的客户端数据不会发送给服务器,因为它与 HTTP 没有任何关系。当然,如果您愿意,也可以将这些数据发送给服务器。这样做不会有任何障碍。然而,您必须选择发送哪些数据以及以什么方式发送。如果您真的希望客户端和服务器都包含这些数据,这就有点复杂了。但是,这通常更加安全,因为您必须在网络中显式 “公开” 这些数据。

用于存储和检索本地数据的 Flash API 是 SharedObject。其实,Flash 的 SharedObject 概念也可以是远程的,因此,仅存在于客户端的变体也称为本地 SharedObject。该 API 非常简单,它允许使用键-值范式存储和检索任意复杂的对象。清单 1 是存储和检索 SharedObjects 的简单代码。

清单 1. 存储和检索 SharedObjects
package{
    
    import flash.display.Sprite;
    import flash.net.SharedObject;
    
    public class JsHelper extends Sprite{
        private const SO_NAME:String = "helperSo";
        
        private function saveLocal(name:String, value:Object):void{
            var so:SharedObject = SharedObject.getLocal(SO_NAME);
            so.data[name] = value;
            so.flush();
        }
        
        private function readLocal(name:String):Object{
            var so:SharedObject = SharedObject.getLocal(SO_NAME);
            return so.data[name];
        }
    }
}

如果您不熟悉 ActionScript,清单 1 的代码可能看上去有点奇怪。ActionScript 很像 JavaScript;实际上,它来自于 ECMAScript 标准。尽管如此,它具有很多 C++ 和 Java 中常见的特性。变量都是强类型的,还有基于类的继承。清单 1 中的代码是一个扩展了 Sprite 类的类。该类将会编译进 Shockwave Flash (SWF) 文件,此文件会嵌入 Web 页面中。该类有两个方法。其中一个叫 saveLocal。它需要一个名称(只是一个字符串)和一个值(任何类型的对象)。然后它使用 getLocal 工厂方法取得特定的 SharedObject

每个 SharedObject 实例都有一个数据属性,可以看作存储数据的哈希表。这就是 saveLocal 函数的第二行的作用。 最后一行 “清除” SharedObject,或将其保存到磁盘上。这些就是本地存储所要做的一切。如果大量使用 SharedObjects 并接近了 100 KB 的限制,那么您可能需要添加事件监听器。这将能够对 “清除完成” 或 “清除失败” 之类的事件做出响应。

从本地存储中回读同样也很简单,这是通过清单 1 的 readLocal 函数完成的。本例中,已搜索到 SharedObject,并且 name 参数已用于从数据哈希表中搜索已存储的对象。如果哈希表中没有与键值对应的名称,将返回 null。既然已学会如何访问 SharedObjects,现在只需要在 Web 页面上获取 Flash(不可见)— 以及使用 JavaScript 访问它。

使其不可见

默认情况下,清单 1 中的任何函数都无法在 JsHelper 类以外访问。然而,Flash 能够很容易地将函数公开给 JavaScript。需要做的就是使用 Flash 的 ExternalInterface API,如清单 2 所示。

清单 2. 将 JsHelper 函数公开给 JavaScript
package{
    
    import flash.external.ExternalInterface;
    import flash.net.SharedObject;
    
    public class JsHelper extends Sprite{
        private const SO_NAME:String = "helperSo";
        private const SAVE_LOCAL:String = "saveLocal";
        private const READ_LOCAL:String = "readLocal";
        
        public function JsHelper(){
            ExternalInterface.addCallback(SAVE_LOCAL, saveLocal);
            ExternalInterface.addCallback(READ_LOCAL, readLocal);
        }
        // functions omitted for brevity
    }
}

清单 2 简单演示了向清单 1 中的 JsHelper 类添加的内容。主要的注意点是添加了一个显式构造函数。正如您所料,这个构造函数将在创建类的一个实例时得到调用。其中,使用 ExternalInterface API 将两个函数 saveLocalreadLocal 公开给 SWF 所嵌入的所有 Web 页面上的 JavaScript。addCallback 函数的第一个参数是字符串。JavaScript 客户端使用它作为名称识别函数。它与类中的函数名可能不同,但在该例中是相同的。第二个参数是一个函数闭包。与 JavaScript 一样,ActionScript 是函数语言,因此函数是一阶的,可以传递。这就是公开两个函数所需要做的。现在看看用于嵌入和访问 SWF 的 JavaScript。详见清单 3。

清单 3. 嵌入不可见 Flash
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Flash Helper for JavaScript</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
    function writeFlash(){
        var attrs = {id : "JsHelper"};
        swfobject.embedSWF("JsHelper.swf", "flashContainer", "1", "1", "10.0.0", 
            "playerProductInstall.swf", null, null, attrs);
    }
    function save(name, value){
        var helper = document.getElementById("JsHelper");
        helper.saveLocal(name, value);
    }
    function load(name){
        var helper = document.getElementById("JsHelper");
        return helper.readLocal(name);        
    }
</script>
</head>
<body onload="writeFlash()">
    <div id="flashContainer"></div>
</body>
</html>

这段代码中首先要注意的是使用了第三方 JavaScript 库 swfobject。这实际上是嵌入 SWF 的标准库。它是开源的,虽然最初不是由 Adobe 开发的,但是现在由他们维护。它的 embedSWF 函数用于嵌入由清单 2 编译的 SWF。第一个参数是指向 SWF 的 URL。本例中,SWF 来自和 HTML 文件相同的服务器和路径,因此可以使用一个相对 URL。第二个参数是在其中嵌入 SWF 的页面的 HTML 元素的 ID。本例中是 "flashContainer"— 同时您将注意到 HTML 文档的主体部分有一个 HTML div 使用该 ID。

接下来的两个 embedSWF 参数是 SWF 的高度和宽度。在本例中,两者的值都是 1。这意味着 SWF 的高度和宽度都为一个像素。这就是不可见的 SWF!下一个参数是 Flash 的最低版本,您的 JavaScript 将检查该参数。如果 Flash 已安装在浏览器上,但其版本号比传递给 embedSWF 的版本号更低,那么 embedSWF 将使用下一个参数 "playerProductInstall.swf"。这是指向另一个 SWF 的 URL,它提示用户安装最新版本的 Flash。对于不可见 Flash,这其实没什么影响 — “您需要安装最新版本 Flash” 的 SWF 其实也不可见(当然,也是 1x1 像素)。传给 embedSWF 的最后一个参数很重要。这是一个包含各种可选参数的属性对象。其中一个可选参数是 SWF 的 ID。对于和本文类似的用例,该参数是不可选的 — 这至关重要!它将会提供给 SWF 一个 HTML ID,而这将会在使用 JavaScript 进行编程访问时用到。

现在看看 清单 3 中的两个 JavaScript 函数。二者很相似。都使用熟悉的 getElementById 函数获取 SWF 的引用。请注意它们使用的是 writeFlash 函数中的 attrs 对象指定的 ID。取得 SWF 引用后,可以直接调用 清单 2 中公开的 ActionScript 函数。此处的关键是,JavaScript 中使用的函数名必须匹配 ExternalInterface.addCallback 函数中公开的名称或者是传给 addCallback 的第一个参数。

对于 save 方法,传递了一个 JavaScript 对象作为 value 参数。这可以是任意对象,甚至可以具有一个深度嵌套的结构。然而,只会传递对象的数据。如果该对象包含函数,那么将不会被传递。请注意 load 方法将返回所有来自 SWF 的内容。是什么内容?答案很简单:您发送的任何内容。如果您存储的是数字或字符串等标量值,那么将返回这些内容。如果存储的是复杂的对象,那么将返回该 JavaScript 对象 — 不需要解析或其它操作。一个例外是假如对象包含函数,那么肯定不会被序列化并保存。因此只会返回对象中的数据,而不是其他内容。以上总结了使用 Flash 进行本地存储所需掌握的全部内容。在学习使用 Flash 操作跨域 Ajax 之前,了解一些可作为替代选择的本地存储方法。

其他的本地存储选择

我提到 Flash 是面向客户端存储的一个有吸引力的选择,可以代替 HTTP cookies。尽管如此,还有两项 Web 技术采用了与 Flash SharedObjects 一样的范式。事实上,长期以来各种浏览器都提供相似的 API。但各浏览器的 API 不尽相同。因此您可以先了解一下各个浏览器,然后使用特定的 API。Flash 提供了一致的替代方案。本文中开发的代码几乎可以运行于所有浏览器:Internet Explorer 6 和 Firefox 2,以及这些浏览器的最新版本。更新的版本提供一致的解决方案。HTML 5 规范包含一个 localStorage API。主流浏览器的最新版本都实现了这个 API,包括 IE 8 和 Firefox 3.5。因此如果只担心浏览器的话,localStorage 将是一个可用的选项。如果担心旧的浏览器(IE 6、IE 7 等等),那么使用 Flash 可能会更容易。现在,看看 Flash 能够实现的新功能,跨域 Ajax。

跨域 Ajax

Ajax 目前在 Web 应用程序中无所不在;它是任何 Web 应用程序中都假设会有的部分。Ajax 的一个主要不足就是为人诟病的同源策略。如果 Web 页面由 a.com 提供,您只能向 a.com 调用 Ajax(更精确地说是 XMLHttpRequest)。例如,您无法调用 b.com。如果您的公司拥有 a.com 和 b.com,那就没有影响;浏览器不会关心这些。但是对 Flash 应用程序却不是这样。

Flash 应用程序可以获得许可,并对提供服务的域以外的域进行调用。这可以用跨域策略文件完成。默认情况下,Flash 运行时将在服务器的文档根目录搜索策略文件:http://<your domain>/crossdomain.xml。例如,清单 4 是 Twitter 搜索域的策略文件,http://search.twitter.com/crossdomain.xml。

清单 4. Twitter 搜索域的策略文件
<!DOCTYPE cross-domain-policy 
SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>
    <allow-access-from domain="*" />
</cross-domain-policy>

这是个相当好的策略文件。它允许所有(如 “*” 通配符所示)域对 SWF 进行访问。因此所有 Flash 应用程序都可以调用 Twitter 的搜索 API。在大多数具有公共 API 的 Web 网站上像这样的策略文件很常见。尽管如此,有些网站使用更加严格的策略,只允许自己所有的其他域或合作伙伴的域进行访问。有了策略文件,就可以细粒度地精确控制谁能够调用服务器,谁不能。看看如何将同样的功能扩展到 Ajax 应用程序中。

跨域

如果您的应用程序只需要调用特定的域,可以编写 ActionScript 代码调用该域,然后在应用程序中使用 ExternalInterface 将其公开给 JavaScript。然而,我将采取一种更好的方法,并试图用更一般化的方式进行公开。因此,清单 5 中,将使用一个实用类从 ActionScript 中调用任意域。

清单 5. 跨域工具 SWF
package{
    
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.external.ExternalInterface;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    
    public class JsHelper extends Sprite{
        private const SEND_REQUEST:String = "sendRequest";
        
        public function JsHelper(){
            ExternalInterface.addCallback(SEND_REQUEST, sendRequest);
        }

        public function sendRequest(url:String, handlerName:String, 
                method:String="GET", content:Object=null, 
                headers:Object=null):void{
            var loader:URLLoader = new URLLoader();
            var request:URLRequest = new URLRequest(url);
            if (method){
                request.method = method;
            }
            if (headers){
                for each (var name:String in headers){
                    request.requestHeaders[name] = headers[name];
                }
            }
            if (content){
                request.data = content;
            }
            loader.addEventListener(Event.COMPLETE, 
                function(e:Event):void{
                    ExternalInterface.call(handlerName, loader.data);
            });
            loader.load(request);
        }
    }
}

该类使用 ExternalInterfacesendRequest 函数公开给 JavaScript。这在对象的构造函数中完成,和之前 清单 2 中的例子一样。sendRequest 函数有点复杂:它有两个必需的参数。第一个参数是需要调用的 URL。这是一个完整的 URL 字符串,包含所有请求参数。下一个是 JavaScript 函数名,此函数在获得服务器响应后由 Flash 调用。和典型的 Ajax 一样,Flash 对远程服务器进行异步调用,主 UI 线程在等待远程服务器响应时不会停止。因此,与 Ajax 一样,需要编写 callback 函数,它将在接收到服务器响应后被调用。将其作为字符串传递给 Flash,但它必须与函数名完全相同。

sendRequest 函数也有三个可选参数。ActionScript 允许有可选参数,但必须有可用的缺省值。第一个是使用 HTTP 方法,通常是 GETPOST。本文中,我将它默认为 GET,但是可以很容易地用 POST 将其替换,这取决于远程服务器的需要。下一个可选参数叫做 content。这是一个一般数据对象,包含有需要发送给远程服务器的任意名称-值对。向远程服务器发送数据时需要用到。最后一个可选参数是另一个用于头部的一般数据对象。它用于将定制 HTTP 头部设置为作为远程服务器调用的一部分发送。

然后代码获取所有这些参数,使用 Flash URLRequest 对象构造 HTTP 请求,然后使用 Flash URLLoader 类发送请求。事件监听器会绑定到 URLLoader 以便能处理返回的响应。这里创建一个闭包,如同在 JavaScript 中一样,创建一个匿名内联函数,将在 COMPLETE 事件被加载程序触发后调用。该闭包将只使用 ExternalInterface 来调用名称被传递给 sendRequest 的函数。它将来自远程服务器的所有数据传递给该函数。这显然比使用本地存储复杂一点。看看清单 6 中调用 Twitter 搜索的例子。

清单 6. 调用 Twitter 搜索
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Calling Twitter from the client</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
    function writeFlash(){
        var attrs = {id : "JsHelper"};
        swfobject.embedSWF("JsHelper.swf", "flashContainer", "1", "1", "10.0.0",
            "playerProductInstall.swf", null, null, attrs);
    }
    function search(){
        var keyword = document.getElementById("keyword").value;
        var helper = document.getElementById("JsHelper");
        helper.sendRequest("http://search.twitter.com/search.json?q=" + keyword, 
"showResults");
    }
    function showResults(responseStr){
        var response = eval("(" + responseStr +")");
        var results = response.results;
        alert("#Results = " + results.length);
    }
</script>
</head>
<body onload="writeFlash()">
    <div id="flashContainer"></div>
    <div id="inputDiv">
        <label for="id">Search Twitter</label>
        <input type="text" id="keyword" name="keyword"/>
        <input type="button" value="Search Twitter" onclick="search()"/>
    </div>
</body>
</html>

此处的代码与 清单 3 中的 HTML/JS 类似。再次使用了 swfobject 库将 SWF 嵌入页面中。代码中有个简单的表单,提示用户输入在 Twitter 中搜索的关键词。单击 Search 按钮后调用搜索函数。它从输入字段取得关键词并用于创建需要调用的 URL。然后传递给 JsHelper SWF,就像在 清单 3 中那样,通过使用 ID 取得 SWF 的引用,然后使用 ExternalInterface 直接调用公开的函数。传递一个 URL 和命名回调函数的字符串。数据从 Twitter 返回后,传递到 showResults 函数。这是一个 JSON 字符串,因为它是从 Twitter 返回的,因此可以简单地用 JavaScript 的 eval 函数将其转换为一个对象。本例中,只是简单显示了从 Twitter 中返回的结果数。尽管如此,可以方便地在页面中创建 HTML 来列举每个结果;只是些样板 DOM 代码。就是这样,您学习了借助 Flash 的跨域 Ajax 调用的方法。与使用 Flash 进行本地储存一样,还有其他一些替代方法。

跨域替代方案

Flash 长期以来被用于实现跨域调用,就像它可以实现本地存储一样。与本地存储的用例一样,一些 Web 浏览器实现了自己的方式,后来也出现了一些标准化工作。与本地存储相比,进展并不顺利。尽管目前已有跨域 Ajax 标准,却未被 IE 8 采用。相反,IE 8 以一种特有的方式处理跨域 Ajax。这种特有方式的实现在 IE 8 中是全新的,在旧版本 IE 中没有。Flash 再一次展示了一个可以跨所有浏览器工作的模型。

最后,如果您研究过跨域 Ajax,您肯定会发现其他方法,而不需要 Flash 或任何其他浏览器特性。这项技术,通常称为 JSONP(含有 Padding 的 JSON )或者是 “动态脚本标记”,它利用了浏览器不对 JavaScript 源文件强制执行同源策略的特性。因此创建了一个脚本标记,它的源指向要调用的 URL,并插入到 DOM 中。该 URL 通常包含一个 “回调” 参数,它是您想要调用的回调函数的名称。服务器然后将数据与函数名封装,以便函数得到调用,且来自服务器的数据被传递给它。这项技术在 Internet 上广泛运用(例如,前面展示的 Twitter 搜索 API 就支持该技术),但有相应的安全风险 — 因此,标准的、安全的做法,就应该像 Flash 那样。

结束语

本文介绍了如何使用 Flash 扩展 Web 应用程序的功能。您可以极大地增加应用程序所能访问的本地存储容量。这使您能够在客户端缓存大量数据。您还可以使用 Flash 从 Web 应用程序进行跨域 Ajax 调用。这里只是粗略体验一下 Flash 的功能。如果您觉得其他功能对您也有所帮助,可以使用同样的技术:使 Flash 不可见并用 ExternalInterface 公开其函数。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Open source
ArticleID=499574
ArticleTitle=不可见的 Flash
publish-date=07082010