使用應用程式快取的新手與#39

引言

方便使用者離線存取網頁式應用程式也越來越重要。 可以。所有瀏覽器都可長時間快取網頁和資源 (如果需要的話),但瀏覽器也可以隨時將個別項目移出快取,以騰出空間進行其他操作。HTML5 處理了使用 ApplicationCache 介面離線的一些惱人問題。使用快取介面可為應用程式帶來以下三項好處:

  1. 離線瀏覽 - 使用者可以在離線時瀏覽整個網站
  2. 速度:資源直接來自磁碟,沒有往返網路的情形。
  3. 彈性 - 如果網站因「維護」問題而停擺 (例如有人不小心破壞網站),使用者將能獲得離線體驗

應用程式快取 (或 AppCache) 可讓開發人員指定瀏覽器應快取哪些檔案,並提供給離線使用者。即使使用者在離線狀態下按下重新整理按鈕,應用程式仍會正確載入並正常運作。

快取資訊清單檔案

快取資訊清單檔案是簡單的文字檔案,其中列出瀏覽器應快取的資源,以便離線存取。

參照資訊清單檔案

如要為應用程式啟用應用程式快取,請在文件的 html 標記中加入資訊清單屬性:

<html manifest="example.appcache">
  ...
</html>

您要快取的網頁應用程式每個網頁都必須加入 manifest 屬性。除非資訊清單檔案明確列出網頁,否則瀏覽器不會快取不含 manifest 屬性的網頁。這表示使用者前往的任何頁面都含有 manifest,會以隱含方式加入應用程式快取中。因此,您不需要在資訊清單中列出每一頁。如果某個網頁指向資訊清單,就無法防止網頁快取。

您可以前往 Chrome 造訪 about://appcache-internals/,查看應用程式快取控制的網址。您可以在這裡清除快取及查看項目。Firefox 提供了類似的開發人員工具

manifest 屬性可以指向絕對網址或相對路徑,但絕對網址必須位於與網頁應用程式相同的來源位置。資訊清單檔案可以有任何副檔名,但必須使用正確的 MIME 類型 (如下所示)。

<html manifest="http://www.example.com/example.mf">
  ...
</html>

資訊清單檔案必須以 MIME 類型 text/cache-manifest 提供。您可能需要將自訂檔案類型新增至網路伺服器或 .htaccess 設定。

舉例來說,如要在 Apache 中提供這個 mime-type,請將以下這一行新增至您的設定檔:

AddType text/cache-manifest .appcache

或者在 Google App Engine 的 app.yaml 檔案中:

- url: /mystaticdir/(.*\.appcache)
  static_files: mystaticdir/\1
  mime_type: text/cache-manifest
  upload: mystaticdir/(.*\.appcache)

最新版 Chrome、Safari 和 Firefox 已不再需要這項規定。不過,如要在舊版瀏覽器和 IE11 中運作,就必須使用 MIME 類型。

資訊清單檔案的結構

資訊清單是透過 html 元素上的資訊清單屬性連結到個別檔案。簡單的資訊清單看起來會像這樣:

CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js
http://cdn.example.com/scripts/main.js

這個範例會在指定這個資訊清單檔案的頁面上快取四個檔案。

請留意以下事項:

  • CACHE MANIFEST 字串是第一行,且為必要字串。
  • 檔案可能來自其他網域
  • 有些瀏覽器會限制應用程式可用的儲存空間配額。以 Chrome 為例,AppCache 會使用其他離線 API 可共用的暫時儲存共用集區儲存空間。如要編寫 Chrome 線上應用程式商店的應用程式,請使用 unlimitedStorage 解除這項限制。
  • 如果資訊清單本身傳回 404 或 410,就會刪除快取。
  • 如果無法下載資訊清單或其中指定的資源,整個快取更新程序就會失敗。如果發生故障,瀏覽器將繼續使用舊的應用程式快取。

以下來看看更複雜的範例:

CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
*

# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg

以「#」開頭的行是註解行,但也可以用於其他用途。 只有在資訊清單檔案變更時,應用程式快取才會更新。例如,如果您編輯圖片資源或變更 JavaScript 函式,系統不會重新快取這些變更。您必須修改資訊清單檔案本身,通知瀏覽器重新整理快取檔案

避免使用持續更新的時間戳記或隨機字串,每次都強制更新。資訊清單會在更新過程中兩次,一次會在開始時和所有快取檔案更新完成後進行檢查。如果資訊清單在更新時有異動,可能是因為瀏覽器從某個版本擷取部分檔案,也可能從其他版本擷取其他檔案,因此不會在之後套用快取並重試。

雖然快取會更新,但瀏覽器要等到網頁重新整理後才會使用這些檔案,因為系統會在網頁從目前的快取版本載入後進行更新。

資訊清單可以有三個不同的部分:CACHENETWORKFALLBACK

CACHE:
這是項目的預設部分。列在此標頭下的檔案 (或 CACHE MANIFEST 之後立即) 會在初次下載後明確快取。NETWORK:
本節列出的檔案如果不在快取中,也可能來自該網路;否則,即便使用者連上網路,系統也不會使用網路。您可以將特定網址加入許可清單,也可以直接列出「」允許所有網址。大多數網站都需要「」。 FALLBACK:
如果無法存取資源,則可指定備用網頁。第一個 URI 是資源,第二個 URI 是網路要求失敗或錯誤時要使用的備用服務。這兩個 URI 的來源都必須與資訊清單檔案相同。您也可以擷取特定網址,也可以擷取網址前置字元。「images/large/」會擷取網址中的錯誤,例如「images/large/whatever/img.jpg」。

下列資訊清單定義了一個「全部接收」頁面 (Offline.html),在使用者離線時嘗試存取網站的根目錄時。也會宣告所有其他資源 (例如遠端網站上的資源) 需要網際網路連線。

CACHE MANIFEST
# 2010-06-18:v3

# Explicitly cached entries
index.html
css/style.css

# offline.html will be displayed if the user is offline
FALLBACK:
/ /offline.html

# All other resources (e.g. sites) require the user to be online.
NETWORK:
*

# Additional resources to cache
CACHE:
images/logo1.png
images/logo2.png
images/logo3.png

更新快取

應用程式離線後,系統會保持快取狀態,直到發生下列其中一種情況為止:

  1. 使用者清除了網站上的瀏覽器資料儲存空間。
  2. 已修改資訊清單檔案。注意:更新資訊清單中列出的檔案不代表瀏覽器會重新快取該資源。資訊清單檔案本身必須遭到修改。

快取狀態

window.applicationCache 物件是透過程式輔助方式存取瀏覽器的應用程式快取。status 屬性有助於檢查快取目前的狀態:

var appCache = window.applicationCache;

switch (appCache.status) {
case appCache.UNCACHED: // UNCACHED == 0
return 'UNCACHED';
break;
case appCache.IDLE: // IDLE == 1
return 'IDLE';
break;
case appCache.CHECKING: // CHECKING == 2
return 'CHECKING';
break;
case appCache.DOWNLOADING: // DOWNLOADING == 3
return 'DOWNLOADING';
break;
case appCache.UPDATEREADY:  // UPDATEREADY == 4
return 'UPDATEREADY';
break;
case appCache.OBSOLETE: // OBSOLETE == 5
return 'OBSOLETE';
break;
default:
return 'UKNOWN CACHE STATUS';
break;
};

如要透過程式輔助檢查資訊清單更新,請先呼叫 applicationCache.update()。系統會嘗試更新使用者的快取 (資訊清單檔案必須變更)。 最後,當 applicationCache.status 處於 UPDATEREADY 狀態時,呼叫 applicationCache.swapCache() 就會交換新快取的舊快取。

var appCache = window.applicationCache;

appCache.update(); // Attempt to update the user's cache.

...

if (appCache.status == window.applicationCache.UPDATEREADY) {
appCache.swapCache();  // The fetch was successful, swap in the new cache.
}

好消息是,這項作業可自動執行。如要將使用者更新為最新的網站版本,請設定事件監聽器,在網頁載入時監控 updateready 事件:

// Check if a new cache is available on page load.
window.addEventListener('load', function(e) {

window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
    // Browser downloaded a new app cache.
    if (confirm('A new version of this site is available. Load it?')) {
    window.location.reload();
    }
} else {
    // Manifest didn't changed. Nothing new to server.
}
}, false);

}, false);

AppCache 事件

如同預期的結果,系統會顯示其他事件來監控快取狀態。瀏覽器會觸發各種事件,例如下載進度、更新應用程式快取和錯誤狀況。下列程式碼片段會為每種快取事件類型設定事件監聽器:

function handleCacheEvent(e) {
//...
}

function handleCacheError(e) {
alert('Error: Cache failed to update!');
};

// Fired after the first cache of the manifest.
appCache.addEventListener('cached', handleCacheEvent, false);

// Checking for an update. Always the first event fired in the sequence.
appCache.addEventListener('checking', handleCacheEvent, false);

// An update was found. The browser is fetching resources.
appCache.addEventListener('downloading', handleCacheEvent, false);

// The manifest returns 404 or 410, the download failed,
// or the manifest changed while the download was in progress.
appCache.addEventListener('error', handleCacheError, false);

// Fired after the first download of the manifest.
appCache.addEventListener('noupdate', handleCacheEvent, false);

// Fired if the manifest file returns a 404 or 410.
// This results in the application cache being deleted.
appCache.addEventListener('obsolete', handleCacheEvent, false);

// Fired for each resource listed in the manifest as it is being fetched.
appCache.addEventListener('progress', handleCacheEvent, false);

// Fired when the manifest resources have been newly redownloaded.
appCache.addEventListener('updateready', handleCacheEvent, false);

如果資訊清單檔案或中指定的資源無法下載,整個更新作業都會失敗。如果這類情形發生,瀏覽器會繼續使用舊的應用程式快取。

參考資料