HTML 5 是一项被大肆宣扬的技术,但是它实至名归。它有望成为一个技术引爆点,将桌面应用程序功能引向浏览器。它不仅适用于传统浏览器,甚至也针对移动浏览器。更好的是,最流行的移动浏览器已经采纳和实现 HTML 5 规范的很多重要部分。在这个五部分的系列中,我们将详细了解几个新技术,它们都是 HTML 5 的一部分,可以大大影响移动 Web 应用程序开发。在每一部分中,都将开发一个可以工作的移动 Web 应用程序,展示一个可以用于现代移动 Web 浏览器(比如 iPhone 和基于 Android 的设备上的浏览器)的 HTML 5 特性。
在本文中,您将使用最新的 Web 技术开发 Web 应用程序。这里的大部分代码只是 HTML、JavaScript 和 CSS — 所有 Web 开发人员的核心技术。所需的最重要的工具是用于进行测试的浏览器。本文大部分代码将在最新桌面浏览器上运行,但也有一些例外,我们将在文章中进行说明。当然,您也必须在移动浏览器上测试,为此,您需要最新的 iPhone 和 Android SDKs。本文将使用 iPhone SDK 3.1.3 和 Android SDK 2.1。本文的样例还将使用一个代理服务器来从浏览器访问远程服务。这个代理服务器是一个简单的 Java™ servlet,但也可以使用以 PHP、Ruby 以及其他语言编写的代理轻松替换。参见 参考资料 获取链接。
对于大多数开发人员来说,多线程或并发编程并不新鲜。但是,JavaScript 并不是一种支持并发编程的语言。JavaScript 的创建者认为,对于 JavaScript 这样旨在 Web 页面上执行简单任务的语言来说,并发编程容易出现问题,而且没有必要。然而,由于 Web 页面已经发展成为 Web 应用程序,使用 JavaScript 完成的任务的复杂程度已经大大增加,向 JavaScript 提出了与其他语言同等的要求。与此同时,使用其他支持并发编程的语言工作的开发人员经常面临伴随线程和 mutexes 这样的并发原语而来的超高复杂性的困扰。实际上,最近像 Scala、Clojure 和 F# 这样的几种新语言已经发展,它们都有可能简化并发性。
Web Worker 规范不只是向 JavaScript 和 Web 浏览器添加并发性,而且是以一种智慧的方式添加,这种方式将增加开发人员的能力,但不会向他们提供一种会导致问题的工具。 例如,多年来,桌面应用程序开发人员一直在使用多线程来支持他们的应用程序访问多个 I/O 资源,以避免在等待这些资源时冻结 UI。然而,当这些多线程更改共享的资源(包括 UI)时,这样的应用程序通常会出现问题,因为这种行为可能会导致应用程序冻结或崩溃。有了 Web Workers,这种情况就不会发生。衍生线程不能访问主 UI 线程访问的资源。事实上,衍生线程中的代码甚至不能与主 UI 线程执行的代码位于同一个文件中。
您甚至必须提供相应的外部文件作为构造函数的一部分,如 清单 1 所示。
这个进程使用三个资源:
- 在主线程上执行的 Web 页面 JavaScript(我称其为页面脚本)。
- Worker 对象,这是用于执行 Web Worker 函数的 JavaScript 对象。
- 将在新衍生的线程上执行的脚本。我称其为 Worker 脚本。
让我们首先看看 清单 1 中的页面脚本。
var worker = new Worker("worker.js"); worker.onmessage = function(message){ // do stuff }; worker.postMessage(someDataToDoStuffWith); |
在 清单 1 中,您可以看到使用 Web Workers 的三个基本步骤。首先,您创建一个 Worker 对象并向它传递将在新线程中执行的脚本的 URL。Worker 将执行的所有代码都必须包含在一个 Worker 脚本中,该脚本的 URL 将被传递到这个 Worker 的构造函数中。这个 Worker 脚本的 URL 受到浏览器的同源策略的限制 — 它必须来自加载这个页面的同一个域,该页面已加载正在创建这个 Web Worker 的页面脚本。
下一步是使用 onmessage
函数指定一个回调处理器函数。这个回调函数将在该 Worker 脚本执行后调用。message
是从该 Worker 脚本返回的数据,您可以随意处理该消息。回调函数在主线程上执行,因此它能访问 DOM。Worker 脚本在一个不同的线程内运行且不能访问 DOM,因此,您需要将来自这个 Worker 脚本的数据返回主线程,在那里,您可以安全地修改 DOM 来更新您的应用程序的 UI。这是 Web Workers 的无共享设计的关键特性。
清单 1 中的最后一行展示如何通过调用 Worker 的 postMessage
函数来启动它。这里,您传递一条消息(重申一下,它只是数据)给 Worker。当然,postMessage
是一个异步函数;您调用它,它就立即返回。
现在,检查这个 Worker 脚本。清单 2 中的代码是来自 清单 1 的 worker.js
文件的内容。
importScripts("utils.js"); var workerState = {}; onmessage = function(message){ workerState = message.data; // do stuff with the message postMessage({responseXml: this.responseText}); } |
可以看到,这个 Worker 脚本拥有自己的 onmessage
函数。该函数在您从主线程调用 postMessage
时调用。从页面脚本传来的数据被传递到 message
对象中的 postMessage
函数。您通过检索 message
对象的 data
属性来访问该数据。当您处理完 Worker 脚本中的数据时,调用 postMessage
函数将数据返回主线程。主线程也可以通过访问它接收到的消息的 data 属性来访问该数据。
至此,您已经见识了 Web Workers 的这个简单、但强大的语义。接下来,您将了解如何应用这个语义来加速移动 Web 应用程序。在此之前,有必要先讨论一下设备支持。毕竟,这些是移动 Web 应用程序,且处理不同浏览器之间的功能的区别对于移动 Web 应用程序开发很重要。
从 Android 2.0 开始,Android 浏览器就拥有了对 HTML 5 Web Worker 规范的全面支持。在撰写本文之时,最新的 Android 设备(包括非常流行的 Motorola Droid)已配置了 Android 2.1。另外,此特性在运行 Maemo 操作系统的 Nokia 设备上的 Mozilla Fennec 浏览器以及 Windows Mobile 设备上受到完全支持。这里需要引起注意的遗漏是 iPhone。iPhone OS 3.1.3 和 3.2 版(在 iPad 上运行的 OS 的版本)并不支持 Web Workers。但是,此特性已在 Safari 上受到支持,因此,此特性在运行在 iPhone 上的 Mobile Safari 浏览器上出现应该只是一个时间问题。鉴于 iPhone 的主导地位(尤其是在美国),最好不要依赖 Web Workers 的存在,且不要只在您检测到它们的存在时才使用它们来增强您的移动 Web 应用程序。意识到这一点后,我们来看看如何使用 Web Workers 来加速您的移动 Web 应用程序。
智能手机浏览器上的 Web Worker 支持很不错,而且一直在不断改进。这就提出了一个问题:什么时候需要在移动 Web 应用程序中使用 Workers?答案很简单:需要完成耗时的任务的任何时候。有些示例展示了如何将 Workers 用于执行密集的数学计算,比如计算 1 万位数的圆周率。很可能您永远也不需要在 Web 应用程序上执行这样一个计算,在移动 Web 应用程序上执行这种计算的几率则更小。但是,从远程资源检索数据则相当常见,这也是本文示例的关注点。
在这个示例中,您将从 eBay 检索一个 Daily Deals(每天都在变化的交易)列表。这个交易列表包含关于每笔交易的简短信息。更详细的信息可以通过使用 eBay 的 Shopping API 获取。当用户浏览这个交易列表选择感兴趣的商品时,您将使用 Web Workers 来预取这个附加信息。要从您的 Web 应用程序访问所有这些 eBay 数据,您需要通过使用一个泛型代理(generic proxy)来处理浏览器的同源策略。一个简单的 Java servlet 将用于这个代理,它包含在本文的代码中,但不在这里单独展示。相反,我们将把注意力集中在处理 Web Workers 的代码上。清单 3 展示了这个交易应用程序的基本 HTML 页面。
|
可以看出,这是一段非常简单的 HTML;它只是一个 shell。您使用 JavaScript 检索数据并生成 UI。这是移动 Web 应用程序的优化设计,因为它允许将所有代码和静态标记缓存到设备上,用户只需等待来自服务器的数据。注意,在 清单 3 中,一旦那个 body 加载,您就调用 loadDeals
函数,在那里,您将加载 清单 4 中的应用程序的初始数据。
var deals = []; var sections = []; var dealDetails = {}; var dealsUrl = ""; function loadDeals(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if (this.readyState == 4 && this.status == 200){ var i = 0; var j = 0; var dealsXml = this.responseXML.firstChild; var childNode = {}; for (i=0; i< dealsXml.childNodes.length;i++){ childNode = dealsXml.childNodes.item(i); switch(childNode.localName){ case 'Item': deals.push(parseDeal(childNode)); break; case "MoreDeals": for (j=0;j |
清单 4 展示了 loadDeals
函数,以及应用程序中使用的全局变量。您使用了一个 deals 数组和一个 sections 数组。它们是相关交易的附加组(例如,Deals under $10
)。还有一个名为 dealDetails
的映射,其键是 Item IDs(来自于交易数据),其值是从 eBay Shopping API 获取的详细信息。
您首先调用一个代理,该代理又将调用 eBay Daily Deals REST API。这将把交易列表作为一个 XML 文档提供给您。您解析用于进行 Ajax 调用的 XMLHttpRequest 对象的 onreadystatechange
函数中的文档。您还使用其他两个函数,parseDeal
和 parseSection
,来将 XML 节点解析为更易于使用的 JavaScript 对象。这些函数可以在可下载的代码样例(参见 下载 部分)中找到,但由于它们只是令人厌烦的 XML 解析函数,因此我在这里没有包括它们。最后,在解析了 XML 后,您还使用了另外两个函数,createDealUi
和 createSectionUi
,来修改 DOM。此时,这个 UI 如 图 1 所示。
如果您返回 清单 4,就会注意到在加载主交易之后,您对这些交易的每个部分都调用了 loadDetails
函数。在这个函数中,您通过使用 eBay Shopping API 加载每个交易的附加细节 — 但前提是浏览器支持 Web Workers。清单 5 展示了 loadDetails
函数。
function loadDetails(items){ if (!!window.Worker){ items.forEach(function(item){ var xmlStr = null; if (window.localStorage){ xmlStr = localStorage.getItem(item.itemId); } if (xmlStr){ var itemDetails = parseFromXml(xmlStr); dealDetails[itemDetails.id] = itemDetails; } else { var worker = new Worker("details.js"); worker.onmessage = function(message){ var responseXmlStr =message.data.responseXml; var itemDetails=parseFromXml(responseXmlStr); if (window.localStorage){ localStorage.setItem( itemDetails.id, responseXmlStr); } dealDetails[itemDetails.id] = itemDetails; }; worker.postMessage(item.itemId); } }); } } |
在 loadDetails
中,您首先检查全局作用域(window
对象)中的 Worker
函数。如果该函数不在那里,那么无需做任何事。反之,您首先检查 XML 的 localStorage
以获取这个交易的细节。这是移动 Web 应用程序常用的本地缓存策略,本系列第 2 部分(参见 参考资料 部分的链接)详细介绍过这种策略。
如果 XML 位于本地,那么您在 parseFromXml
函数中解析它并将交易细节添加到 dealDetails
对象。反之,则衍生一个 Web Worker 并使用 postMessage
向其发送 Item ID。当这个 Worker 检索到数据并将数据发布回主线程后,您解析 XML,将结果添加到 dealDetails
,然后将 XML 存储到 localStorage
中。清单 6 展示了这个 Worker 脚本:details.js。
importScripts("common.js"); onmessage = function(message){ var itemId = message.data; var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if (this.readyState == 4 && this.status == 200){ postMessage({responseXml: this.responseText}); } }; var urlStr = generateUrl(itemId); xhr.open("GET", "proxy?url=" + escape(urlStr)); xhr.send(null); } |
这个 Worker 脚本非常简单。您使用 Ajax 调用代理,该代理又调用 eBay Shopping API。当您收到来自代理的 XML 后,使用一个 JavaScript 对象文字(object literal)将其发送回主线程。注意,即使您能够使用来自一个 Worker 的 XMLHttpRequest
,但所有信息都将返回它的 responseText
属性,而不是它的 responseXml
属性。这是因为这个 Worker 脚本范围内没有 JavaScript DOM 解析器。注意,generateUrl
函数来自 common.js 文件(见 清单 7)。您使用 importScripts
函数导入 common.js 文件。
function generateUrl(itemId){ var appId = "YOUR APP ID GOES HERE"; return ""+ "responseencoding=XML&appid=" + appId + "&siteid=0&version=665" +"&ItemID=" + itemId; } |
现在,您已经知道如何(为支持 Web Workers 的浏览器)填充交易细节,我们返回 图 1 研究一下如何在应用程序中使用这种方法。注意,每笔交易旁边都有一个 Show Details 按钮,单击该按钮修改这个 UI,如 图 2 所示。
这个 UI 将在您调用 showDetails
函数时显示。清单 8 展示了这个函数。
function showDetails(id){ var el = $(id); if (el.style.display == "block"){ el.style.display = "none"; } else { el.style.display = "block"; if (!el.innerHTML){ var details = dealDetails[id]; if (details){ var ui = createDetailUi(details); el.appendChild(ui); } else { var itemId = id; var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if (this.readyState == 4 && this.status == 200){ var itemDetails = parseFromXml(this.responseText); if (window.localStorage){ localStorage.setItem( itemDetails.id, this.responseText); } dealDetails[id] = itemDetails; var ui = createDetailUi(itemDetails); el.appendChild(ui); } }; var urlStr = generateUrl(id); xhr.open("GET", "proxy?url=" + escape(urlStr)); xhr.send(null); } } } } |
您收到了即将显示的交易的 ID 并切换是否显示它。当该函数第一次调用时,它将检查细节是否已经存储到 dealDetails 映射中。如果浏览器支持 Web Workers,那么这些细节已经存储且它的 UI 已经创建并添加到 DOM 中。如果这些细节还没有加载,或者,如果浏览器不支持 Workers,那么您需要执行一个 Ajax 调用来加载此数据。这就是这个应用程序无论在有无 Workers 时都同样能正常工作的原因。这意味着,如果 Workers 受到支持,那么数据就已被加载且 UI 将立即响应。如果没有 Workers,UI 仍将加载,只是需要花费几秒钟时间。
对于 Web 开发人员来说,Web Workers 听起来就像一种外来的新技术。但是,如本文所述,它们是非常实用的应用程序。这对于移动 Web 应用程序来说尤其正确。这些 Workers 可用于预取数据或执行其他预先操作,从而提供一个更加实时的 UI。这对于需要通过网速可能较慢的网络加载数据的移动 Web 应用程序来说尤其正确。结合使用这种技术和缓存策略,您的应用程序的快捷反应将使您的用户感到惊喜!
描述 | 名字 | 大小 | 下载方法 |
---|---|---|---|
文章源代码 | Workers.zip | 8KB | HTTP |
学习
- 使用 HTML 5 创建移动 Web 应用程序,第 1 部分:联合使用 HTML 5、地理定位 API 和 Web 服务来创建移动混搭程序(Michael Galpin,developerWorks,2010 年 5 月):找到并跟踪位置坐标,以用于各种 Web 服务中。在本系列的第 1 部分中,使用地理位置标准的各个方面以及 HTML 5 和流行的 Web 服务,创建一个有趣的移动混搭程序。
- 使用 HTML 5 创建移动 Web 应用程序,第 2 部分:使用 HTML 5 开启移动 Web 应用程序的本地存储(Michael Galpin,developerWorks,2010 年 5 月):探索 HTML 5 中针对无线应用程序的一个重要新特性。通过本地存储的标准化和一个简单 API,可以在客户端上存储大量数据并改善性能。
- 使用 HTML 5 创建移动 Web 应用程序,第 3 部分:使用 HTML 5 支持移动 Web 应用程序离线工作(Michael Galpin,developerWorks,2010 年 6 月):支持您的应用程序在有无 Internet 连接时都能正常工作,并了解如何探测您的应用程序何时从离线转为在线或从在线转为离线。
- HTML 5 Offline Application Cache 文档:参阅苹果公司关于清单文件和应用程序缓存的详细信息。
- Firefox 中的离线资源:离线功能不仅仅用于移动应用程序。阅读相关信息,了解 Firefox 如何在 Mozilla 中实现这个标准。
- Create Ajax applications for the mobile Web(Michael Galpin,developerWorks,2010 年 3 月):探索如何使用 Ajax,这是所有移动 Web 应用程序的重要部分。
- HTML 5 中的新元素(Elliotte Rusty Harold,developerWorks,2007 年 8 月):HTML 5 不仅仅与 JavaScript 有关。阅读关于 HTML 5 中新标记的内容。
- Android 和 iPhone 浏览器之战,第 1 部分:WebKit 成援兵(Frank Ableson,developerWorks,2009 年 12 月):您是否喜欢使用 HTML 5 的移动 Web 应用程序方法,但还想您的应用程序进入 iPhone App Store 和 Android Market?在这个两部分文章系列的第 1 部分看看如何能两全其美。
- :阅读这本免费图书,详细了解 HTML 5 检测技术以及 HTML 5 的许多特性。
- Safari Reference Library:如果开发 iPhone 的 Web 应用程序,请将它放在手边。
- (Working Draft,2010 年 3 月):参考 HTML 5 的权威来源。
- 本文作者的更多文章(Michael Galpin,developerWorks,2006 年 4 月至今):阅读关于 XML、Eclipse、Apache Geronimo、Ajax、更多 Google API 及其他技术的文章。
- My developerWorks:个性化您的 developerWorks 体验。
- :了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- XML 技术库:访问 developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。
- developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
- developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
获得产品和技术
- 是一个用于检测 HTML 5 特性的综合工具。
- :获取检测 HTML 5 特性的综合工具,这些特性包括 localStorage、Web Workers、applicationCache 及其他。
- Android 开发人员网站:下载 Android SDK,访问 API 参考,获取 Android 最新消息。
- iPhone SDK:获取最新的 iPhone SDK 来开发 iPad、iPhone 和 iPod 触摸应用程序。
- :获取 Android 移动平台的开放源代码。
- :使用 Google 下载 Java™ 和 Python 工具来构建可伸缩的 Web 应用程序。
- IBM 产品评估试用版软件:下载或 在线试用 IBM SOA Sandbox,并开始使用来自 DB2®、Lotus®、Rational® 、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- 参与论坛讨论。
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
- developerWorks 博客:查看博客,参与其中。