1. 程式人生 > >HTML5 離線

HTML5 離線

轉:https://www.ibm.com/developerworks/cn/web/1011_guozb_html5off/


使用 HTML5 開發離線應用

Web2.0 技術鼓勵個人的參與,每個人都是 Web 內容的撰寫者。如果 Web 應用能夠提供離線的功能,讓使用者在沒有網路的地方(例如飛機上)和時候(網路壞了),也能進行內容撰寫,等到有網路的時候,再同步到 Web 上,就大大方便了使用者的使用。HTML5 作為新一代的 HTML 標準,包含了對離線功能的支援。本文介紹了 HTML5 離線功能中的離線資源快取、線上狀態檢測、本地資料儲存等內容,並舉例說明了如何使用 HTML5 的新特性開發離線應用。

郭 宗寶, 軟體工程師, IBM

2010 年 11 月 24 日

  • +內容

在 IBM Bluemix 雲平臺上開發並部署您的下一個應用。

HTML5 離線功能介紹

HTML5 是目前正在討論的新一代 HTML 標準,它代表了現在 Web 領域的最新發展方向。在 HTML5 標準中,加入了新的多樣的內容描述標籤,直接支援表單驗證、視訊音訊標籤、網頁元素的拖拽、離線儲存和工作執行緒等功能。其中一個新特性就是對離線應用開發的支援。

在開發支援離線的 Web 應用程式時,開發者通常需要使用以下三個方面的功能:

  1. 離線資源快取:需要一種方式來指明應用程式離線工作時所需的資原始檔。這樣,瀏覽器才能在線上狀態時,把這些檔案快取到本地。此後,當用戶離線訪問應用程式時,這些資原始檔會自動載入,從而讓使用者正常使用。HTML5 中,通過 cache manifest 檔案指明需要快取的資源,並支援自動和手動兩種快取更新方式。
  2. 線上狀態檢測:開發者需要知道瀏覽器是否線上,這樣才能夠針對線上或離線的狀態,做出對應的處理。在 HTML5 中,提供了兩種檢測當前網路是否線上的方式。
  3. 本地資料儲存:離線時,需要能夠把資料儲存到本地,以便線上時同步到伺服器上。為了滿足不同的儲存需求,HTML5 提供了 DOM Storage 和 Web SQL Database 兩種儲存機制。前者提供了易用的 key/value 對儲存方式,而後者提供了基本的關係資料庫儲存功能。

儘管 HTML5 還處於草稿狀態,但是各大主流瀏覽器都已經實現了其中的很多功能。Chrome、Firefox、Safari 和 Opera 的最新版本都對 HTML5 離線功能提供了完整的支援。IE8 也支援了其中的線上狀態檢測和 DOM Storage 功能。下面將具體介紹 HTML5 離線功能中的離線資源快取、線上狀態檢測、DOM Storage 和 Web SQL Database,最後通過一個簡單的 Web 程式說明使用 HTML5 開發離線應用的方法。

離線資源快取

為了能夠讓使用者在離線狀態下繼續訪問 Web 應用,開發者需要提供一個 cache manifest 檔案。這個檔案中列出了所有需要在離線狀態下使用的資源,瀏覽器會把這些資源快取到本地。本節先通過一個例子展示 cache manifest 檔案的用途,然後詳細描述其書寫方法,最後說明快取的更新方式。

cache manifest 示例

我們通過 W3C 提供的示例來說明。Clock Web 應用由三個檔案“clock.html”、“clock.css”和“clock.js”組成。

清單 1. Clock 應用程式碼
 <!-- 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);

當用戶在離線狀態下訪問“clock.html”時,頁面將無法展現。為了支援離線訪問,開發者必須新增 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> 標籤的 manifest 屬性設定為“clock.manifest”。修改後的“clock.html”程式碼如下。

清單 3. 設定 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>

修改後,當用戶線上訪問“clock.html”時,瀏覽器會快取“clock.html”、“clock.css”和“clock.js”檔案;而當用戶離線訪問時,這個 Web 應用也可以正常使用了。

cache manifest 格式

下面說明書寫 cache manifest 檔案需要遵循的格式。

  1. 首行必須是 CACHE MANIFEST。
  2. 其後,每一行列出一個需要快取的資原始檔名。
  3. 可根據需要列出線上訪問的白名單。白名單中的所有資源不會被快取,在使用時將直接線上訪問。宣告白名單使用 NETWORK:識別符號。
  4. 如果在白名單後還要補充需要快取的資源,可以使用 CACHE:識別符號。
  5. 如果要宣告某 URI 不能訪問時的替補 URI,可以使用 FALLBACK:識別符號。其後的每一行包含兩個 URI,當第一個 URI 不可訪問時,瀏覽器將嘗試使用第二個 URI。
  6. 註釋要另起一行,以 # 號開頭。

清單 4 的程式碼中給出了 cache manifest 中各類識別符號的使用示例。

清單 4. cache manifest 示例程式碼
 CACHE MANIFEST 
 # 上一行是必須書寫的。

 images/sound-icon.png 
 images/background.png 

 NETWORK: 
 comm.cgi

# 下面是另一些需要快取的資源,在這個示例中只有一個 css 檔案。

 CACHE: 
 style/default.css 

 FALLBACK: 
 /files/projects /projects

更新快取

應用程式可以等待瀏覽器自動更新快取,也可以使用 Javascript 介面手動觸發更新。

  1. 自動更新

    瀏覽器除了在第一次訪問 Web 應用時快取資源外,只會在 cache manifest 檔案本身發生變化時更新快取。而 cache manifest 中的資原始檔發生變化並不會觸發更新。

  2. 手動更新

    開發者也可以使用 window.applicationCache 的介面更新快取。方法是檢測 window.applicationCache.status 的值,如果是 UPDATEREADY,那麼可以呼叫 window.applicationCache.update() 更新快取。示範程式碼如下。

    清單 5 手動更新快取
     if (window.applicationCache.status == window.applicationCache.UPDATEREADY) 
    {
     window.applicationCache.update(); 
    }

線上狀態檢測

如果 Web 應用程式僅僅是一些靜態頁面的組合,那麼通過 cache manifest 快取資原始檔以後,就可以支援離線訪問了。但是隨著網際網路的發展,特別是 Web2.0 概念流行以來,使用者的提交的資料漸漸成為網際網路的主流。那麼在開發支援離線的 Web 應用時,就不能僅僅滿足於靜態頁面的展現,還必需考慮如何讓使用者在離線狀態下也可以操作資料。離線狀態時,把資料儲存在本地;線上以後,再把資料同步到伺服器上。為了做到這一點,開發者首先必須知道瀏覽器是否線上。HTML5 提供了兩種檢測是否線上的方式:navigator.online 和 online/offline 事件。

  1. navigator.onLine

    navigator.onLine 屬性表示當前是否線上。如果為 true, 表示線上;如果為 false, 表示離線。當網路狀態發生變化時,navigator.onLine 的值也隨之變化。開發者可以通過讀取它的值獲取網路狀態。

  2. online/offline 事件

    當開發離線應用時,通過 navigator.onLine 獲取網路狀態通常是不夠的。開發者還需要在網路狀態發生變化時立刻得到通知,因此 HTML5 還提供了 online/offline 事件。當線上 / 離線狀態切換時,online/offline 事件將觸發在 body 元素上,並且沿著 document.body、document 和 window 的順序冒泡。因此,開發者可以通過監聽它們的 online/offline 事件來獲悉網路狀態。

DOM Storage

在開發支援離線功能的 Web 應用時,開發者需要在本地儲存資料。當前瀏覽器支援的 cookie 雖然也可以用來儲存資料,但是 cookie 長度非常小(通常幾 k),而且功能有限。因此,HTML5 中新引入了 DOM Storage 機制,用於儲存 key/value 對,它的設計目標是提供大規模、安全且易用的儲存功能。

DOM Storage 分類

DOM Storage 分為兩類:sessionStorage 和 localStorage。除了以下區別外,這兩類儲存物件的功能是完全一致的。

  1. sessionStorage 用於儲存與當前瀏覽器視窗關聯的資料。視窗關閉後,sessionStorage 中儲存的資料將無法使用。
  2. localStorage 用於長期儲存資料。視窗關閉後,localStorage 中的資料仍然可以被訪問。所有瀏覽器視窗可以共享 localStorage 的資料。

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 用於設定指定 key 的 value。

DOM Storage 示例

這裡給出一個使用了 sessionStorage 的例子,localStorage 的用法與它相同。首先使用 SetItem 添加了一個名為“userName”的項,它的值是“developerworks”。然後,呼叫 getItem 得到“userName”的值,並且彈出提示框顯示它。最後,呼叫 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 中還有另外一種資料儲存方式 Web SQL Database。它提供了基本的關係資料庫功能,支援頁面上的複雜的、互動式的資料儲存。它既可以用來儲存使用者產生的資料,也可以作為從伺服器獲取資料的本地快取記憶體。例如可以把電子郵件、日程等資料儲存到資料庫中。Web SQL Database 支援資料庫事務的概念,從而保證了即使多個瀏覽器視窗操作同一資料,也不會產生衝突。

Web SQL Database 基本用法

  1. 建立和開啟資料庫

使用資料庫的第一步是建立並開啟資料庫,API 是 openDatabase。當資料庫已經存在時,openDatabase 僅僅開啟資料庫;如果這個資料庫不存在,那麼就建立一個空資料庫並且開啟它。openDatabase 的定義是:

  Database openDatabase(in DOMString name, in DOMString version, 
  in DOMString displayName, in unsigned long estimatedSize, 
  in optional DatabaseCallback creationCallback);

name:資料庫名。

version:資料庫版本。

displayName:顯示名稱。

estimatedSize:資料庫預估長度(以位元組為單位)。

creationCallback:回撥函式。

  1. 執行事務處理

    在開啟資料庫以後,就可以使用事務 API transaction。每一個事務作為操作資料庫的原子操作,不會被打斷,從而避免了資料衝突。transaction 的定義是:

     void transaction(in SQLTransactionCallback callback, 
     in optional SQLTransactionErrorCallback errorCallback, 
     in optional SQLVoidCallback successCallback);

    callback:事務回撥函式,其中可以執行 SQL 語句。

    errorCallback:出錯回撥函式。

    successCallback:執行成功回撥函式。

  2. 執行 SQL 語句

    在事務的回撥函式 callback 中,可以執行 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 語句需要的引數。

    callback:回撥函式。

    errorCallback:出錯回撥函式。

Web SQL Database 示例

下面通過一個例子說明 Web SQL Database 的基本用法。它首先呼叫 openDatabase 建立了名為“fooDB”的資料庫。然後使用 transaction 執行兩條 SQL 語句。第一條 SQL 語句建立了名為“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")'); 
 });

離線應用示例

最後,通過一個例子來說明使用 HTML5 開發離線應用的基本方法。這個例子會用到前面提到的離線資源快取、線上狀態檢測和 DOM Storage 等功能。假設我們開發一個便籤管理的 Web 應用程式,使用者可以在其中新增和刪除便籤。它支援離線功能,允許使用者在離線狀態下新增、刪除便籤,並且當線上以後能夠同步到伺服器上。

  1. 應用程式頁面

    這個程式的介面很簡單,如圖 1 所示。使用者點選“New Note”按鈕可以在彈出框中建立新的便籤,雙擊某便籤就表示刪除它。

    圖 1. 應用程式頁面
    圖 1. 應用程式頁面

    這個頁面的原始檔是 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 中聲明瞭一個按鈕和一個無序列表。當按下“New Note”按鈕時,newNote 函式將被呼叫,它用來新增一條新的便籤。而無序列表初始為空,它是用來顯示便籤的列表。

  2. cache manifest 檔案

    定義 cache manifest 檔案,宣告需要快取的資源。在這個例子中,需要快取“index.html”、“server.js”、“data.js”和“UI.js”等 4 個檔案。除了前面列出的“index.html”外,“server.js”、“data.js”和“UI.js”分別包含伺服器相關、資料儲存和使用者介面程式碼。cache manifest 檔案定義如下。

    清單 9 cache manifest 檔案
     CACHE MANIFEST 
     index.html 
     server.js 
     data.js 
     UI.js
  3. 使用者介面程式碼

    使用者介面程式碼定義在 UI.js 中。

    清單 10 使用者介面程式碼 UI.js
     function newNote() 
     { 
        var title = window.prompt("New Note:"); 
        if (title) 
        { 
            add(title); 
        } 
     } 
    
     function add(title) 
     { 
        // 在介面中新增
        addUIItem(title); 
        // 在資料中新增
        addDataItem(title); 
     } 
    
     function remove(title) 
     { 
        // 從介面中刪除
        removeUIItem(title); 
        // 從資料中刪除
        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 中的程式碼包含新增便籤和刪除便籤的介面操作。

    • 新增便籤
    1. 使用者點選“New Note”按鈕,newNote 函式被呼叫。
    2. newNote 函式會彈出對話方塊,使用者輸入新便籤內容。newNote 呼叫 add 函式。
    3. add 函式分別呼叫 addUIItem 和 addDataItem 新增頁面元素和資料。addDataItem 程式碼將在後面列出。
    4. addUIItem 函式在頁面列表中新增一項。並指明 ondblclick 事件的處理函式是 remove,使得雙擊操作可以刪除便籤。
    • 刪除便籤
    1. 使用者雙擊某便籤時,呼叫 remove 函式。
    2. remove 函式分別呼叫 removeUIItem 和 removeDataItem 刪除頁面元素和資料。removeDataItem 將在後面列出。
    3. removeUIItem 函式刪除頁面列表中的相應項。
  4. 資料儲存程式碼

    資料儲存程式碼定義在 data.js 中。

    清單 11 資料儲存程式碼 data.js
     var storage = window['localStorage']; 
    
     function addDataItem(title) 
     { 
        if (navigator.onLine) // 線上狀態
        { 
            addServerItem(title); 
        } 
        else // 離線狀態
        { 
            var str = storage.getItem("toAdd"); 
            if(str == null) 
            { 
                str = title; 
            } 
            else 
            { 
                str = str + "," + title; 
            } 
            storage.setItem("toAdd", str); 
        } 
     } 
    
     function removeDataItem(title) 
     { 
        if (navigator.onLine) // 線上狀態
        { 
            removeServerItem(title); 
        } 
        else // 離線狀態
        { 
            var str = storage.getItem("toRemove"); 
            if(str == null) 
            { 
                str = title; 
            } 
            else 
            { 
                str = str + "," + title; 
            } 
            storage.setItem("toRemove", str); 
        } 
     } 
      
     function SyncWithServer() 
     { 
        // 如果當前是離線狀態,不需要做任何處理
        if (navigator.onLine == false)return; 
    
        var i = 0; 
        // 和伺服器同步新增操作
        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"); 
        } 
    
        // 和伺服器同步刪除操作
        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);        
    
        // 從伺服器獲取全部便籤,並顯示在介面中
        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 中的程式碼包含新增便籤、刪除便籤和與伺服器同步等資料操作。其中用到了 navigator.onLine 屬性、online 事件、DOM Storage 等 HTML5 新功能。

    • 新增便籤:addDataItem
    1. 通過 navigator.onLine 判斷是否線上。
    2. 如果線上,那麼呼叫 addServerItem 直接把資料儲存到伺服器上。addServerItem 將在後面列出。
    3. 如果離線,那麼把資料新增到 localStorage 的“toAdd”項中。
    • 刪除便籤:removeDataItem
    1. 通過 navigator.onLine 判斷是否線上。
    2. 如果線上,那麼呼叫 removeServerItem 直接在伺服器上刪除資料。removeServerItem 將在後面列出。
    3. 如果離線,那麼把資料新增到 localStorage 的“toRemove”項中。
    • 資料同步:SyncWithServer

    在 data.js 的最後一行,註冊了 window 的 online 事件處理函式 SyncWithServer。當 online 事件發生時,SyncWithServer 將被呼叫。其功能如下。

    1. 如果 navigator.onLine 表示當前離線,則不做任何操作。
    2. 把 localStorage 中“toAdd”項的所有資料新增到伺服器上,並刪除“toAdd”項。
    3. 把 localStorage 中“toRemove”項的所有資料從伺服器中刪除,並刪除“toRemove”項。
    4. 刪除當前頁面列表中的所有便籤。
    5. 呼叫 getServerItems 從伺服器獲取所有便籤,並新增在頁面列表中。getServerItems 將在後面列出。
  5. 伺服器相關程式碼

    伺服器相關程式碼定義在 server.js 中。

    清單 12 伺服器相關程式碼 server.js
     function addServerItem(title) 
     { 
        // 在伺服器中新增一項
     } 
    
     function removeServerItem(title) 
     { 
        // 在伺服器中刪除一項
     } 
    
     function getServerItems() 
     { 
        // 返回伺服器中儲存的便籤列表
     }

    由於這部分程式碼與伺服器有關,這裡只說明各個函式的功能,具體實現可以根據不同伺服器編寫程式碼。

    • 在伺服器中新增一項:addServerItem
    • 在伺服器中刪除一項:removeServerItem
    • 返回伺服器中儲存的便籤列表:getServerItems

總結

本文介紹了 HTML5 為了支援離線應用程式新增的強大功能。通過閱讀本文,讀者能夠了解到 HTML5 中離線相關特性的基本用法,從而掌握利用 HTML5 開發 Web 離線應用的方法。