使用ApplicationCache接口實現離線緩存
推薦:html5 application cache遇到的嚴重問題
在我們的3G版網站的項目中使用了html5 application cache,將大部分圖片資源、js、css等靜態資源放在manifest文件中,需要了解的朋友可以參考下
離線訪問對基於網絡的應用而言越來越重要。雖然所有瀏覽器都有緩存機制,但它們並不可靠,也不一定總能起到預期的作用。 HTML5 使用 ApplicationCache 接口解決了由離線帶來的部分難題。
使用緩存接口可為您的應用帶來以下三個優勢:
離線瀏覽 – 用戶可在離線時瀏覽您的完整網站
速度 – 緩存資源為本地資源,因此加載速度較快。
服務器負載更少 – 瀏覽器只會從發生了更改的服務器下載資源。
應用緩存(又稱 AppCache)可讓開發人員指定瀏覽器應緩存哪些文件以供離線用戶訪問。即使用戶在離線狀態下按了刷新按鈕,您的應用也會正常加載和運行。
緩存清單文件
緩存清單文件是個簡單的文本文件,其中列出了瀏覽器應緩存以供離線訪問的資源。
引用清單文件
要啟用某個應用的應用緩存,請在文檔的html 標記中添加manifest 屬性:
www.mb5u.com <html manifest="example.appcache"> ... </html>
您應在要緩存的網絡應用的每個頁面上都添加 manifest 屬性。如果網頁不包含 manifest 屬性,瀏覽器就不會緩存該網頁(除非清單文件中明確列出了該屬性)。這就意味着用戶瀏覽的每個包含manifest 的網頁都會隱式添加到應用緩存。因此,您無需在清單中列出每個網頁。
manifest 屬性可指向絕對網址或相對路徑,但絕對網址必須與相應的網絡應用同源。清單文件可使用任何文件擴展名,但必須以正確的 MIME 類型提供(參見下文)。
<html manifest="http://www.example.com/example.mf"> ... </html>
清單文件必須以 text/cache-manifest MIME 類型提供。您可能需要向網絡服務器或 .ht access 配置添加自定義文件類型。
例如,要在 Apache 中提供此 MIME 類型,請在您的配置文件中添加下面一行內容:
AddType text/cache-manifest .appcache要在 Google App Engine 的 app.yaml 文件中提供此 MIME 類型,則添加以下內容:
- url: /mystaticdir/(.*\.appcache)
static_files: mystaticdir/\1
mime_type: text/cache-manifest
upload: mystaticdir/(.*\.appcache)清單文件結構
簡單的清單格式如下:
CACHE MANIFEST
index.html
stylesheet. css
images/logo.png
scripts/main.js該示例將在指定此清單文件的網頁上緩存四個文件。
您需要注意以下幾點:
CACHE MANIFEST 字符串應在第一行,且必不可少。
網站的緩存數據量不得超過 5 MB。不過,如果您要編寫的是針對 Chrome 網上應用店的應用,可使用 unlimitedStorage 取消該限制。
如果清單文件或其中指定的資源無法下載,就無法進行整個緩存更新進程。在這種情況下,瀏覽器將繼續使用原應用緩存。
我們再來看看更復雜的示例:
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: login.php /myapi http://api.twitter.com # 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
*.html /offline.html以“#”開頭的行是注釋行,但也可用於其他用途。應用緩存只在其清單文件發生更改時才會更新。例如,如果您修改了圖片資源或更改了 JavaScript 函數,這些更改不會重新緩存。您必須修改清單文件本身才能讓瀏覽器刷新緩存文件。使用生成的版本號、文件哈希值或時間戳創建注釋行,可確保用戶獲得您的軟件的最新版。您還可以在出現新版本后,以編程方式更新緩存,如更新緩存部分中所述。
清單可包括以下三個不同部分:CACHE、NETWORK 和 FALLBACK。
CACHE:
這是條目的默認部分。系統會在首次下載此標頭下列出的文件(或緊跟在 CACHE MANIFEST 后的文件)后顯式緩存這些文件。
NETWORK:
此部分下列出的文件是需要連接到服務器的白名單資源。無論用戶是否處於離線狀態,對這些資源的所有請求都會繞過緩存。可使用通配符。
FALLBACK:
此部分是可選的,用於指定無法訪問資源時的后備網頁。其中第一個 URI 代表資源,第二個代表后備網頁。兩個 URI 必須相關,並且必須與清單文件同源。可使用通配符。
請注意:這些部分可按任意順序排列,且每個部分均可在同一清單中重復出現。
以下清單定義了用戶嘗試離線訪問網站的根時顯示的“綜合性”網頁 (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請注意:系統會自動緩存引用清單文件的 HTML 文件。因此您無需將其添加到清單中,但我們建議您這樣做。
請注意:HTTP 緩存標頭以及對通過 SSL 提供的網頁設置的緩存限制將被替換為緩存清單。因此,通過 htt ps 提供的網頁可實現離線運行。
更新緩存
應用在離線后將保持緩存狀態,除非發生以下某種情況:
用戶清除了瀏覽器對您網站的數據存儲。
清單文件經過修改。請注意:更新清單中列出的某個文件並不意味着瀏覽器會重新緩存該資源。清單文件本身必須進行更改。
應用緩存通過編程方式進行更新。
緩存狀態
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.
}
請注意:以這種方式使用 update() 和 swapCache() 不會向用戶提供更新的資源。此流程只是讓瀏覽器檢查是否有新的清單、下載指定的更新內容以及重新填充應用緩存。因此,還需要對網頁進行兩次重新加載才能向用戶提供新的內容,其中第一次是獲得新的應用緩存,第二次是刷新網頁內容。
好消息是,您可以避免重新加載兩次的麻煩。要使用戶更新到最新版網站,可設置監聽器,以監聽網頁加載時的 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.
// Swap it in and reload the page to get the new hotness.
window.applicationCache.swapCache();
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);
應用程序緩存 API(簡稱“AppCache”)
使用 AppCache 在本地保存資源,你可以通過減少主機服務器的請求數量來改善網頁性能;此外,你還可以脫機訪問緩存的資源。
若要在本地緩存資源,請執行下列操作:
- 1. 創建一個 清單文件,以指定你要保存的資源。
- 2. 在要使用緩存資源的每個網頁中引用該清單文件。
H5的一個重要特性就是離線存儲,所謂的離線存儲就是將一些資源文件保存在本地,這樣后續的頁面重新加載將使用本地資源文件,在離線情況下可以繼續訪問web應用,同時通過一定的手法(更新相關文件或者使用相關API),可以更新、刪除離線存儲等操作;
H5的離線存儲使用一個manifest文件來標明哪些文件是需要被存儲的,使用如 <html manifest='offline.manifest'> 來引入一個manifest文件,這個文件的路徑可以是相對的,也可以是絕對的,如果你的web應用很多,而且希望能集中管理manifest文件,那么靜態文件服務器是個不錯的選擇。
對於manifest文件,要求:文件的mime-type必須是 text/cache-manifest類型。
如果你是JAVA工程,在你的web.xml中配置請求后綴為manifest的格式:
-
[html] view plaincopy <mime-mapping> <extension>manifest</extension> <mime-type>text/cache-manifest</mime-type> </mime-mapping>
這樣可以控制請求到的manifest文件格式為text/cache-manifest的。
創建清單文件
清單文件是一個針對網頁所使用的資源指定緩存行為的文本文件,參見下面的示例。
CACHE MANIFEST CACHE: # Defines resources to be cached. script/library.js css/stylesheet.css images/figure1.png FALLBACK: # Defines resources to be used if non-cached # resources cannot be downloaded, for example # when the browser is offline.. photos/ figure2.png NETWORK: # Defines resources that will not be cached. figure3.png
- 其中 CACHE 不是必須存在的,可以直接在 CACHE MANIFEST 行之下直接寫需要緩存的文件,在這里指明的文件將被緩存到瀏覽器本地。
- 在NETWORK之下指明的文件,是強制必須通過網絡資源獲取的
- 在FALLBACK下指明的是一種失敗的回調方案,比如上述index.php無法訪問,那么就發出404.htm請求
CACHE MANIFEST script/library.js css/stylesheet.css images/figure1.png
- 必須使用 8 位 Unicode 轉換格式 (UTF-8) 字符編碼。
- 必須接受文本/緩存清單 MIME 類型。
- 必須以“CACHE MANIFEST”行開始。
- 可以包含注釋,但前面必須加英鎊標記 (#)。
有關更多信息,請參閱緩存清單語法。
聲明清單
要將清單與網頁關聯,需將清單文件的名稱分配給 html 元素的 manifest 屬性,如下面得示例所示。
<!doctype html> <html manifest="appcache.manifest"> <head> <title>A Web Page</title> <script src="library.js"></script> <link rel="stylesheet" href="stylesheet.css"> </head> <body onload="doSomething();"> <p>Results go here: <span id="results">Unknown</span></p> </body> </html>
在此示例中,網頁將“appcache.manifest”作為清單文件進行聲明。清單聲明可像任何其他文件引用一樣被解釋。由於此示例使用相對文件名稱,所以該清單被假定位於與網頁自身相同的目錄中。
注意 清單中的文件引用被解釋為清單文件的相對位置,而不是聲明它的網頁。
對清單來說,沒有必要包含聲明該清單的網頁名稱;聲明清單的網頁會被自動緩存。
這樣幾步就可以完成對離線存儲的支持。接下來要思考的,是如何更新離線存儲?
當用戶本地再次聯網的時候,本地的離線存儲資源需要檢查是否需要更新,這個更新過程,也是通過manifest的更新來控制的,更新了manifest文件,瀏覽器會自動的重新下載新的manifest文件並在下一次刷新頁面的時候進行資源文件的重新請求(第三次刷新替換本地緩存為最新緩存),而且這個請求是全局性的,也就是所有在manifest緩存列表中的文件都會被請求一次,而不是單獨請求某個特定修改過的資源文件,因為manifest是不知道哪個文件被修改過了的。
對於全局更新的擔心是不必要的,因為對於沒有更新過的資源文件,請求依舊是304響應,只有真正更新過的資源文件才是200.
所以控制離線存儲的更新,需要2個步驟,一是更新資源文件,二是更新manifest文件,特別的,更新manifest文件是不需要修改什么特定內容的,只要是這個文件隨意一處被修改,那么瀏覽器就會感知,對於我們的資源文件通常名稱是固定的,比如xxx.css,更新內容不會帶有文件名更新的情況下,需要更新manifest文件怎么操作呢?一個比較好的方式是更新任意一處# 開頭的注釋即可,其目的只是告訴瀏覽器這個manifest文件被更新過。
以上的這些內容,其更新操作都是瀏覽器自動完成的。同樣的,W3C定義了離線存儲的API規范:http://www.whatwg.org/specs/web-apps/current-work/#applicationcache
ApplicationCache 對象
Internet Explorer 10 還支持 ApplicationCache 對象,該對象提供可用來管理應用程序緩存的屬性。此外,你還可以定義顯示緩存進程進度的事件處理程序。
使用 window 對象的 applicationCache 屬性(或 worker 對象)以訪問 ApplicationCache 對象,如下面的示例所示。
var sCacheStatus = "Not supported";
if (window.applicationCache)
{
var oAppCache = window.applicationCache;
switch ( oAppCache.status )
{
case oAppCache.UNCACHED :
sCacheStatus = "Not cached";
break;
case oAppCache.IDLE :
sCacheStatus = "Idle";
break;
case oAppCache.CHECKING :
sCacheStatus = "Checking";
break;
case oAppCache.DOWNLOADING :
sCacheStatus = "Downloading";
break;
case oAppCache.UPDATEREADY :
sCacheStatus = "Update ready";
break;
case oAppCache.OBSOLETE :
sCacheStatus = "Obsolete";
break;
default :
sCacheStatus = "Unexpected Status ( " +
oAppCache.status.toString() + ")";
break;
}
}
return sCacheStatus;
此示例使用 status 屬性為當前通過網頁所加載的文檔確定應用程序緩存的狀態。
ApplicationCache 對象支持兩種控制緩存的方法。update 方法啟動對更新的異步檢查,類似於在首次加載網頁時執行的操作。在重新加載該網頁或調用 swapCache 方法之前,將會一直使用任何現有的緩存。要開始使用已更新的緩存,需要重新加載網頁(手動或以編程方式)或調用 swapCache 方法。當重新加載網頁時,因為緩存已更新,所以在重新加載或刷新網頁之前不需要調用 swapCache 方法。
提供了如下API:
// 更新,一般來說更新下載是通過用戶代理(如瀏覽器)自動完成的,但是這個方法適用於一些長期打開的頁面,比如郵件系統,可能這個頁面是長期打開的,而不會有刷新動作,所以這個就比較適合做自動更新下載 void update(); // 取消 void abort(); // 替換緩存內容 ,對於manifest文件的改變,通常是下一次的刷新才會觸發下載更新,第三次刷新才會切換使用新的緩存文件,通過這個方法,可以強制將緩存替換 void swapCache();
注意 在重新加載網頁(由用戶手動或以編程方式使用 window.location 對象的 reload 方法)之前,網頁不會使用已更新的緩存。
ApplicationCache 對象支持以下事件:
- 當清單已緩存時,將觸發 cached 事件。
- 當檢查是否有更新時,將觸發 checking 事件。
- 當下載清單資源時,將觸發 downloading 事件。
- 在下載清單資源期間,將觸發 progress 事件。
- 當出現問題(如 HTML 404 或 410 響應代碼)時,將觸發 error 事件。當無法下載清單文件時,也會觸發該事件。
- 當緩存有更新的版本可用時,將觸發 updateready 事件。
- 請求更新時將會觸發 noupdate 事件,但清單不會發生更改。
- 當現有的緩存被標記為已過時時,將會觸發 obsolete 事件。
下面的示例演示如何為這些事件注冊事件處理程序。
if (window.applicationCache) {
var appCache = window.applicationCache;
appCache.addEventListener('error', appCacheError, false);
appCache.addEventListener('checking', checkingEvent, false);
appCache.addEventListener('noupdate', noUpdateEvent, false);
appCache.addEventListener('downloading', downloadingEvent, false);
appCache.addEventListener('progress', progressEvent, false);
appCache.addEventListener('updateready', updateReadyEvent, false);
appCache.addEventListener('cached', cachedEvent, false);
}
最后說一個對於manifest比較特別的地方:對於某個文件a.htm,其中有 <html manifest='a.manifest'> ,那么離線存儲中,會自動將a.htm加入到列表中,這意味着a.htm的再次刷新將從本地緩存中獲取,這樣的機制從官方得到的答復是“特別的設計”,而對我們來說,這種強加的特性在后續的開發過程中會有不少問題,比如:
1、如何計算PV UV,由於當前頁面被強制加入manifest,那么PV 和UV的統計,成了一個難題,因為請求不再是發送到服務器;
2、對於某個使用manifest的文件,其帶有的參數可能是隨機性的統計參數,如sid=123sss, sid=234fff ,尤其是比如商品詳情的id字段等,這樣每個頁面都自動加入到manifest中,將會帶來很大的存儲開銷,而且是毫無意義的;
所以伴隨而來的,是如何在現有的體系架構下進行數據統計的難題,一個常規的方案是進入離線存儲頁面后自動發出ajax請求,以告知服務器統計PV UV;
對於第二個問題,可能就比較棘手,但是將GET請求的方式改成POST的方式確實是個解決問題的方案。
慎用manifest
Html5已經被提過很久了,各種新屬性出來,人們也都歡呼了一把,今天要討論的是一個html5的新屬性——manifest,可能有很多人已經用過該方法來優化自己的網站了。今天來分享下我們在使用manifest的時候遇到的問題。
1.概念
該特性為HTML5的新特性,能夠讓web程序指定可以緩存在本地的資源,以及緩存策略,使得程序能夠在離線時仍然能夠運行,也可以使程序在線時也可以從本地加載資源而不用重新從網絡加載。
2.基本寫法
要用manifest,就要了解其基本寫法
通過在頁面的html標簽中中引入:
<html manifest=”/static/index/innovation/cache.manifest”>
Cache.manifest的文件寫法:
- CACHE MANIFEST
- # v2012 1203v3
- http://m.baidu.com/static/index/i.gif
- http://m.baidu.com/static/hb/hot.gif
- http://m.baidu.com/static/index/logo_index2.png
- NETWORK:
- *
- http://m.baidu.com/su
- http://loc.map.baidu.com/wloc
- http://m.baidu.com/static/tj.gif
第一行一定要寫上CACHE MANIFEST,然后注釋寫后面,以后通過修改注釋,來實現manifest的更新。
A.路徑寫法不支持帶端口,所以最好用相對路徑吧,要不然線下測試機上開發,有端口不好調試。
B.*號通配符有時候好像並不好使。如果在某些設備上出現問題,可以排查一下是不是這個導致的。
C.#以后的內容雖然是注釋,但是不能放到第一行,第一行必須是如上的大寫字母。
1.離線后的頁面
頁面離線后,好處顯而易見,頁面可以秒開了(啟動速度快)。你會感覺頁面加載的速度快了很多。但是也有一些問題。
A.頁面都離線了,如何更新?首先,你可以修改下manifest文件來更新這個頁面,這個我就不介紹了,但是作為文章內容頁面離線以后,就會存儲在本地了,如果你是一篇文章的話,那么這個文章的內容頁就被存下來了,你如果以相同的url去訪問,不管你文章里面的數據更新沒有,這個離線下來的頁面都不會更新了(除非你更新manifest文件)。所以,所有的動態數據,你都得用ajax方式去獲取,就像客戶端一樣,離線的頁面應該是一個沒有數據的空殼,然后通過ajax去拉去數據填補這個空殼。然后要注意的是,ajax的請求地址,要寫到manifest的network中,要不然,你可以試試。
B.頁面的參數如何攜帶?,還是剛才那個問題,文章頁的殼如果緩存了,怎么樣傳數據過來標識這個特點頁面呢?比如m.baidu.com/app?a=1&b=2,通常我們用一些參數來標記這個頁面,通過參數來渲染頁面內容,但是manifest對於上面的方式,會認為不同的參數表示不同的頁面。如果你吧內容頁做成一個無數據的空殼,這種傳參的方式顯然不行,好在不一樣的hash頁面,manifest會認為是同一個頁面,比如m.baidu.com/app#detail-111111與m.baidu.com/app#detail-222222會認為和m.baidu.com/app是同一個緩存頁面。這樣我們就可以通過hash傳值了,當然,你也可以通過其它方式傳值,比如寫入cookie,寫入localstorage方式等等。
4.離線頁面的更新
網站更新了,如果更新用戶本地的離線頁面呢?與很多文章中說的一樣,先上線你的文件,然后修改一下頁面中引入的cache.manifest文件即可,比如修改下注釋,修改后,如果再訪問頁面,就會先去校驗manifest時候有更新,如有更新,再次刷新頁面的時候,頁面就會更新了。
A.長尾問題,就像前面說到的一樣,如果你的manifest文件更新了,你訪問頁面,需要刷新一次,更新的頁面才能load進來,那么這樣就有一個問題,如果你的后端數據,就是給js ajax接口的數據變化了,你對應的js也修改了。那么你修改manifest上線的時候,第一次開頁面,頁面就會bug了。再刷一次頁面,就好了。那么,這個第一次訪問的bug,是我們不想看到的。而且你不能知道用戶什么時候第二次再來訪問你的頁面,所以你的頁面一旦使用manifest離線,就像客戶端一樣,這樣就出現了長尾問題。還好,manifest有一些js接口,可以來判斷,load更新文件。
B.刷新頁面
有了js的api,一切都好辦了,我們可以干很多事情了。現在你可以用js來判斷頁面的狀態了,可能你已經想到,判斷狀態后,可以刷新一下頁面,那么,就算數據發生了修改,這種處理方式也是ok的,沒錯,這樣確實解決了問題。你也可以在設置了manifest的頁面中加入這些方法,然后修改下文件,看看會觸發哪些事件,刷新頁面的唯一問題就是頁面會閃動一下,這點體驗當然是不好的。因此,我們想到了loading頁面。
C.Loading頁面
制作一個loading頁面,用來檢測manifest文件有沒有更新如果發現有更新,則更新資源,然后再跳到真實的首頁。這個實現起來就比較容易了,在loading頁面你可以加一些效果,比如加載效果等等,給用戶一個預知。讓我們看看manifest修改過和沒有修改過各種狀態的差別。保證你的manifest文件存在有效,如下:
- CACHE MANIFEST
- #test11123aaadfsaasdadffdsfaaaffd
- http://m.baidu.com/static/js/zepto-event-ajax.js
- testb.html
- NETWORK:
- *
Html文件如下:
- <!DOCTYPE html>
- <html manifest=”notes.manifest”>
- <head>
- <title>離線存儲demo</title>
- <meta http-equiv=”Content-Type” content=”text/html;charset=UTF-8″>
- </head>
- <body>
- Hello world!
- <script type=”text/javascript” src=”http://m.baidu.com/static/js/zepto-event-ajax.js”></script>
- <script type=”text/javascript”>
- var appCache=window.applicationCache;
- console.log(appCache.status);
- appCache.addEventListener(“obsolete”,function(){
- console.log(“Obsolete,status:”+appCache.status);
- },false);
- appCache.addEventListener(“cached”,function(){
- console.log(“chache,status:”+appCache.status);
- },false);
- appCache.addEventListener(“checking”,function(){
- console.log(“checking,status:”+appCache.status);
- },false);
- appCache.addEventListener(“downloading”,function(){
- console.log(“downloading,status:”+appCache.status);
- },false);
- appCache.addEventListener(‘noupdate’, function(){
- console.log(‘Noupdate,Status:’ + appCache.status);
- }, false);
- appCache.addEventListener(‘updateready’, function(){
- console.log(‘updateready,Status:’ + appCache.status);
- }, false);
- appCache.addEventListener(“error”,function(){
- console.log(“error”);
- },false);
- </script>
- </body>
- </html>
如果頁面沒有發生變化:那么以上代碼會輸出:
如果對應的manifest有修改,需要更新文件,那么上面的代碼會輸出:
如果更新了manifest,會觸發downloading和updateready事件。你可以根據這兩個事件來處理一些邏輯了,比如在downloading給用戶一些提示,正在加載,updateready就跳轉到真正的頁面,那么這個時候,頁面就已經加載好了,就不用再刷新才生效頁面了。比如:
- appCache.addEventListener(‘updateready’, function(){
- console.log(‘updateready,Status:’ + appCache.status);
- location.href=”testb.html”;
- }, false);
該頁面在loading頁面上,其實主要的內容頁面在testb.html上。
D.Loading頁面的問題
這時,你可能認為,這個不錯,解決了所有問題。效果也ok,也沒有第一次加載頁面可能出錯的狀況。如果對於一個單頁面來說,這樣確實沒問題了,但是如果你是一個完整的站,問題又來了,你不可能給網站每個頁面都加loading,就算每個模版頁都用一個loading,如果用戶進入了你某個頁面,如果把你某個頁面存到了桌面快捷方式。那么下次啟動,可能還是會掛,必須刷一次頁面才能好,所以,你可能想到,整個頁面都要通過js去build了……,然后又要保持build頁面的js要在頁面渲染前發生更改,那么,你可以在每個殼模版頁面判斷manifest的邏輯,然后動態載入的js,通過動態載入的js來build整個html頁面,那么這樣就木有bug了,不過這種處理方式確實惡心了,如果你的模版頁的html殼更新不多,僅僅會更新里面的js邏輯的話,那你可以在改html殼中動態載入js即可,也可以直接刷一下頁面來解決。
但是這畢竟不是一個app,如果你的站點突然接到需求要更新得很頻繁,比如一天要更新一次,那么,這個manifest離線的意義就僅僅在於第二次載入的時候可以秒開。除此之外,我也想不到其他好處。然后用戶如果一天剛好訪問一次的話。這個狀態好像還沒有不用manifest離線的狀態好。那么,我需要把manifest下線……
5.Manifest的回滾與下線
其實,回滾與下線表現上來說,差不多,當然,我這里所提到的回滾,是第一次上線manifest的回滾,一般如果你上線manifest出現了問題,比如造成的線上嚴重的bug,那么,這是時候,一般公司就會有回滾機制,用以前的代碼來覆蓋現在的代碼。但是你一旦上線了manifest離線存儲,回滾就沒那么容易了,就是從有manifest的狀態到無manifest的狀態,其實這點和manifest下線一樣。很多人都知道每次更新了靜態文件,更新一下manifest多刷兩次頁面,頁面就更新了。如果manifest文件沒有了呢,我們還是拿上面的那個頁面做測試。
第一次刷新會這樣:
第二次再刷新是這樣:
刪除manifest以后,瀏覽器校驗manifest文件就會返回一個404,那么這時,manifest就會失效,再刷頁面,頁面就會又開始自動更新了。也不會緩存了,是不是很興奮。這不是很簡單么?刪掉manifest文件就能回滾了,就能下線了。不過現在好像404不流行了,如果你刪掉了服務器上某個靜態文件,服務器會302跳轉到一個錯誤頁,那么這時候,你在怎么刷新頁面,頁面都不會在更新了。悲劇了……用戶手機瀏覽器中的頁面再也更新不了了。此時你只能再次更新manifest讓其強制更新。可能你還想到,我刪除了manifest文件,把html中manifest引用也刪除不就行了么。這個也是不行的。瀏覽器緩存了該地址的離線信息,除非你清除瀏覽器的緩存,否則這招無效。
所以,如果可以,就可以利用manifest文件404把manifest下掉。
如果這招行不通呢?就是沒法返回404。那么你上線manifest的時候就要注意了。前文中提到了loading,那么你得再loading中處理好load manifest文件失效的情況。其實處理方式也很簡單,就是跳到本來該跳的頁面,然后帶上一個參數(前面已經說過,帶參數與帶hash的區別)也就可以很輕松解決這個問題了,但是需要注意一定,找不到manifest文件,第一次刷新和第二次刷新所觸發的事情不一樣,第一次會觸發obsolete事件,第二次再刷新,就觸發error事件,不過這樣也好辦,我們在這兩個事件中,將鏈接都跳到一個帶參數的頁面就好了(在error、obsolete中跳到頁面帶參數,在updateready、noupdate里面直接跳到頁面)。所以,利用manifest的js api然后配合刪除manifest文件(此時就可以不管頁面是不是302或者404了),就可以下線掉manifest文件了,如果用戶不清緩存,那段曾經被離線的代碼就會起作用,會讓用戶永遠頁面帶上參數。
- appCache.addEventListener(“downloading”,function(){
- console.log(“downloading,status:”+appCache.status);
- console.log(“downing”);
- },false);
- appCache.addEventListener(‘noupdate’, function(){
- console.log(‘Noupdate,Status:’ + appCache.status);
- location.href=”testb.html”;
- }, false);
- appCache.addEventListener(‘updateready’, function(){
- console.log(‘updateready,Status:’ + appCache.status);
- location.href=”testb.html”;
- }, false);
- appCache.addEventListener(“error”,function(){
- console.log(“error”);
- location.href=”testb.html?a=1″;
- },false);
- appCache.addEventListener(“obsolete”,function(){
- console.log(“Obsolete,status:”+appCache.status);
- location.href=”testb.html?a=2″;
- },false);
5.適用場景小結
上線,下線,回滾,長尾等等問題都考慮過了,現在我們應該可以正確使用manifest了,現在來討論一個問題,什么樣的頁面適合使用manifest呢。關於這點,目前我們還沒有具體的數據證明,但是通過manifest離線的頁面,第二次訪問時啟動速度快了很多,而且,如果處理得當,還是有一些收益的,每次更新manifest文件以后,manifest中未修改的文件是不會被重新下載的,會返回304,而且頁面中的其他靜態文件,如css和img等,也能享受到傳統緩存的方式。
如果你的應用是一個html5的游戲、或者是一個html的app應用,明顯,采用manifest會收到很好的效果,第二次及以后加載速度都非常快,而且你的應用如果不依賴網絡的話,就算用戶斷網了,也可以正常使用,如html5游戲,你完全可以離線來帶來更好的用戶體驗。然而對於一些傳統的站點,也想運用一下manifest新特性,如果你的站點每天都有上線更新,每天都有上線,從manifest的機制上來說,這個是不怎么適合用manifest離線的,靜態資源本來就可以自己緩存,引入manifest還會帶來一些維護成本已經上面提到的一些問題。但是你的站點開發完成以后,更新頻率比較低,做完一版以后很長時間內不做升級(這點類似客戶端),那么,你的站點就很適合用manifest離線來優化用戶體驗,manifest雖然帶來了一些好處,但是也有很多問題。
另外,在使用過程中,某些情況下,會出現瀏覽器無法更新manifest的情況:目前chrome 23.0.1271.97已經找到穩定復現方法。其中:
1.問題:chrome會無法更新靜態文件(這些文件包括離線主頁,靜態文件。)
2.原因:bug情況下,修改了manifest文件以后,通過抓包,發現某些靜態文件不會發生校驗。Manifest的機制是如果manifest文件發生過修改,是會校驗資源列表中的靜態文件的,如果文件發生過修改則會重新download這個文件,如果資源沒有發生修改,服務器會返回304 。但是實際情況是,瀏覽器不會校驗一些文件了。導致文件無法更新。此時,通過chrome://appcache-internals/方式找到瀏覽器的manifest緩存,然后刪除,然后直接訪問無法校驗的靜態文件。發現仍然可以訪問,並且是未修改過的。后來發現是瀏覽器傳統緩存緩存了這個文件。通過chrome://cache/可以查看到緩存的文件並沒有發生修改,造成無法更新。
3.解決方法:
1.把manifest離線資源與主文件跨域存放,但這個也會導致一個問題,就是會導致dns解析變多。
2.在將manifest的列表中的靜態文件的文件頭中加上no-store(加no-cache不行)。這個也會導致一個問題,就是如果一個庫文件,如zepto.js在m.baidu.com/static 下,manifest用到了,然后其他產品線也用到了,那么其他沒用manifest的產品線就享受不了傳統緩存了。
3.將manifest靜態資源列表中的文件加上時間戳參數,每次修改靜態文件,都主動修改下manifest中靜態文件的時間戳。可能有人會認為修改一次時間戳,manifest就會本地多存一次文件,其實不是的,發現列表中如果沒有該文件了,瀏覽器會很高級,會自動刪除這個文件。不好的地方可能就是上線要多改一處地方。略微增加維護成本。
另外,有同學還碰到過瀏覽器將manifest文件緩存的情況,后來將這個文件的過期時間設置成了1s。
一路坑踩過來,還不知道有多少,而且一些bug出現后很難復現,總之慎用吧。
正確應用manifest來優化你的站點吧。






manifest不要太長, 太長的時候瀏覽器會發起N多的請求去一一校驗文件是否過期, 這個在移動端上是很恐怖的. 建議可以app cache和其他緩存結合的方式. 保證app cache里的東西最少.