編輯:中級開發
簡介: HTML 5 中一個最有用的新特性是本地存儲的標准化。最終,Web 開發人員可以不再試圖將所有客戶端數據都填塞到 4 KB 的 CookIEs 中。現在您可以利用一個簡單的 API 將大量數據存儲在客戶機上。這是一個完美的緩存機制,可以大大提高應用程序的速度 —— 速度對於移動 Web 應用程序是一個至關重要的因素,因為它們相對於桌面應用程序來說,依賴的是慢得多的連接。在這個關於 Html 5 的系列的第二篇文章中,將了解如何使用本地存儲,如何調試它,以及使用它來改善 Web 應用程序的各種方式。
關於本系列
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。
Storage
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 搜索應用程序。
清單 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); } // 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> <!-- 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>
在這個應用程序中,使用了 Twitter 搜索 API 對 JSONP 的支持。用戶提交搜索時,會動態添加一個腳本標記到頁面並指定回調函數的名稱,從而進行一次 API 調用。這允許您從 Web 頁面進行一次跨域調用。一旦調用返回,回調函數(showResults
)就會被調用。您添加一個鏈接 URL 到 Twitter 返回的每個 tweet,然後創建一個簡單的表格用於顯示這些 tweet。為了提速,您可以高速緩存從搜索查詢得到的結果,然後在用戶每次提交查詢時使用這些緩存的結果。首先來看如何使用 localStorage
來本地存儲 tweet。
本地保存
基本的 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); } 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); } 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 進行查看。
圖 1. 本地高速緩存的 tweet
Chrome 和 Safari 都內置了開發人員工具,可以用於查看任何保存在 localStorage
中的數據。這對於調試使用 localStorage
的應用程序非常有用。它以純文本形式展示本地存儲的鍵/值對。既然您已經開始保存來自 Twitter 的搜索 API 的 tweet,以便它們可以被用作高速緩存,所以您只需開始從 localStorage
讀取它們即可。下面來看這是如何做到的。
快速本地數據加載
在 清單 2 中,您看到了一些例子使用 getItem
方法從localStorage
讀取數據。現在當一個用戶提交搜索時,您可以檢查高速緩存命中情況,並立即加載緩存的結果。當然,您仍將針對 Twitter 搜索 API 進行查詢,因為人們一直在產生 tweet 並添加到搜索結果。但是,通過只尋找還沒在高速緩存中的結果,現在您也有了讓查詢更為高效的方式。清單 3 展示了更新後的搜索代碼。
清單 3. 首先進行本地搜索
function searchTwitter(){ if ($("resultsTable")){ $("resultsTable").innerHtml = ""; // clear results } makeResultsTable(); var keyword = $("kwBox").value; var maxId = 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; } var index = localStorage.getItem("index::" + keyWord); var tweets = []; var i = 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; }
您將注意到的第一件事情是,當一個搜索被提交時,您首先調用新的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 個搜索條目的函數。
清單 4. 計算前 10 個搜索條目
function displayStats(){ if (!window.localStorage){ return; } var i = 0; var key = ""; var index = []; var cachedSearches = []; 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}); } } 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; } return b.numResults - a.numResults; }).slice(0,10).forEach(function(search){ var li = 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(); } }
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 應用程序的另一個新的令人振奮的功能 —— 離線 —— 是很關鍵的。這將是本系列下一篇文章的主題。
有關android SDK自帶的性能分析調試工具TraceVIEw使用方法,我們在android.os.Debug調試工具使用方法 簡單的說過,有關實際使用如
簡介: 一直以來,“對多個浏覽器進行測試” 像是一條難以解開的咒語,因為需要對大量浏覽器進行測試。對所有浏覽器進行測試(尤其是目前)幾乎是不可能的。但是這比您
簡介: 在這個由五個部分所組成的系列的第一部分中,您將接觸到移動 Web 應用程序中最流行的新技術:地理定位。高端智能手機都內置 GPS,現在您將了解 Web
對於Android 3.x honeycomb系統來說屏幕的兼容性很重要,這裡目前我們就主流的Android 1.5~2.3.4的軟件如何兼容android 3.0有關