HTML5 離線功能介紹
HTML5 是目前正在討論的新一代 HTML 標(biāo)準(zhǔn),它代表了現(xiàn)在 Web 領(lǐng)域的最新發(fā)展方向。在 HTML5 標(biāo)準(zhǔn)中,加入了新的多樣的內(nèi)容描述標(biāo)簽,直接支持表單驗(yàn)證、視頻音頻標(biāo)簽、網(wǎng)頁元素的拖拽、離線存儲和工作線程等功能。其中一個新特性就是對離線應(yīng)用開發(fā)的支持。
在開發(fā)支持離線的 Web 應(yīng)用程序時,開發(fā)者通常需要使用以下三個方面的功能:
- 離線資源緩存:需要一種方式來指明應(yīng)用程序離線工作時所需的資源文件。這樣,瀏覽器才能在在線狀態(tài)時,把這些文件緩存到本地。此后,當(dāng)用戶離線訪問應(yīng)用程序時,這些資源文件會自動加載,從而讓用戶正常使用。HTML5 中,通過 cache manifest 文件指明需要緩存的資源,并支持自動和手動兩種緩存更新方式。
- 在線狀態(tài)檢測:開發(fā)者需要知道瀏覽器是否在線,這樣才能夠針對在線或離線的狀態(tài),做出對應(yīng)的處理。在 HTML5 中,提供了兩種檢測當(dāng)前網(wǎng)絡(luò)是否在線的方式。
- 本地?cái)?shù)據(jù)存儲:離線時,需要能夠把數(shù)據(jù)存儲到本地,以便在線時同步到服務(wù)器上。為了滿足不同的存儲需求,HTML5 提供了 DOM Storage 和 Web SQL Database 兩種存儲機(jī)制。前者提供了易用的 key/value 對存儲方式,而后者提供了基本的關(guān)系數(shù)據(jù)庫存儲功能。
盡管 HTML5 還處于草稿狀態(tài),但是各大主流瀏覽器都已經(jīng)實(shí)現(xiàn)了其中的很多功能。Chrome、Firefox、Safari 和 Opera 的最新版本都對 HTML5 離線功能提供了完整的支持。IE8 也支持了其中的在線狀態(tài)檢測和 DOM Storage 功能。下面將具體介紹 HTML5 離線功能中的離線資源緩存、在線狀態(tài)檢測、DOM Storage 和 Web SQL Database,最后通過一個簡單的 Web 程序說明使用 HTML5 開發(fā)離線應(yīng)用的方法。
離線資源緩存
為了能夠讓用戶在離線狀態(tài)下繼續(xù)訪問 Web 應(yīng)用,開發(fā)者需要提供一個 cache manifest 文件。這個文件中列出了所有需要在離線狀態(tài)下使用的資源,瀏覽器會把這些資源緩存到本地。本節(jié)先通過一個例子展示 cache manifest 文件的用途,然后詳細(xì)描述其書寫方法,最后說明緩存的更新方式。
cache manifest 示例
我們通過 W3C 提供的示例來說明。Clock Web 應(yīng)用由三個文件“clock.html”、“clock.css”和“clock.js”組成。
清單 1. Clock 應(yīng)用代碼
<!-- clock.html -->
<!DOCTYPE HTML>
<html>
<head>
<title>Clock</title>
<script src="clock.js"></script>
<link rel="stylesheet" href="clock.css">
</head>
<body>
<p>The time is: <output id="clock"></output></p>
</body>
</html>
/* clock.css */
output { font: 2em sans-serif; }
/* clock.js */
setTimeout(function () {
document.getElementById('clock').value = new Date();
}, 1000);
|
當(dāng)用戶在離線狀態(tài)下訪問“clock.html”時,頁面將無法展現(xiàn)。為了支持離線訪問,開發(fā)者必須添加 cache manifest 文件,指明需要緩存的資源。這個例子中的 cache manifest 文件為“clock.manifest”,它聲明了 3 個需要緩存的資源文件“clock.html”、“clock.css”和“clock.js”。
清單 2. clock.manifest 代碼
CACHE MANIFEST
clock.html
clock.css
clock.js
|
添加了 cache manifest 文件后,還需要修改“clock.html”,把 <html> 標(biāo)簽的 manifest 屬性設(shè)置為“clock.manifest”。修改后的“clock.html”代碼如下。
清單 3. 設(shè)置 manifest 后的 clock.html 代碼
<!-- clock.html -->
<!DOCTYPE HTML>
<html manifest="clock.manifest">
<head>
<title>Clock</title>
<script src="clock.js"></script>
<link rel="stylesheet" href="clock.css">
</head>
<body>
<p>The time is: <output id="clock"></output></p>
</body>
</html>
|
修改后,當(dāng)用戶在線訪問“clock.html”時,瀏覽器會緩存“clock.html”、“clock.css”和“clock.js”文件;而當(dāng)用戶離線訪問時,這個 Web 應(yīng)用也可以正常使用了。
cache manifest 格式
下面說明書寫 cache manifest 文件需要遵循的格式。
- 首行必須是 CACHE MANIFEST。
- 其后,每一行列出一個需要緩存的資源文件名。
- 可根據(jù)需要列出在線訪問的白名單。白名單中的所有資源不會被緩存,在使用時將直接在線訪問。聲明白名單使用 NETWORK:標(biāo)識符。
- 如果在白名單后還要補(bǔ)充需要緩存的資源,可以使用 CACHE:標(biāo)識符。
- 如果要聲明某 URI 不能訪問時的替補(bǔ) URI,可以使用 FALLBACK:標(biāo)識符。其后的每一行包含兩個 URI,當(dāng)?shù)谝粋€ URI 不可訪問時,瀏覽器將嘗試使用第二個 URI。
- 注釋要另起一行,以 # 號開頭。
清單 4 的代碼中給出了 cache manifest 中各類標(biāo)識符的使用示例。
清單 4. cache manifest 示例代碼
CACHE MANIFEST
# 上一行是必須書寫的。
images/sound-icon.png
images/background.png
NETWORK:
comm.cgi
|
# 下面是另一些需要緩存的資源,在這個示例中只有一個 css 文件。
CACHE:
style/default.css
FALLBACK:
/files/projects /projects
|
更新緩存
應(yīng)用程序可以等待瀏覽器自動更新緩存,也可以使用 Javascript 接口手動觸發(fā)更新。
- 自動更新
瀏覽器除了在第一次訪問 Web 應(yīng)用時緩存資源外,只會在 cache manifest 文件本身發(fā)生變化時更新緩存。而 cache manifest 中的資源文件發(fā)生變化并不會觸發(fā)更新。
手動更新
開發(fā)者也可以使用 window.applicationCache 的接口更新緩存。方法是檢測 window.applicationCache.status 的值,如果是 UPDATEREADY,那么可以調(diào)用 window.applicationCache.update() 更新緩存。示范代碼如下。
清單 5 手動更新緩存
if (window.applicationCache.status == window.applicationCache.UPDATEREADY)
{
window.applicationCache.update();
}
|
在線狀態(tài)檢測
如果 Web 應(yīng)用程序僅僅是一些靜態(tài)頁面的組合,那么通過 cache manifest 緩存資源文件以后,就可以支持離線訪問了。但是隨著互聯(lián)網(wǎng)的發(fā)展,特別是 Web2.0 概念流行以來,用戶的提交的數(shù)據(jù)漸漸成為互聯(lián)網(wǎng)的主流。那么在開發(fā)支持離線的 Web 應(yīng)用時,就不能僅僅滿足于靜態(tài)頁面的展現(xiàn),還必需考慮如何讓用戶在離線狀態(tài)下也可以操作數(shù)據(jù)。離線狀態(tài)時,把數(shù)據(jù)存儲在本地;在線以后,再把數(shù)據(jù)同步到服務(wù)器上。為了做到這一點(diǎn),開發(fā)者首先必須知道瀏覽器是否在線。HTML5 提供了兩種檢測是否在線的方式:navigator.online 和 online/offline 事件。
- navigator.onLine
navigator.onLine 屬性表示當(dāng)前是否在線。如果為 true, 表示在線;如果為 false, 表示離線。當(dāng)網(wǎng)絡(luò)狀態(tài)發(fā)生變化時,navigator.onLine 的值也隨之變化。開發(fā)者可以通過讀取它的值獲取網(wǎng)絡(luò)狀態(tài)。
- online/offline 事件
當(dāng)開發(fā)離線應(yīng)用時,通過 navigator.onLine 獲取網(wǎng)絡(luò)狀態(tài)通常是不夠的。開發(fā)者還需要在網(wǎng)絡(luò)狀態(tài)發(fā)生變化時立刻得到通知,因此 HTML5 還提供了 online/offline 事件。當(dāng)在線 / 離線狀態(tài)切換時,online/offline 事件將觸發(fā)在 body 元素上,并且沿著 document.body、document 和 window 的順序冒泡。因此,開發(fā)者可以通過監(jiān)聽它們的 online/offline 事件來獲悉網(wǎng)絡(luò)狀態(tài)。
DOM Storage
在開發(fā)支持離線功能的 Web 應(yīng)用時,開發(fā)者需要在本地存儲數(shù)據(jù)。當(dāng)前瀏覽器支持的 cookie 雖然也可以用來存儲數(shù)據(jù),但是 cookie 長度非常?。ㄍǔ?k),而且功能有限。因此,HTML5 中新引入了 DOM Storage 機(jī)制,用于存儲 key/value 對,它的設(shè)計(jì)目標(biāo)是提供大規(guī)模、安全且易用的存儲功能。
DOM Storage 分類
DOM Storage 分為兩類:sessionStorage 和 localStorage。除了以下區(qū)別外,這兩類存儲對象的功能是完全一致的。
- sessionStorage 用于存儲與當(dāng)前瀏覽器窗口關(guān)聯(lián)的數(shù)據(jù)。窗口關(guān)閉后,sessionStorage 中存儲的數(shù)據(jù)將無法使用。
- localStorage 用于長期存儲數(shù)據(jù)。窗口關(guān)閉后,localStorage 中的數(shù)據(jù)仍然可以被訪問。所有瀏覽器窗口可以共享 localStorage 的數(shù)據(jù)。
DOM Storage 接口
每一個 Storage 對象都可以存儲一系列 key/value 對,Storage 接口定義為:
interface Storage {
readonly attribute unsigned long length;
getter DOMString key(in unsigned long index);
getter any getItem(in DOMString key);
setter creator void setItem(in DOMString key, in any data);
deleter void removeItem(in DOMString key);
void clear();
};
|
其中最常用的接口是 getItem 和 setItem。getItem 用于獲取指定 key 的 value,而 setItem 用于設(shè)置指定 key 的 value。
DOM Storage 示例
這里給出一個使用了 sessionStorage 的例子,localStorage 的用法與它相同。首先使用 SetItem 添加了一個名為“userName”的項(xiàng),它的值是“developerworks”。然后,調(diào)用 getItem 得到“userName”的值,并且彈出提示框顯示它。最后,調(diào)用 removeItem 刪除“userName”。
清單 6 DOM Storage 示例代碼
<!DOCTYPE HTML>
<html>
<body>
<script>
// 在 sessionStorage 中定義'userName'變量
sessionStorage.setItem('userName', 'developerworks');
// 訪問'userName'變量
alert("Your user is: " + sessionStorage.getItem('userName'));
// 最后刪除'userName'
sessionStorage.removeItem('userName');
</script>
</body>
</html>
|
Web SQL Database
除了 DOM Storage 以外,HTML5 中還有另外一種數(shù)據(jù)存儲方式 Web SQL Database。它提供了基本的關(guān)系數(shù)據(jù)庫功能,支持頁面上的復(fù)雜的、交互式的數(shù)據(jù)存儲。它既可以用來存儲用戶產(chǎn)生的數(shù)據(jù),也可以作為從服務(wù)器獲取數(shù)據(jù)的本地高速緩存。例如可以把電子郵件、日程等數(shù)據(jù)存儲到數(shù)據(jù)庫中。Web SQL Database 支持?jǐn)?shù)據(jù)庫事務(wù)的概念,從而保證了即使多個瀏覽器窗口操作同一數(shù)據(jù),也不會產(chǎn)生沖突。
Web SQL Database 基本用法
- 創(chuàng)建和打開數(shù)據(jù)庫
使用數(shù)據(jù)庫的第一步是創(chuàng)建并打開數(shù)據(jù)庫,API 是 openDatabase。當(dāng)數(shù)據(jù)庫已經(jīng)存在時,openDatabase 僅僅打開數(shù)據(jù)庫;如果這個數(shù)據(jù)庫不存在,那么就創(chuàng)建一個空數(shù)據(jù)庫并且打開它。openDatabase 的定義是:
Database openDatabase(in DOMString name, in DOMString version,
in DOMString displayName, in unsigned long estimatedSize,
in optional DatabaseCallback creationCallback);
|
name:數(shù)據(jù)庫名。
version:數(shù)據(jù)庫版本。
displayName:顯示名稱。
estimatedSize:數(shù)據(jù)庫預(yù)估長度(以字節(jié)為單位)。
creationCallback:回調(diào)函數(shù)。
- 執(zhí)行事務(wù)處理
在打開數(shù)據(jù)庫以后,就可以使用事務(wù) API transaction。每一個事務(wù)作為操作數(shù)據(jù)庫的原子操作,不會被打斷,從而避免了數(shù)據(jù)沖突。transaction 的定義是:
void transaction(in SQLTransactionCallback callback,
in optional SQLTransactionErrorCallback errorCallback,
in optional SQLVoidCallback successCallback);
|
callback:事務(wù)回調(diào)函數(shù),其中可以執(zhí)行 SQL 語句。
errorCallback:出錯回調(diào)函數(shù)。
successCallback:執(zhí)行成功回調(diào)函數(shù)。
- 執(zhí)行 SQL 語句
在事務(wù)的回調(diào)函數(shù) callback 中,可以執(zhí)行 SQL 語句,API 是 executeSQL。executeSQL 的定義是:
void executeSql(in DOMString sqlStatement,
in optional ObjectArray arguments, in optional SQLStatementCallback callback,
in optional SQLStatementErrorCallback errorCallback);
|
sqlStatement:SQL 語句。
arguments:SQL 語句需要的參數(shù)。
callback:回調(diào)函數(shù)。
errorCallback:出錯回調(diào)函數(shù)。
Web SQL Database 示例
下面通過一個例子說明 Web SQL Database 的基本用法。它首先調(diào)用 openDatabase 創(chuàng)建了名為“fooDB”的數(shù)據(jù)庫。然后使用 transaction 執(zhí)行兩條 SQL 語句。第一條 SQL 語句創(chuàng)建了名為“foo”的表,第二條 SQL 語句向表中插入一條記錄。
清單 7 Web SQL Database 示例代碼
var db = openDatabase('fooDB', '1.0', 'fooDB', 2 * 1024);
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS foo (id unique, text)');
tx.executeSql('INSERT INTO foo (id, text) VALUES (1, "foobar")');
});
|
離線應(yīng)用示例
最后,通過一個例子來說明使用 HTML5 開發(fā)離線應(yīng)用的基本方法。這個例子會用到前面提到的離線資源緩存、在線狀態(tài)檢測和 DOM Storage 等功能。假設(shè)我們開發(fā)一個便簽管理的 Web 應(yīng)用程序,用戶可以在其中添加和刪除便簽。它支持離線功能,允許用戶在離線狀態(tài)下添加、刪除便簽,并且當(dāng)在線以后能夠同步到服務(wù)器上。
- 應(yīng)用程序頁面
這個程序的界面很簡單,如圖 1 所示。用戶點(diǎn)擊“New Note”按鈕可以在彈出框中創(chuàng)建新的便簽,雙擊某便簽就表示刪除它。
圖 1. 應(yīng)用程序頁面
這個頁面的源文件是 index.html,它的代碼如清單 8 所示。
清單 8 頁面 HTML 代碼
<html manifest="notes.manifest">
<head>
<script type="text/javascript" src="server.js"></script>
<script type="text/javascript" src="data.js"></script>
<script type="text/javascript" src="UI.js"></script>
<title>Note List</title>
</head>
<body onload = "SyncWithServer()">
<input type="button" value="New Note" onclick="newNote()">
<ul id="list"></ul>
</body>
</html>
|
在 body 中聲明了一個按鈕和一個無序列表。當(dāng)按下“New Note”按鈕時,newNote 函數(shù)將被調(diào)用,它用來添加一條新的便簽。而無序列表初始為空,它是用來顯示便簽的列表。
cache manifest 文件
定義 cache manifest 文件,聲明需要緩存的資源。在這個例子中,需要緩存“index.html”、“server.js”、“data.js”和“UI.js”等 4 個文件。除了前面列出的“index.html”外,“server.js”、“data.js”和“UI.js”分別包含服務(wù)器相關(guān)、數(shù)據(jù)存儲和用戶界面代碼。cache manifest 文件定義如下。
清單 9 cache manifest 文件
CACHE MANIFEST
index.html
server.js
data.js
UI.js
|
用戶界面代碼
用戶界面代碼定義在 UI.js 中。
清單 10 用戶界面代碼 UI.js
function newNote()
{
var title = window.prompt("New Note:");
if (title)
{
add(title);
}
}
function add(title)
{
// 在界面中添加
addUIItem(title);
// 在數(shù)據(jù)中添加
addDataItem(title);
}
function remove(title)
{
// 從界面中刪除
removeUIItem(title);
// 從數(shù)據(jù)中刪除
removeDataItem(title);
}
function addUIItem(title)
{
var item = document.createElement("li");
item.setAttribute("ondblclick", "remove('"+title+"')");
item.innerHTML=title;
var list = document.getElementById("list");
list.appendChild(item);
}
function removeUIItem(title)
{
var list = document.getElementById("list");
for (var i = 0; i < list.children.length; i++) {
if(list.children[i].innerHTML == title)
{
list.removeChild(list.children[i]);
}
}
}
|
UI.js 中的代碼包含添加便簽和刪除便簽的界面操作。
- 用戶點(diǎn)擊“New Note”按鈕,newNote 函數(shù)被調(diào)用。
- newNote 函數(shù)會彈出對話框,用戶輸入新便簽內(nèi)容。newNote 調(diào)用 add 函數(shù)。
- add 函數(shù)分別調(diào)用 addUIItem 和 addDataItem 添加頁面元素和數(shù)據(jù)。addDataItem 代碼將在后面列出。
- addUIItem 函數(shù)在頁面列表中添加一項(xiàng)。并指明 ondblclick 事件的處理函數(shù)是 remove,使得雙擊操作可以刪除便簽。
- 用戶雙擊某便簽時,調(diào)用 remove 函數(shù)。
- remove 函數(shù)分別調(diào)用 removeUIItem 和 removeDataItem 刪除頁面元素和數(shù)據(jù)。removeDataItem 將在后面列出。
- removeUIItem 函數(shù)刪除頁面列表中的相應(yīng)項(xiàng)。
數(shù)據(jù)存儲代碼
數(shù)據(jù)存儲代碼定義在 data.js 中。
清單 11 數(shù)據(jù)存儲代碼 data.js
var storage = window['localStorage'];
function addDataItem(title)
{
if (navigator.onLine) // 在線狀態(tài)
{
addServerItem(title);
}
else // 離線狀態(tài)
{
var str = storage.getItem("toAdd");
if(str == null)
{
str = title;
}
else
{
str = str + "," + title;
}
storage.setItem("toAdd", str);
}
}
function removeDataItem(title)
{
if (navigator.onLine) // 在線狀態(tài)
{
removeServerItem(title);
}
else // 離線狀態(tài)
{
var str = storage.getItem("toRemove");
if(str == null)
{
str = title;
}
else
{
str = str + "," + title;
}
storage.setItem("toRemove", str);
}
}
function SyncWithServer()
{
// 如果當(dāng)前是離線狀態(tài),不需要做任何處理
if (navigator.onLine == false)return;
var i = 0;
// 和服務(wù)器同步添加操作
var str = storage.getItem("toAdd");
if(str != null)
{
var addItems = str.split(",");
for(i = 0; i<addItems.length; i++)
{
addDataItem(addItems[i]);
}
storage.removeItem("toAdd");
}
// 和服務(wù)器同步刪除操作
str = storage.getItem("toRemove");
if(str != null)
{
var removeItems = str.split(",");
for(i = 0; i<removeItems.length; i++)
{
removeDataItem(removeItems[i]);
}
storage.removeItem("toRemove");
}
// 刪除界面中的所有便簽
var list = document.getElementById("list");
while(list.lastChild != list.firstElementChild)
list.removeChild(list.lastChild);
if(list.firstElementChild)
list.removeChild(list.firstElementChild);
// 從服務(wù)器獲取全部便簽,并顯示在界面中
var allItems = getServerItems();
if(allItems != "")
{
var items = allItems.split(",");
for(i = 0; i<items.length; i++)
{
addUIItem(items[i]);
}
}
}
|
window.addEventListener("online", SyncWithServer,false);
data.js 中的代碼包含添加便簽、刪除便簽和與服務(wù)器同步等數(shù)據(jù)操作。其中用到了 navigator.onLine 屬性、online 事件、DOM Storage 等 HTML5 新功能。
- 通過 navigator.onLine 判斷是否在線。
- 如果在線,那么調(diào)用 addServerItem 直接把數(shù)據(jù)存儲到服務(wù)器上。addServerItem 將在后面列出。
- 如果離線,那么把數(shù)據(jù)添加到 localStorage 的“toAdd”項(xiàng)中。
- 通過 navigator.onLine 判斷是否在線。
- 如果在線,那么調(diào)用 removeServerItem 直接在服務(wù)器上刪除數(shù)據(jù)。removeServerItem 將在后面列出。
- 如果離線,那么把數(shù)據(jù)添加到 localStorage 的“toRemove”項(xiàng)中。
- 數(shù)據(jù)同步:SyncWithServer
在 data.js 的最后一行,注冊了 window 的 online 事件處理函數(shù) SyncWithServer。當(dāng) online 事件發(fā)生時,SyncWithServer 將被調(diào)用。其功能如下。
- 如果 navigator.onLine 表示當(dāng)前離線,則不做任何操作。
- 把 localStorage 中“toAdd”項(xiàng)的所有數(shù)據(jù)添加到服務(wù)器上,并刪除“toAdd”項(xiàng)。
把 localStorage 中“toRemove”項(xiàng)的所有數(shù)據(jù)從服務(wù)器中刪除,并刪除“toRemove”項(xiàng)。
刪除當(dāng)前頁面列表中的所有便簽。
調(diào)用 getServerItems 從服務(wù)器獲取所有便簽,并添加在頁面列表中。getServerItems 將在后面列出。
服務(wù)器相關(guān)代碼
服務(wù)器相關(guān)代碼定義在 server.js 中。
清單 12 服務(wù)器相關(guān)代碼 server.js
function addServerItem(title)
{
// 在服務(wù)器中添加一項(xiàng)
}
function removeServerItem(title)
{
// 在服務(wù)器中刪除一項(xiàng)
}
function getServerItems()
{
// 返回服務(wù)器中存儲的便簽列表
}
|
由于這部分代碼與服務(wù)器有關(guān),這里只說明各個函數(shù)的功能,具體實(shí)現(xiàn)可以根據(jù)不同服務(wù)器編寫代碼。
- 在服務(wù)器中添加一項(xiàng):addServerItem
- 在服務(wù)器中刪除一項(xiàng):removeServerItem
- 返回服務(wù)器中存儲的便簽列表:getServerItems
總結(jié)
本文介紹了 HTML5 為了支持離線應(yīng)用程序新增的強(qiáng)大功能。通過閱讀本文,讀者能夠了解到 HTML5 中離線相關(guān)特性的基本用法,從而掌握利用 HTML5 開發(fā) Web 離線應(yīng)用的方法。
參考資料
關(guān)于作者
郭宗寶現(xiàn)在 IBM 中國軟件開發(fā)實(shí)驗(yàn)室 Lotus 開發(fā)中心工作,目前從事 Lotus Quickr 的客戶支持工作。對 Web 服務(wù),Web 2.0 相關(guān)技術(shù)有濃厚的興趣。 您可以在 My developerWorks 社區(qū)中找到他:http://www.ibm.com/developerworks/mydeveloperworks/profiles/user/nkhit。