HTML 5 是一项被大肆宣扬的技术,但是它实至名归。它有望成为一个技术引爆点,将桌面应用程序功能引向浏览器。它不仅适用于传统浏览器,甚至也针对移动浏览器。更好的是,最流行的移动浏览器已经采纳和实现 HTML 5 规范的很多重要部分。
在这个五部分的系列中,我们将详细了解几个新技术,它们都是 HTML 5 的一部分,可以大大影响移动 Web 应用程序开发。在每一部分中,都将开发一个可以工作的移动 Web 应用程序,展示一个可以用于现代移动 Web 浏览器(比如 iPhone 和基于 Android 的设备上的浏览器)的 HTML 5 特性。
在本文中,您将使用最新 Web 技术开发 Web 应用程序。这里的大多数代码只是 HTML、JavaScript 和 CSS — 任何 Web 开发人员的核心技术。需要的最重要的东西是用于测试代码的浏览器。本文中的大多数代码将运行在最新的桌面浏览器上,例外的情况会指出来。当然,还必须在移动浏览器上进行测试,您肯定希望最新的 iPhone 和 Android SDK 支持这些代码。本文中使用的是 iPhone SDK 3.1.3 和 Android SDK 2.1。参见 参考资料 中的链接。
Web 开发人员多年来一直在尝试将数据存储在客户机上。HTTP Cookies 被滥用于此目的。开发人员将大量数据挤放在 HTTP 规范分配的 4KB 上。原因很简单。出于各种原因,交互式 Web 应用程序需要存储数据,并且将这些数据存储在服务器上通常效率低下、不安全或者不适当。多年来,这个问题有了好几种备选方法。各种各样的浏览器已经引入了专有存储 API。开发人员也利用了 Flash Player 中的扩展存储功能(通过 JavaScript 实现)。类似地,Google 为各种浏览器创建了 Gears 插件,并且它包含了存储 API。毫不奇怪的是,一些 JavaScript 库试图抹平这些差异。换句话说,这些库提供一个简单的 API,然后检查有哪些存储功能(可能是一个专有浏览器 API 或者是一个诸如 Flash 的插件)。
对 Web 开发人员来说幸运的是,HTML 5 规范最终包含了一个针对本地存储的标准,被广泛的浏览器所实现。事实上,该标准是最快被采纳的标准,在所有主要浏览器的最新版本中都受到支持:Microsoft®、Internet Explorer®、Mozilla Firefox、Opera、Apple Safari 和 Google Chrome。对于移动开发人员更为重要的是,它在基于 WebKit 的浏览器(诸如 iPhone 和使用 Android(版本 2.0 或更高版本)的手机中的浏览器)以及其他移动浏览器(比如 Mozilla 的 Fennec)中受到支持。记住这一点,我们来看一下这个 API。
localStorage
API 十分简单。实际上,根据 HTML 5 规范,它实现了 DOM Storage 接口。差别的原因是,HTML 5 指定两个不同的对象实现该接口:localStorage
和 sessionStorage
。sessionStorage
对象是一个只在会话期间存储数据的 Storage
实现。更确切地说,只要没有可以访问 sessionStorage
的脚本正在运行,浏览器就可以删除 sessionStorage
数据。这是与 localStorage
相对的,后者跨多个用户会话。两个对象共享相同的 API,所以我将只着重介绍 localStorage
。
Storage
API 是一种经典的名/值对数据结构。您将使用的最常见的方法是 getItem(name)
和 setItem(name, value)
。这些方法完全跟您预期的一样:getItem
返回与名称相关联的值,如果什么都不存在,则返回 null,而 setItem
要么是将名/值对添加到 localStorage
,要么是取代现有值。还有一个 removeItem(name)
,顾名思意,它从 localStorage
删除一个名/值对(如果存在的话,否则什么都不做)。最后,对于在所有名/值对上迭代,存在两个 API。一个是长度属性,给出正在存储的名/值对的总数。对应地,一个 key(index)
方法从存储中使用的所有名称中返回一个名称。
利用这些简单的 API,可以完成大量任务,比如说个性化或跟踪用户行为。这些可以说对移动 Web 开发人员是重要的用例,但是还有一个更为重要的用例:高速缓存。利用 localStorage
,可以在客户机的本地机器上容易地从服务器高速缓存数据。这让您无需等待可能缓慢的服务器回调,并且最小化了对服务器上数据的需求量。现在来看一个例子,演示了如何使用 localStorage 来获得这种高速缓存。
本例建立在本系列第 1 部分中的例子之上,那时您最先开始了 t0 开发。那个例子展示了如何通过利用地理定位 API 取得用户的位置而执行 Twitter 的本地搜索。从那个例子开始,对它进行简化,并大大提高它的性能。首先,将那个例子简化成不带地理位置的 Twitter 搜索。清单 1 展示了简化的 Twitter 搜索应用程序。
|
在这个应用程序中,使用了 Twitter 搜索 API 对 JSONP 的支持。用户提交搜索时,会动态添加一个脚本标记到页面并指定回调函数的名称,从而进行一次 API 调用。这允许您从 Web 页面进行一次跨域调用。一旦调用返回,回调函数(showResults
)就会被调用。您添加一个链接 URL 到 Twitter 返回的每个 tweet,然后创建一个简单的表格用于显示这些 tweet。为了提速,您可以高速缓存从搜索查询得到的结果,然后在用户每次提交查询时使用这些缓存的结果。首先来看如何使用 localStorage
来本地存储 tweet。
基本的 Twitter 搜索将从 Twitter 搜索 API 提供一组 tweet。如果您可以本地保存这些 tweet,并将它们与生成它们的关键词搜索相关联,那么您就具有了一个有用的高速缓存。要保存 tweet,您只需要修改当对 Twitter 搜索 API 的调用返回时将被调用的 callback
函数。清单 2 展示了修改后的函数。
function searchTwitter(){ var keyword = $("kwBox").value; var query = " =processResults&q="; query += keyword; var script = document.createElement("script"); script.src = query; document.getElementsByTagName("head")[0].appendChild(script); } function processResults(response){ var keyword = $("kwBox").value; var tweets = response.results; tweets.forEach(function(tweet){ saveTweet(keyword, tweet); tweet.linkUrl = "" + tweet.from_user + "/status/" + tweet.id; }); makeResultsTable(); addTweetsToResultsTable(tweets); } function saveTweet(keyword, tweet){ // check if the browser supports localStorage if (!window.localStorage){ return; } if (!localStorage.getItem("tweet" + tweet.id)){ localStorage.setItem("tweet" + tweet.id, JSON.stringify(tweet)); } var index = localStorage.getItem("index::" + keyword); if (index){ index = JSON.parse(index); } else { index = []; } if (!index.contains(tweet.id)){ index.push(tweet.id); localStorage.setItem("index::"+keyword, JSON.stringify(index)); } } |
从第一个函数 searchTwitter
开始。这在用户提交搜索时被调用。相对于 清单 1 做了改动的惟一的地方是 callback
函数。不只是在 tweet 返回时显示它们,您还需要处理它们(除了显示,还要保存它们)。因此,您指定一个新的 callback
函数 processResults
。您针对每个 tweet 调用 saveTweet
。您还传递被用于生成搜索结果的关键词。这是因为您想要将这些 tweet 与该关键词相关联。
在 saveTweet
函数中,首先进行检查,确保 localStorage
真正受到浏览器的支持。正如前面所提到的,localStorage 在桌面和移动浏览器中都受到广泛支持,但是在使用这样的新特性时进行检查总是一个好主意。如果它不受支持,那么您简单地从函数返回。显然不会保存任何东西,但是也不会报错 — 应用程序在这种情况下只是不会具有高速缓存。如果 localStorage
受到支持,那么首先进行检查,看这个 tweet 是否已经存储。如果没有存储,那么使用 setItem
本地存储它。接下来,检索一个对应于关键词的索引对象。这只是一组与关键词相关联的 tweet 的 ID。如果 tweet ID 还不是索引的一部分,那么添加它并更新索引。
注意,在 清单 3 中保存和加载 JSON 时,您使用了 JSON.stringify
和 JSON.parse
。JSON 对象(或者更确切地说,是 window.JSON
)是 HTML 5 规范的一部分,作为一个总是存在的 原生 对象。stringify
方法将把任何 JavaScript 对象转换成一个序列化的字符串,而 parse
方法则进行相反的操作,它从序列化的字符串表示还原 JavaScript 对象。这是很必要的,因为 localStorage
只存储字符串。但是,原生 JSON 对象并不被广泛实现为 localStorage
。例如,它不出现在 iPhone(在撰写本文时是版本 3.1.3)的最新 Mobile Safari 浏览器上。它在最新 Android 浏览器上受支持。您可以容易地检查它是否在那里,如果不在,就加载一个额外的 JavaScript 文件。您可以通过访问 json.org Web 站点(参见 参考资料),获得原生使用的相同 JSON 对象。要本地查看这些序列化的字符串是什么样的,可以使用各种浏览器工具检查 localStorage 中为给定站点存储的内容。图 1 展示了一些高速缓存的 tweet,它们存储在本地,使用 Chrome 的 Developer Tools 进行查看。
Chrome 和 Safari 都内置了开发人员工具,可以用于查看任何保存在 localStorage
中的数据。这对于调试使用 localStorage
的应用程序非常有用。它以纯文本形式展示本地存储的键/值对。既然您已经开始保存来自 Twitter 的搜索 API 的 tweet,以便它们可以被用作高速缓存,所以您只需开始从 localStorage
读取它们即可。下面来看这是如何做到的。
在 清单 2 中,您看到了一些例子使用 getItem
方法从 localStorage
读取数据。现在当一个用户提交搜索时,您可以检查高速缓存命中情况,并立即加载缓存的结果。当然,您仍将针对 Twitter 搜索 API 进行查询,因为人们一直在产生 tweet 并添加到搜索结果。但是,通过只寻找还没在高速缓存中的结果,现在您也有了让查询更为高效的方式。清单 3 展示了更新后的搜索代码。
function searchTwitter(){ if ($("resultsTable")){ $("resultsTable").innerHTML = ""; // clear results } makeResultsTable(); var keyword = $("kwBox").value; var maxId = loadLocal(keyword); var query = "=processResults&q="; query += keyword; if (maxId){ query += "&since_id=" + maxId; } var script = document.createElement("script"); script.src = query; document.getElementsByTagName("head")[0].appendChild(script); } function loadLocal(keyword){ if (!window.localStorage){ return; } var index = localStorage.getItem("index::" + keyword); var tweets = []; var i = 0; var tweet = {}; if (index){ index = JSON.parse(index); for (i=0;i |
您将注意到的第一件事情是,当一个搜索被提交时,您首先调用新的 loadLocal
函数。该函数返回一个整数,即高速缓存中找到的最新 tweet 的 ID。loadLocal
函数接受一个 keyword
作为参数,该关键词也被用于在 localStorage
高速缓存中寻找相关 tweet。如果具有一个 maxId
,那么使用它来修改对 Twitter 的查询,添加 since_id
参数。您在告诉 Twitter API 只返回比该参数中给定的 ID 新的 tweet。潜在地,这可以减少从 Twitter 返回的结果数量。您任何时候都可以为移动 Web 应用程序优化服务器调用,因为它可以真正改善慢速移动网络上的用户体验。现在更仔细地来看一下 loadLocal
。
在 loadLocal
函数中,您利用了存储在前面 清单 2 中的数据结构。通过使用 getItem
,您首先加载与关键词相关联的索引。如果没找到任何索引,那么就没有缓存的 tweet,所以就没有展示的东西,并且没有可对查询进行的优化(您返回一个 0 值以指示这一点)。如果找到一个索引,那么您从它得到 ID 列表。这些 tweet 中的每一个都被本地高速缓存,所以您只需再次使用 getItem
方法,从高速缓存加载每一个 tweet。加载的 tweet 然后被排序。使用 addTweetsToResultsTable
函数来显示 tweet,然后返回最新 tweet 的 ID。在本例中,得到新 tweet 的 代码直接调用更新 UI 的函数。您可能会对此感到惊讶,因为它在存储和检索 tweet 的代码与显示它们的代码之间创建了耦合,全都通过 processResults
函数。使用存储事件会提供一种备选的、更少耦合的方法。
现在扩展示例应用程序,展示最可能具有缓存结果的前 10 个搜索条目。这可能代表用户最常提交的搜索。清单 4 展示了一个用于计算并显示前 10 个搜索条目的函数。
function displayStats(){ if (!window.localStorage){ return; } var i = 0; var key = ""; var index = []; var cachedSearches = []; for (i=0;i |
该函数充分展示了 localStorage
API。您首先得到存储在 localStorage
中的条目的总数,然后再迭代这些条目。如果条目是索引,那么您就解析该对象并创建一个表示您要处理的数据的对象:与索引相关联的关键词和索引中 tweet 的数量。该数据存储在一个叫做 cachedSearches
的数组中。接下来,排序 cachedSearches
,将具有最多结果的搜索排在第一位,如果两个搜索具有相同数量的缓存结果,就再使用一个不区分大小写的字母排序。然后对于前 10 个搜索,为每个搜索创建 HTML,并将它们附加到一个排好序的列表。让我们在页面初次加载时调用该函数,如 清单 5 所示。
window.onload = function() { displayStats(); document.body.setAttribute("onstorage", "handleOnStorage();"); } |
第一行在页面加载时调用 清单 4 中的函数。第二次加载是变得更有趣的地方。您在这里为 onstorage
事件设置一个事件处理程序。每当 localStorage.setItem
函数执行完成,该事件就会激活。这将允许您重新计算前 10 个搜索。清单 6 展示了该事件处理程序。
function handleOnStorage() { if (window.event && window.event.key.indexOf("index::") == 0){ $("stats").innerHTML = ""; displayStats(); } } |
onstorage
事件将与窗口相关联。它具有几个有用的属性:key
、oldValue
和 newValue
。除了这些自解释的属性之外,它还有一个 url
(更改值的页面的 URL)和 source
(包含更改值的脚本的窗口)。如果用户具有多个到应用程序的窗口或选项卡或者甚至是 iFrames,那么这最后两个属性就更有用,但是没有哪一个在移动应用程序中特别常见。回到 清单 6,您真正需要的惟一的属性是 key
属性。您使用该属性来看它是不是一个已修改的索引。如果是的,那么您重新设置前 10 名列表,并通过再次调用 displayStats
函数而重新绘制它。该技术的优点是,其他函数都不需要了解前 10 名列表,因为它是自包含的。
前面 我提到过,DOM Storage(它包含 localStorage
和 sessionStorage
)总体来说是一个被广泛采纳的 HTML 5 特性。但是,存储事件对于这一点来说是一个例外 — 至少在桌面浏览器上如此。在撰写本文时,仅有的支持存储事件的桌面浏览器是 Safari 4+ 和 Internet Explorer 8+。在 Firefox、Chrome 和 Opera 中不受支持。但是在移动领域,情况稍有好转。iPhone 和 Android 浏览器的最新版都完全支持存储事件,并且这里给出的代码都能在这些浏览器中完美地运行。
作为一名开发人员,突然在客户机上拥有巨额的存储空间,您会觉得自己获得了很大的解放。对于长期的 Web 开发人员来说,为做到他们多年来一直想做、却苦于找不到好的方式来做的事情带来了转机。对于移动开发人员来说,则更为振奋人心,因为它真正开启了数据的本地高速缓存。除了大大改善应用程序的性能之外,本地高速缓存对于推进移动 Web 应用程序的另一个新的令人振奋的功能 —— 离线 —— 是很关键的。这将是本系列下一篇文章的主题。
描述 | 名字 | 大小 | 下载方法 |
---|---|---|---|
文章源代码 | local.zip | 8KB | HTTP |
学习
- 使用 HTML 5 创建移动 Web 应用程序,第 1 部分:联合使用 HTML 5、地理定位 API 和 Web 服务来创建移动混搭程序(Michael Galpin,developerWorks,2010 年 5 月):找到并跟踪位置坐标,以用于各种 Web 服务中。在本系列的第 1 部分中,使用地理位置标准的各个方面以及 HTML 5 和流行的 Web 服务,创建一个有趣的移动混搭程序。
- 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 部分看看如何能两全其美。
- Invisible Flash: Enhance Web applications by secretly using the Flash player(Michael Galpin,developerWorks,2010 年 2 月):很多 Web 开发人员已经使用 Flash 来实现类似功能多年了。在本文中了解您如何实现这些功能。
- :查阅本书,广泛了解 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 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- XML 技术库:访问 developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。
- developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
- developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
获得产品和技术
- :获取检测 HTML 5 特性的综合工具,这些特性包括 localStorage、Web Workers、applicationCache 及其他。
- Android 开发人员网站:下载 Android SDK,访问 API 参考,获取 Android 最新消息。
- iPhone SDK:获取最新的 iPhone SDK 来开发 iPad、iPhone 和 iPod 触摸应用程序。
- :获取 Android 移动平台的开放源代码。
- :使用 Google 下载 Java™ 和 Python 工具来构建可伸缩的 Web 应用程序。
- :获取原生使用的相同 JSON 对象。
- IBM 产品评估试用版软件:下载或 IBM SOA Sandbox for People,并开始使用来自 DB2®、Lotus®、Rational® 、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
- developerWorks 博客:查看博客,参与其中。