370 likes | 458 Views
Android 开发教程之使用 HTML 5 开启移动 Web 应用程序的本地存储. 简介 先决条件 本地存储基础 Storage API 例子:利用本地存储实现高速缓存 清单 1. 最基本的 Twitter 搜索 本地保存 清单 2. 搜索和保存 快速本地数据加载 清单 3. 首先进行本地搜索 清单 4. 计算前 10 个搜索条目 清单 5. 初始化页面 清单 6. Storage 事件处理程序 结束语. 简介.
E N D
Android开发教程之使用 HTML 5 开启移动 Web 应用程序的本地存储 简介 先决条件 本地存储基础 Storage API 例子:利用本地存储实现高速缓存 清单 1. 最基本的 Twitter 搜索 本地保存 清单 2. 搜索和保存 快速本地数据加载 清单 3. 首先进行本地搜索 清单 4. 计算前 10 个搜索条目 清单 5. 初始化页面 清单 6. Storage 事件处理程序 结束语
简介 • HTML 5 中一个最有用的新特性是本地存储的标准化。最终,Web 开发人员可以不再试图将所有客户端数据都填塞到 4 KB 的 Cookies 中。现在您可以利用一个简单的 API 将大量数据存储在客户机上。这是一个完美的缓存机制,可以大大提高应用程序的速度 —— 速度对于移动 Web 应用程序是一个至关重要的因素,因为它们相对于桌面应用程序来说,依赖的是慢得多的连接。在这个关于 HTML 5 的系列的第二篇文章中,将了解如何使用本地存储,如何调试它,以及使用它来改善 Web 应用程序的各种方式。
Android开发教程之关于本系列 • HTML 5 是一项被大肆宣扬的技术,但是它实至名归。它有望成为一个技术引爆点,将桌面应用程序功能引向浏览器。它不仅适用于传统浏览器,甚至也针对移动浏览器。更好的是,最流行的移动浏览器已经采纳和实现 HTML 5 规范的很多重要部分。 • 在这个五部分的系列中,我们将详细了解几个新技术,它们都是 HTML 5 的一部分,可以大大影响移动 Web 应用程序开发。在每一部分中,都将开发一个可以工作的移动 Web 应用程序,展示一个可以用于现代移动 Web 浏览器(比如iPhone和基于 Android 的设备上的浏览器)的 HTML 5 特性。
Android开发教程之先决条件 • 常用缩略语 • API: 应用程序编程接口 • CSS: 层叠样式表 • DOM: 文档对象模型 • HTML: 超文本标记语言 • HTTP: 超文本传输协议 • JSON: JavaScript 对象表示法 • JSONP: 带填充的 JSON • SDK: 软件开发工具包 • UI: 用户界面 • URL: 统一资源定位符 • W3C: 万维网联盟 • 在本文中,您将使用最新 Web 技术开发 Web 应用程序。这里的大多数代码只是 HTML、JavaScript 和 CSS — 任何 Web 开发人员的核心技术。需要的最重要的东西是用于测试代码的浏览器。本文中的大多数代码将运行在最新的桌面浏览器上,例外的情况会指出来。当然,还必须在移动浏览器上进行测试,您肯定希望最新的iPhone和 Android SDK 支持这些代码。本文中使用的是iPhone SDK 3.1.3 和 Android SDK 2.1。参见 参考资料中的链接。
Android开发教程之本地存储基础 • 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。
Android开发教程之Storage API • localStorage API 十分简单。实际上,根据 HTML 5 规范,它实现了 DOM Storage 接口。差别的原因是,HTML 5 指定两个不同的对象实现该接口:localStorage和 sessionStorage。sessionStorage对象是一个只在会话期间存储数据的 Storage 实现。更确切地说,只要没有可以访问 sessionStorage的脚本正在运行,浏览器就可以删除 sessionStorage数据。这是与 localStorage相对的,后者跨多个用户会话。两个对象共享相同的 API,所以我将只着重介绍 localStorage。
Android开发教程 • 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 搜索应用程序。
清单 1. 最基本的 Twitter 搜索 • <html> • <head> • <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> • <meta name = "viewport" content = "width = device-width"/> • <title>Basic Twitter Search</title> • <script type="text/javascript"> • function searchTwitter(){ • var query = "http://search.twitter.com/search.json?callback • =showResults&q="; • query += $("kwBox").value; • var script = document.createElement("script"); • script.src = query; • document.getElementsByTagName("head")[0].appendChild(script); • }
Android开发教程 • // ui code deleted for brevity • function showResults(response){ • var tweets = response.results; • tweets.forEach(function(tweet){ • tweet.linkUrl = "http://twitter.com/" + tweet.from_user • + "/status/" + tweet.id; • }); • makeResultsTable(tweets); • } • </script>
Android开发教程 • <!-- CSS deleted for brevity --> • </head> • <body> • <div id="main"> • <label for="kwBox">Search Twitter:</label> • <input type="text" id="kwBox"/> • <input type="button" value="Go!" onclick="searchTwitter()"/> • </div> • <div id="results"> • </div> • </body> • </html>
Android开发教程 • 在这个应用程序中,使用了 Twitter 搜索 API 对 JSONP 的支持。用户提交搜索时,会动态添加一个脚本标记到页面并指定回调函数的名称,从而进行一次 API 调用。这允许您从 Web 页面进行一次跨域调用。一旦调用返回,回调函数(showResults)就会被调用。您添加一个链接 URL 到 Twitter 返回的每个 tweet,然后创建一个简单的表格用于显示这些 tweet。为了提速,您可以高速缓存从搜索查询得到的结果,然后在用户每次提交查询时使用这些缓存的结果。首先来看如何使用 localStorage来本地存储 tweet。
Android开发教程之本地保存 • 基本的 Twitter 搜索将从 Twitter 搜索 API 提供一组 tweet。如果您可以本地保存这些 tweet,并将它们与生成它们的关键词搜索相关联,那么您就具有了一个有用的高速缓存。要保存 tweet,您只需要修改当对 Twitter 搜索 API 的调用返回时将被调用的 callback 函数。清单 2展示了修改后的函数。
清单 2. 搜索和保存 • function searchTwitter(){ • var keyword = $("kwBox").value; • var query = "http://search.twitter.com/search.json?callback • =processResults&q="; • query += keyword; • var script = document.createElement("script"); • script.src = query; • document.getElementsByTagName("head")[0].appendChild(script); • }
Android开发教程 • function processResults(response){ • var keyword = $("kwBox").value; • var tweets = response.results; • tweets.forEach(function(tweet){ • saveTweet(keyword, tweet); • tweet.linkUrl = "http://twitter.com/" + tweet.from_user + "/status/" + tweet.id; • }); • makeResultsTable(); • addTweetsToResultsTable(tweets); • }
Android开发教程 • 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);
Android开发教程 • if (index){ • index = JSON.parse(index); • } else { • index = []; • } • if (!index.contains(tweet.id)){ • index.push(tweet.id); • localStorage.setItem("index::"+keyword, JSON.stringify(index)); • } • }
Android开发教程 • 从第一个函数 searchTwitter开始。这在用户提交搜索时被调用。相对于 清单 1做了改动的惟一的地方是 callback 函数。不只是在 tweet 返回时显示它们,您还需要处理它们(除了显示,还要保存它们)。因此,您指定一个新的 callback 函数 processResults。您针对每个 tweet 调用 saveTweet。您还传递被用于生成搜索结果的关键词。这是因为您想要将这些 tweet 与该关键词相关联。 • 在 saveTweet函数中,首先进行检查,确保 localStorage真正受到浏览器的支持。正如前面所提到的,localStorage在桌面和移动浏览器中都受到广泛支持,但是在使用这样的新特性时进行检查总是一个好主意。如果它不受支持,那么您简单地从函数返回。显然不会保存任何东西,但是也不会报错 — 应用程序在这种情况下只是不会具有高速缓存。如果 localStorage受到支持,那么首先进行检查,看这个 tweet 是否已经存储。如果没有存储,那么使用 setItem本地存储它。接下来,检索一个对应于关键词的索引对象。这只是一组与关键词相关联的 tweet 的 ID。如果 tweet ID 还不是索引的一部分,那么添加它并更新索引。
Android开发教程 • 注意,在 清单 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读取它们即可。下面来看这是如何做到的。
Android开发教程之快速本地数据加载 • 在 清单 2中,您看到了一些例子使用 getItem方法从 localStorage读取数据。现在当一个用户提交搜索时,您可以检查高速缓存命中情况,并立即加载缓存的结果。当然,您仍将针对 Twitter 搜索 API 进行查询,因为人们一直在产生 tweet 并添加到搜索结果。但是,通过只寻找还没在高速缓存中的结果,现在您也有了让查询更为高效的方式。清单 3展示了更新后的搜索代码。
清单 3. 首先进行本地搜索 • function searchTwitter(){ • if ($("resultsTable")){ • $("resultsTable").innerHTML = ""; // clear results • } • makeResultsTable(); • var keyword = $("kwBox").value; • varmaxId = loadLocal(keyword); • var query = "http://search.twitter.com/search.json?callback=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; • }
Android开发教程 • var index = localStorage.getItem("index::" + keyword); • var tweets = []; • vari = 0; • var tweet = {}; • if (index){ • index = JSON.parse(index); • for (i=0;i<index.length;i++){ • tweet = localStorage.getItem("tweet"+index[i]); • if (tweet){ • tweet = JSON.parse(tweet); • tweets.push(tweet); • } • } • }
if (tweets.length < 1){ • return 0; • } • tweets.sort(function(a,b){ • return a.id > b.id; • }); • addTweetsToResultsTable(tweets); • return tweets[0].id; • }
Android开发教程 • 您将注意到的第一件事情是,当一个搜索被提交时,您首先调用新的 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函数。使用存储事件会提供一种备选的、更少耦合的方法。
Android开发教程之存储事件 • 现在扩展示例应用程序,展示最可能具有缓存结果的前 10 个搜索条目。这可能代表用户最常提交的搜索。清单 4 展示了一个用于计算并显示前 10 个搜索条目的函数。
清单 4. 计算前 10 个搜索条目 • function displayStats(){ • if (!window.localStorage){ return; } • vari = 0; • var key = ""; • var index = []; • varcachedSearches = []; • for (i=0;i<localStorage.length;i++){ • key = localStorage.key(i); • if (key.indexOf("index::") == 0){ • index = JSON.parse(localStorage.getItem(key)); • cachedSearches.push ({keyword: key.slice(7), numResults: index.length}); • } • }
Android开发教程 • cachedSearches.sort(function(a,b){ • if (a.numResults == b.numResults){ • if (a.keyword.toLowerCase() < b.keyword.toLowerCase()){ • return -1; • } else if (a.keyword.toLowerCase() > b.keyword.toLowerCase()){ • return 1; • } • return 0; • }
Android开发教程 • return b.numResults - a.numResults; • }).slice(0,10).forEach(function(search){ • varli = document.createElement("li"); • var txt = document.createTextNode(search.keyword + " : " + search.numResults); • li.appendChild(txt); • $("stats").appendChild(li); • }); • }
该函数充分展示了 localStorage API。您首先得到存储在 localStorage中的条目的总数,然后再迭代这些条目。如果条目是索引,那么您就解析该对象并创建一个表示您要处理的数据的对象:与索引相关联的关键词和索引中 tweet 的数量。该数据存储在一个叫做 cachedSearches的数组中。接下来,排序 cachedSearches,将具有最多结果的搜索排在第一位,如果两个搜索具有相同数量的缓存结果,就再使用一个不区分大小写的字母排序。然后对于前 10 个搜索,为每个搜索创建 HTML,并将它们附加到一个排好序的列表。让我们在页面初次加载时调用该函数,如 清单 5所示。
清单 5. 初始化页面 • window.onload = function() { • displayStats(); • document.body.setAttribute("onstorage", "handleOnStorage();"); • } • 第一行在页面加载时调用 清单 4中的函数。第二次加载是变得更有趣的地方。您在这里为 onstorage事件设置一个事件处理程序。每当 localStorage.setItem函数执行完成,该事件就会激活。这将允许您重新计算前 10 个搜索。清单 6展示了该事件处理程序。
清单 6. Storage 事件处理程序 • function handleOnStorage() { • if (window.event && window.event.key.indexOf("index::") == 0){ • $("stats").innerHTML = ""; • displayStats(); • } • }
Android开发教程 • 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 应用程序的另一个新的令人振奋的功能 —— 离线 —— 是很关键的。这将是本系列下一篇文章的主题。