先決條件
在本文中,您將使用最新 Web 技術開發 Web 應用程序。這裡的大多數代碼只是 HTML、JavaScript 和 CSS — 任何 Web 開發人員的核心技術。需要的最重要的東西是用於測試代碼的浏覽器。本文中的大多數代碼將運行在最新的桌面浏覽器上,例外的情況會指出來。當然,還必須在移動浏覽器上進行測試,您肯定希望最新的 iPhone 和 Android SDK 支持這些代碼。本文中使用的是 iPhone SDK 3.1.3 和 Android SDK 2.1。
為何要支持您的應用程序離線工作?
由於幾個原因,離線 Web 應用程序對於用戶和開發人員都有吸引力。許多開發人員希望能夠編寫一個能夠在所有最流行的智能手機上運行的 Web 應用程序,而不是為每個平台編寫本機應用程序。這對開發人員很方便,但並不意味這這是用戶的願望。為實現上述目標,移動 Web 應用程序必須能夠提供本機移動應用程序能夠提供的許多(或絕大部分)相同的特性。離線工作肯定是其中一個特性。有些應用程序非常依賴來自 Internet 的數據和服務 — 不管它們是移動 Web 還是本機應用程序。但是,應用程序不能僅僅因為用戶的連接不好而完全失敗。但這正是傳統 Web 應用程序的症結所在。
離線功能使移動 Web 應用程序類似於本機應用程序。此外,離線功能還有其他好處。Web 浏覽器總是緩存靜態資源。它們依賴通過您的 Web 服務器發送的 HTTP 響應頭部中的元數據來檢索渲染頁面所需的 HTML、JavaScript、CSS 和圖像。如果渲染頁面所需的所有資源都已緩存,那麼頁面就可以非常迅速地加載。但是,如果某個資源沒有緩存,那麼它將極大地降低頁面載入速度。這種情況經常發生,實在是讓人無法忍受。也許一個 CSS 文件擁有一個與其他所有文件都不同的 Cache-Control 頭部,或者,也許是浏覽器因為耗盡了已分配空間而無法緩存。
使用離線應用程序,您可以確保所有資源都會被緩存。浏覽器將總是從緩存加載所有資源,盡管您也能夠控制哪些資源從緩存加載。一種常見的 Ajax 技巧是將一個額外的時間戳參數添加到 Ajax GET 請求(或者,更糟糕的是在應該使用 GET 時使用 POST)來避免浏覽器緩存一個響應。您無需使用這種技巧來支持離線 Web 應用程序。
離線應用程序聽起來挺棒,那麼創建一個離線應用程序一定很復雜,對吧?實際上,創建方法非常簡單,只需完成下面三個步驟:
- 創建一個在線清單文件。
- 告知浏覽器這個清單文件。
- 設置服務器上的 MIME 類型。
離線清單
創建過程涉及一個關鍵文件:您的應用程序的緩存清單。這個文件告知浏覽器要緩存(或者,不緩存)的確切內容。這成為您的應用程序的事實來源。清單 1 展示了一個簡單緩存清單示例。
清單 1. 簡單緩存清單
JavaScript Code復制內容到剪貼板
- CACHE MANIFEST
- # Version 0.1
- offline.html
- /iui/iui.js
- /iui/iui.css
- /iui/loading.gif
- /iui/backButton.png
- /iui/blueButton.png
- /iui/cancel.png
- /iui/grayButton.png
- /iui/listArrow.png
- /iui/listArrowSel.png
- /iui/listGroup.png
- /iui/pinstripes.png
- /iui/redButton.png
- /iui/selection.png
- /iui/thumb.png
- /iui/toggle.png
- /iui/toggleOn.png
- /iui/toolbar.png
- /iui/whiteButton.png
- /images/gymnastics.jpg
- /images/soccer.png
- /images/gym.jpg
- /images/soccer.jpg
這個文件列示了您的應用程序正常工作所需的所有文件,其中包括 HTML 文件、JavaScript、 CSS 和圖像。它還可以包括視頻、PDFs、XML 文件,等等。注意,本示例中的所有 URLs 都是相對的。任何相對 URLs 必須相對於緩存清單文件。在本例中,緩存清單文件位於您的 Web 應用程序的根目錄。比較 清單 2 中的目錄結構和 清單 1 中的相對 URLs。
清單 2. Web 應用程序的文本版目錄結構
JavaScript Code復制內容到剪貼板
- Name
- V images
- gymnastics.jpg
- soccer.png
- V iui
- backButton.png
- blueButton.png
- cancel.png
- grayButton.png
- iui.css-logo-touch-icon.png
- iui.css
- iui.js
- iuix.css
- iuix.js
- listArrow.png
- listArrowSel.png
- listGroup.png
- loading.gif
- pinstripes.png
- redButton.png
- selection.png
- thumb.png
- toggle.png
- toggleOn.png
- toolbar.png
- toolButton.png
- whiteButton.png
- manifest.mf
- offline.html
- > WEB-INF
您可能已經注意到,這個應用程序正在使用 iUI 框架。這個一個流行的 JavaScript+CSS 工具包,用於向移動 Web 應用程序提供本機 iPhone 應用程序觀感。如 清單 1 和 清單 2 所示,這個框架使用幾個圖像來伴隨它的 JavaScript 和 CSS 文件。但是,只要列示在清單中,所有這些文件都將被浏覽器緩存並可在離線模式中使用。
清單 1 中另一個需要注意的關鍵點是版本信息,它並不是規范的一部分。事實上,它只是文件中的一個注釋。但是,擁有這樣的信息很關鍵,因為您可以使用該信息來告知浏覽器您的應用程序有一個新版本。想想看,假如您更改了一些 HTML 或 JavaScript,或者甚至只是更換了一個圖像。如果您不修改清單,則浏覽器永遠不會費心去加載修改後的資源的新版本。緩存清單沒有過期一說,因此所有資源都將保持緩存狀態,除非用戶清空緩存或清單文件更改。浏覽器將檢查是否存在新的清單文件。要表明一個新的清單文件,您只需更改現有清單文件的部分或全部內容。返回到您的修改頁面上的 HTML 的示例,如果您更改了 HTML 並更改了清單文件中的版本字符串,那麼浏覽器將知道資源已經被更改並再次下載它們。將版本號放置到注釋中是管理這個生命周期的一種簡單方法。
告知浏覽器關於清單的信息
要啟用您的 Web 應用程序的離線緩存,還需要向浏覽器提供一些信息。Web 浏覽器需要知道您想啟用緩存,以及到哪裡去找到您的緩存清單文件。清單 3 展示了一種非常簡單的方法。
清單 3. 啟用了離線功能的 Web 頁面
XML/HTML Code復制內容到剪貼板
- <!DOCTYPE html>
- <html>
- <html manifest="manifest.mf">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <meta name="viewport" content="width=device-width; initial-scale=1.0;
- maximum-scale=1.0; user-scalable=0;"/>
- <meta name="apple-touch-fullscreen" content="YES" />
- <link rel="apple-touch-icon" href="/iui/iui-logo-touch-icon.png" />
- <style type="text/css" media="screen">@import "/iui/iui.css";</style>
- <script type="application/x-javascript" src="/iui/iui.js"></script>
- <title>Let's do it offline</title>
- </head>
- <body>
- <div class="toolbar">
- <h1 id="pageTitle">Going offline</h1>
- <a id="backButton" class="button" href="#"></a>
- </div>
- <ul id="menu" title="Sports" selected="true">
- <li><a href="#gym"><img height="80" width="80"
- src="/images/gym.jpg" align="middle"/>
- <span style="display:inline-block;
- vertical-align:middle">Gymnastics</span></a></li>
- <li><a href="#soccer"><img src="/images/soccer.jpg"
- align="middle"/>
- <span style="display:inline-block;
- vertical-align:middle">Soccer</span></a></li>
- </ul>
- <div id="gym" title="Gymnastics" class="panel">
- <img src="/images/gymnastics.jpg" alt="Boys Gymnastics"/>
- </div>
- <div id="soccer" title="Soccer" class="panel">
- <img src="/images/soccer.png" alt="Boys Soccer"/>
- </div>
- </body>
- </html>
這個 HTML 最重要的一點是根 html 元素。注意,該元素擁有一個稱為 manifest 的屬性。正是這個屬性告知浏覽器這個頁面可以離線工作。manifest 參數的值是到該 Web 頁面的緩存清單文件的 URL。重申一遍,這個 URL 可以是一個完整 URL,盡管它在這裡是一個相對(於指定 Web 頁面的)URL。這裡需要注意的另一點是該頁面的 DOCTYPE。這是用於 HTML 5 Web 頁面的規范 doctype。離線 Web 應用程序規范並不強制您使用這個 DOCTYPE;但是,建議您這樣做。否則,某些浏覽器可能不會將頁面識別為 HTML 5 頁面,並且可能會忽略緩存清單。這個 HTML 的剩余部分只是使用 iUI 的一個簡單示例。圖 1 展示了這個頁面在 iPhone 模擬器上的外觀。
圖 1. 運行在 iPhone 模擬器上的離線 Web 應用程序
測試離線應用程序可能有點麻煩。如果可能,最簡單的測試方式是將您的應用程序部署到一個 Web 服務器上。然後,您可以訪問這個頁面一次,關閉您的 Internet 連接,然後嘗試再次訪問。如果出現任何失敗,那麼您可能在緩存清單中遺漏了一些文件。在進行上述測試之前,您需要對您的 Web 服務器進行一些關鍵配置。
Web 服務器配置
清單 3 展示您通過使用您的 Web 頁面的根 html 元素上的 manifest 屬性來表明您的緩存清單的位置。但是,緩存清單規范規定,浏覽器在下載和處理緩存清單時必須執行一個額外的驗證步驟,即檢查緩存清單的 MIME 類型,該類型必須為 text/cache-manifest。通常,這意味著您需要配置您的 Web 服務器來設置一個靜態文件的 MIME 類型,或者,您必須編寫一些代碼來動態創建該文件並設置 MIME 類型。前者當然是更有效的方法,但是有時您需要使用後一種方法,比如您沒有對服務器配置的控制權(比如在一個共享或托管環境中)。如果您對服務器擁有控制權且正在使用一個 Java™ 應用程序服務器,您可以在 Web 應用程序的 web.xml 文件中配置這個參數。清單 4 展示了一個配置示例:
清單 4. 配置 web.xml 來設置 MIME 類型
XML/HTML Code復制內容到剪貼板
- <?xml version="1.0" encoding="utf-8"?>
- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
- <!-- Servlets go here -->
- <mime-mapping>
- <extension>mf</extension>
- <mime-type>text/cache-manifest</mime-type>
- </mime-mapping>
- <welcome-file-list>
- <welcome-file>index.html</welcome-file>
- </welcome-file-list>
- </web-app>
顯然,這裡的關鍵部分是 mime-mapping 元素。在本例中,您的設置的含義是:對於以 .mf 擴展名結尾的任意文件,將它們的 MIME 類型設置為 text/cache-manifest。當然,一種甚至更有效的方法是從專用於提供靜態內容的服務器(比如一個 Apache Web 服務器)提供這樣的文件。在一個典型 Apache 安裝中,您只需修改 httpd/conf 目錄中的 mime.types 文件,如 清單 5 所示。
清單 5. 在 mime.types 中設置 MIME 類型
JavaScript Code復制內容到剪貼板
- # This file controls what Internet media types are sent to the client for
- # given file extension(s). Sending the correct media type to the client
- # is important so they know how to handle the content of the file.
- # Extra types can either be added here or by using an AddType directive
- # in your config files. For more information about Internet media types,
- # please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type
- # registry is at <http://www.iana.org/assignments/media-types/>.
-
- # MIME type Extensions
- text/cache-manifest mf
- # many more mappings...
在這兩個示例中,您都使用 mf 作為您的清單文件的擴展名,因此該文件是 manifest.mf。這個擴展名可以任意選擇,您可以使用 .manifest 或 .foo,只要這個清單文件的擴展名匹配在您的配置文件中的映射中使用的擴展名。注意,其他應用程序和 Web 服務器可能擁有不同的配置機制。現在您已經看到了使用 HTML 5 創建離線移動 Web 應用程序中的關鍵因素,下面我們來看一個更復雜的示例,探索離線移動 Web 應用程序的更多功能。
高級示例
在上一個示例中,所有內容都是靜態的。能夠以離線模式看到所有內容讓人感覺不錯,但更典型的應用程序需要從它的服務器和 Web 服務讀取動態數據。為使您的示例更真實,可以拖入一些來自 Twitter 的數據。如果您閱讀了本系列之前的文章,那麼您將對此感到熟悉(參見 參考資料)。首先,在 清單 6 中查看這個示例修改後的 HTML。
清單 6. 修改後的 HTML
XML/HTML Code復制內容到剪貼板
- <body onload="init()">
- <div class="toolbar">
- <h1 id="pageTitle">Going offline</h1>
- <a id="backButton" class="button" href="#"></a>
- </div>
- <ul id="menu" title="Sports" selected="true">
- <li><a href="#gym">
- <img height="80" width="80" src="/images/gym.jpg" align="middle"/>
- <span style="display:inline-block; vertical-align:middle">Gymnastics</span>
- </a></li>
- <li><a href="#soccer"><img src="/images/soccer.jpg" align="middle"/>
- <span style="display:inline-block; vertical-align:middle">Soccer</span>
- </a></li>
- <li id="online" style="display: none"><img src="/images/online.jpg"/></li>
- </ul>
- <ul id="gym" title="Gymnastics"></ul>
- <ul id="soccer" title="Soccer"></ul>
- </body>
主要區別是現在列示了 gym 和 soccer 元素,且這兩個元素為空。您將分別使用來自 Twitter 的關於體操和足球的 tweets 來填充它們。還要注意一個 id 為 online 的列表項元素,該元素顯示一個圖像,用於向用戶表明應用程序是在線還是離線。但是,這個元素默認隱藏,也就是說,默認模式是離線。 這個 body 元素規定:一旦這個 body 加載,就會調用一個 init() 函數。清單 7 展示了這個函數。
清單 7. 頁面初始化 JavaScript
JavaScript Code復制內容到剪貼板
- function init(){
- if (navigator.onLine){
- searchTwitter("gymnastics", "showGymTweets");
- searchTwitter("soccer", "showSoccerTweets");
- $("online").style.display = "inline";
- }
- gymTweets = localStorage.getItem("gymnastics");
- if (gymTweets){
- gymTweets = JSON.parse(gymTweets);
- showGymTweets();
- }
- soccerTweets = localStorage.getItem("soccer");
- if (soccerTweets){
- soccerTweets = JSON.parse(soccerTweets);
- showSoccerTweets();
- }
- document.body.addEventListener("online", function() {
- $("online").style.display= "inline";
- applicationCache.update();
- applicationCache.addEventListener("updateready", function() {
- applicationCache.swapCache();
- }, false);
- }, false);
- document.body.addEventListener("offline", function() {
- $("online").style.display = "none";
- }, false);
- }
這個代碼所做的第一件事就是檢查您是在線還是離線。如果在線,則顯示在線圖像。更重要的是,如果您在線,那麼它將通過調用searchTwitter 函數來從 Twitter 加載數據。同樣,這是一種允許您使用 JSONP 直接從浏覽器搜索 Twitter 的技術(在本系列前面的文章中解釋 — 參見 參考資料)。接下來,試圖從 localStorage 加載現有 tweets。如果您熟悉 localStorage,就會知道這是另一個能夠在離線模式下很好地工作的 HTML 5 功能。參閱本系列第 2 部分進一步了解它(參見 參考資料)。注意,為進行新搜索(在檢測到您處於在線時啟動)並加載本地保存的 tweets,showGymTweets 和 showSoccerTweets 函數將被調用。它們是相似的函數,清單 8展示了 showGymTweets。
清單 8. 顯示 Gym tweets
JavaScript Code復制內容到剪貼板
- function showGymTweets(response){
- var gymList = $("gym");
- gymList.innerHTML = "";
- if (gymTweets){
- if (response){
- gymTweets = response.results.reverse().concat(gymTweets);
- }
- } else {
- gymTweets = response.results.reverse();
- }
- showTweets(gymTweets, gymList);
- localStorage.setItem("gymnastics", JSON.stringify(gymTweets));
- }
這個函數能夠顯示本地存儲的 tweets,來自 Twitter 的新 tweets,或者二者的結合(如果二者都存在)。最重要的是,它本地存儲所有資源,構建您的本地 tweets 數據緩存。這是用於同時管理本地緩存的數據和來自服務器的實時數據的典型代碼。它允許應用程序順暢運行,無論在線還是離線。
返回到 清單 7,需要做的最後一件事是注冊事件處理程序。這將告知您浏覽器的在線或離線狀態何時改變。至少,您可以更改在線圖像,並在是否顯示圖像之間切換。在這個應用程序從離線轉為在線的例子中,您訪問 applicationCache 對象。這個對象表示按照緩存清單中的聲明方式緩存的所有資源。在本例中,您調用它的 update 方法。該方法指示浏覽器檢查它是否檢測到 applicationCache的一個更新。如前所述,浏覽器首先檢查緩存清單文件的一個更新。您添加另一個事件監聽器來檢查可用緩存的一個更新。如果存在更新,則調用 applicationCache 上的 swapCache 方法。該方法將重新加載在緩存清單文件中指定的所有文件。
談到緩存清單文件,您需要對這個高級示例進行最後的完善。緩存清單文件需要按 清單 9 所示修改。
清單 9. 修改後的緩存清單
JavaScript Code復制內容到剪貼板
- CACHE MANIFEST
- # Version 0.2
- CACHE:
- offline.html
- json2.js
- /iui/iui.js
- /iui/iui.css
- /iui/loading.gif
- /iui/backButton.png
- /iui/blueButton.png
- /iui/cancel.png
- /iui/grayButton.png
- /iui/listArrow.png
- /iui/listArrowSel.png
- /iui/listGroup.png
- /iui/pinstripes.png
- /iui/redButton.png
- /iui/selection.png
- /iui/thumb.png
- /iui/toggle.png
- /iui/toggleOn.png
- /iui/toolbar.png
- /iui/whiteButton.png
- /images/gym.jpg
- /images/soccer.jpg
- /images/online.jpg
-
- NETWORK:
- http://search.twitter.com/
在這個示例中,您將一個顯式 CACHE 區域添加到清單。清單可以擁有多個不同的區域,但是,如果它只有一個區域,那麼這個區域就假定為 CACHE 且可以省略。這裡之所以要使用顯式區域,其原因是您還有一個 NETWORK 區域。這個區域向浏覽器表明:來自指定域(這裡是 search.twitter.com)的任何數據都應該從網絡獲取且從不緩存。由於您正在本地緩存來自 Twitter 的搜索結果,您肯定不希望浏覽器再執行間接的查詢緩存。現在,這段代碼就位後,應用程序將總是從 Twitter 加載實時 tweets。但是,它將總是緩存那些 tweets 並使它們對用戶可用,即使在用戶的設備喪失連通性時。
結束語
自從 Mosaic 浏覽器風靡以來,Web 應用程序已經走過了漫長的發展歷程。移動 Web 應用程序發展得甚至更快。只使用 Wireless Markup Language (WML) 的 WAP 手機的日子走到頭了。現在您對您的移動浏覽器提出要求甚至比它們的桌面浏覽器還要多。離線功能就是這樣的要求之一。HTML 5 中的標准經歷了很長時間,以簡化開發人員離線支持他們的移動 Web 應用程序的工作。在本系列下一篇文章中,您將看到另一個 HTML 5 標准 —— Web Workers —— 如何極大地改善移動 Web 應用程序的性能。