教育行業(yè)A股IPO第一股(股票代碼 003032)

全國咨詢/投訴熱線:400-618-4000

移動(dòng)端web加載性能優(yōu)化

更新時(shí)間:2015年12月29日14時(shí)43分 來源:傳智播客前端與移動(dòng)開發(fā)學(xué)科 瀏覽次數(shù):


原文鏈接 
http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=402077566&idx=1&sn=def3337205c3aec5e0fde2476ee03397&scene=0#wechat_redirect
 
1 H5 緩存機(jī)制介紹
H5,即 HTML5,是新一代的 HTML 標(biāo)準(zhǔn),加入很多新的特性。離線存儲(chǔ)(也可稱為緩存機(jī)制)是其中一個(gè)非常重要的特性。H5 引入的離線存儲(chǔ),這意味著 web 應(yīng)用可進(jìn)行緩存,并可在沒有因特網(wǎng)連接時(shí)進(jìn)行訪問。
H5 應(yīng)用程序緩存為應(yīng)用帶來三個(gè)優(yōu)勢(shì):
· 離線瀏覽 用戶可在應(yīng)用離線時(shí)使用它們
· 速度 已緩存資源加載得更快
· 減少服務(wù)器負(fù)載 瀏覽器將只從服務(wù)器下載更新過或更改過的資源。
根據(jù)標(biāo)準(zhǔn),到目前為止,H5 一共有6種緩存機(jī)制,有些是之前已有,有些是 H5 才新加入的。
1. 瀏覽器緩存機(jī)制
2. Dom Storgage(Web Storage)存儲(chǔ)機(jī)制
3. Web SQL Database 存儲(chǔ)機(jī)制
4. Application Cache(AppCache)機(jī)制
5. Indexed Database (IndexedDB)
6. File System API
下面我們首先分析各種緩存機(jī)制的原理、用法及特點(diǎn);然后針對(duì) Anroid 移動(dòng)端 Web 性能加載優(yōu)化的需求,看如果利用適當(dāng)緩存機(jī)制來提高 Web 的加載性能。
 
2 H5 緩存機(jī)制原理分析
2.1 瀏覽器緩存機(jī)制
瀏覽器緩存機(jī)制是指通過 HTTP 協(xié)議頭里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段來控制文件緩存的機(jī)制。這應(yīng)該是 WEB 中最早的緩存機(jī)制了,是在 HTTP 協(xié)議中實(shí)現(xiàn)的,有點(diǎn)不同于 Dom Storage、AppCache 等緩存機(jī)制,但本質(zhì)上是一樣的。可以理解為,一個(gè)是協(xié)議層實(shí)現(xiàn)的,一個(gè)是應(yīng)用層實(shí)現(xiàn)的。
Cache-Control 用于控制文件在本地緩存有效時(shí)長。最常見的,比如服務(wù)器回包:Cache-Control:max-age=600 表示文件在本地應(yīng)該緩存,且有效時(shí)長是600秒(從發(fā)出請(qǐng)求算起)。在接下來600秒內(nèi),如果有請(qǐng)求這個(gè)資源,瀏覽器不會(huì)發(fā)出 HTTP 請(qǐng)求,而是直接使用本地緩存的文件。
Last-Modified 是標(biāo)識(shí)文件在服務(wù)器上的最新更新時(shí)間。下次請(qǐng)求時(shí),如果文件緩存過期,瀏覽器通過 If-Modified-Since 字段帶上這個(gè)時(shí)間,發(fā)送給服務(wù)器,由服務(wù)器比較時(shí)間戳來判斷文件是否有修改。如果沒有修改,服務(wù)器返回304告訴瀏覽器繼續(xù)使用緩存;如果有修改,則返回200,同時(shí)返回最新的文件。
Cache-Control 通常與 Last-Modified 一起使用。一個(gè)用于控制緩存有效時(shí)間,一個(gè)在緩存失效后,向服務(wù)查詢是否有更新。
Cache-Control 還有一個(gè)同功能的字段:Expires。Expires 的值一個(gè)絕對(duì)的時(shí)間點(diǎn),如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表示在這個(gè)時(shí)間點(diǎn)之前,緩存都是有效的。
Expires 是 HTTP1.0 標(biāo)準(zhǔn)中的字段,Cache-Control 是 HTTP1.1 標(biāo)準(zhǔn)中新加的字段,功能一樣,都是控制緩存的有效時(shí)間。當(dāng)這兩個(gè)字段同時(shí)出現(xiàn)時(shí),Cache-Control 是高優(yōu)化級(jí)的。
Etag 也是和 Last-Modified 一樣,對(duì)文件進(jìn)行標(biāo)識(shí)的字段。不同的是,Etag 的取值是一個(gè)對(duì)文件進(jìn)行標(biāo)識(shí)的特征字串。在向服務(wù)器查詢文件是否有更新時(shí),瀏覽器通過 If-None-Match 字段把特征字串發(fā)送給服務(wù)器,由服務(wù)器和文件最新特征字串進(jìn)行匹配,來判斷文件是否有更新。沒有更新回包304,有更新回包200。Etag 和 Last-Modified 可根據(jù)需求使用一個(gè)或兩個(gè)同時(shí)使用。兩個(gè)同時(shí)使用時(shí),只要滿足基中一個(gè)條件,就認(rèn)為文件沒有更新。
另外有兩種特殊的情況:
· 手動(dòng)刷新頁面(F5),瀏覽器會(huì)直接認(rèn)為緩存已經(jīng)過期(可能緩存還沒有過期),在請(qǐng)求中加上字段:Cache-Control:max-age=0,發(fā)包向服務(wù)器查詢是否有文件是否有更新。
· 強(qiáng)制刷新頁面(Ctrl+F5),瀏覽器會(huì)直接忽略本地的緩存(有緩存也會(huì)認(rèn)為本地沒有緩存),在請(qǐng)求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),發(fā)包向服務(wù)重新拉取文件。
下面是通過 Google Chrome 瀏覽器(用其他瀏覽器+抓包工具也可以)自帶的開發(fā)者工具,對(duì)一個(gè)資源文件不同情況請(qǐng)求與回包的截圖。
首次請(qǐng)求:200
 
緩存有效期內(nèi)請(qǐng)求:200(from cache)
 
緩存過期后請(qǐng)求:304(Not Modified)
一般瀏覽器會(huì)將緩存記錄及緩存文件存在本地 Cache 文件夾中。Android 下 App 如果使用 Webview,緩存的文件記錄及文件內(nèi)容會(huì)存在當(dāng)前 app 的 data 目錄中。
分析:Cache-Control 和 Last-Modified 一般用在 Web 的靜態(tài)資源文件上,如 JS、CSS 和一些圖像文件。通過設(shè)置資源文件緩存屬性,對(duì)提高資源文件加載速度,節(jié)省流量很有意義,特別是移動(dòng)網(wǎng)絡(luò)環(huán)境。但問題是:緩存有效時(shí)長該如何設(shè)置?如果設(shè)置太短,就起不到緩存的使用;如果設(shè)置的太長,在資源文件有更新時(shí),瀏覽器如果有緩存,則不能及時(shí)取到最新的文件。
Last-Modified 需要向服務(wù)器發(fā)起查詢請(qǐng)求,才能知道資源文件有沒有更新。雖然服務(wù)器可能返回304告訴沒有更新,但也還有一個(gè)請(qǐng)求的過程。對(duì)于移動(dòng)網(wǎng)絡(luò),這個(gè)請(qǐng)求可能是比較耗時(shí)的。有一種說法叫“消滅304”,指的就是優(yōu)化掉304的請(qǐng)求。
抓包發(fā)現(xiàn),帶 if-Modified-Since 字段的請(qǐng)求,如果服務(wù)器回包304,回包帶有 Cache-Control:max-age 或 Expires 字段,文件的緩存有效時(shí)間會(huì)更新,就是文件的緩存會(huì)重新有效。304回包后如果再請(qǐng)求,則又直接使用緩存文件了,不再向服務(wù)器查詢文件是否更新了,除非新的緩存時(shí)間再次過期。
另外,Cache-Control 與 Last-Modified 是瀏覽器內(nèi)核的機(jī)制,一般都是標(biāo)準(zhǔn)的實(shí)現(xiàn),不能更改或設(shè)置。以 XX 瀏覽器為例,Cache-Control 與 Last-Modified 緩存不能禁用。緩存容量是12MB,不分HOST,過期的緩存會(huì)最先被清除。如果都沒過期,應(yīng)該優(yōu)先清最早的緩存或最快到期的或文件大小最大的;過期緩存也有可能還是有效的,清除緩存會(huì)導(dǎo)致資源文件的重新拉取。
還有,XX瀏覽器,在使用緩存文件時(shí),是沒有對(duì)緩存文件內(nèi)容進(jìn)行校驗(yàn)的,這樣緩存文件內(nèi)容被修改的可能。
分析發(fā)現(xiàn),瀏覽器的緩存機(jī)制還不是非常完美的緩存機(jī)制。完美的緩存機(jī)制應(yīng)該是這樣的:
1. 緩存文件沒更新,盡可能使用緩存,不用和服務(wù)器交互;
2. 緩存文件有更新時(shí),第一時(shí)間能使用到新的文件;
3. 緩存的文件要保持完整性,不使用被修改過的緩存文件;
4. 緩存的容量大小要能設(shè)置或控制,緩存文件不能因?yàn)榇鎯?chǔ)空間限制或過期被清除。
以XX瀏覽器為例,第1、2條不能同時(shí)滿足,第3、4條都不能滿足。
在實(shí)際應(yīng)用中,為了解決 Cache-Control 緩存時(shí)長不好設(shè)置的問題,以及為了”消滅304“,Web前端采用的方式是:
1. 在要緩存的資源文件名中加上版本號(hào)或文件 MD5值字串,如 common.d5d02a02.js,common.v1.js,同時(shí)設(shè)置 Cache-Control:max-age=31536000,也就是一年。在一年時(shí)間內(nèi),資源文件如果本地有緩存,就會(huì)使用緩存;也就不會(huì)有304的回包。
2. 如果資源文件有修改,則更新文件內(nèi)容,同時(shí)修改資源文件名,如 common.v2.js,html頁面也會(huì)引用新的資源文件名。
通過這種方式,實(shí)現(xiàn)了:緩存文件沒有更新,則使用緩存;緩存文件有更新,則第一時(shí)間使用最新文件的目的。即上面說的第1、2條。第3、4條由于瀏覽器內(nèi)部機(jī)制,目前還無法滿足。
2.2 Dom Storage 存儲(chǔ)機(jī)制
DOM 存儲(chǔ)是一套在 Web Applications 1.0 規(guī)范中首次引入的與存儲(chǔ)相關(guān)的特性的總稱,現(xiàn)在已經(jīng)分離出來,單獨(dú)發(fā)展成為獨(dú)立的 W3C Web 存儲(chǔ)規(guī)范。 DOM 存儲(chǔ)被設(shè)計(jì)為用來提供一個(gè)更大存儲(chǔ)量、更安全、更便捷的存儲(chǔ)方法,從而可以代替掉將一些不需要讓服務(wù)器知道的信息存儲(chǔ)到 cookies 里的這種傳統(tǒng)方法。
上面一段是對(duì) Dom Storage 存儲(chǔ)機(jī)制的官方表述??雌饋?,Dom Storage 機(jī)制類似 Cookies,但有一些優(yōu)勢(shì)。
Dom Storage 是通過存儲(chǔ)字符串的 Key/Value 對(duì)來提供的,并提供 5MB (不同瀏覽器可能不同,分 HOST)的存儲(chǔ)空間(Cookies 才 4KB)。另外 Dom Storage 存儲(chǔ)的數(shù)據(jù)在本地,不像 Cookies,每次請(qǐng)求一次頁面,Cookies 都會(huì)發(fā)送給服務(wù)器。
DOM Storage 分為 sessionStorage 和 localStorage。localStorage 對(duì)象和 sessionStorage 對(duì)象使用方法基本相同,它們的區(qū)別在于作用的范圍不同。sessionStorage 用來存儲(chǔ)與頁面相關(guān)的數(shù)據(jù),它在頁面關(guān)閉后無法使用。而 localStorage 則持久存在,在頁面關(guān)閉后也可以使用。
Dom Storage 提供了以下的存儲(chǔ)接口:
interface Storage { 
readonly attribute unsigned long length; 
[IndexGetter] DOMString key(in unsigned long index); 
[NameGetter] DOMString getItem(in DOMString key); 
[NameSetter] void setItem(in DOMString key, in DOMString data); 
[NameDeleter] void removeItem(in DOMString key); 
void clear();
};
sessionStorage 是個(gè)全局對(duì)象,它維護(hù)著在頁面會(huì)話(page session)期間有效的存儲(chǔ)空間。只要瀏覽器開著,頁面會(huì)話周期就會(huì)一直持續(xù)。當(dāng)頁面重新載入(reload)或者被恢復(fù)(restores)時(shí),頁面會(huì)話也是一直存在的。每在新標(biāo)簽或者新窗口中打開一個(gè)新頁面,都會(huì)初始化一個(gè)新的會(huì)話。
<script type="text/javascript">
 // 當(dāng)頁面刷新時(shí),從sessionStorage恢復(fù)之前輸入的內(nèi)容
 window.onload = function(){
    if (window.sessionStorage) {
        var name = window.sessionStorage.getItem("name");
        if (name != "" || name != null){
            document.getElementById("name").value = name;
         }
     }
 };
 
 // 將數(shù)據(jù)保存到sessionStorage對(duì)象中
 function saveToStorage() {
    if (window.sessionStorage) {
        var name = document.getElementById("name").value;
        window.sessionStorage.setItem("name", name);
        window.location.href="session_storage.html";
     }
 }
 </script>
 
<form action="./session_storage.html">
    <input type="text" name="name" id="name"/>
    <input type="button" value="Save" onclick="saveToStorage()"/>
</form>
當(dāng)瀏覽器被意外刷新的時(shí)候,一些臨時(shí)數(shù)據(jù)應(yīng)當(dāng)被保存和恢復(fù)。sessionStorage 對(duì)象在處理這種情況的時(shí)候是最有用的。比如恢復(fù)我們?cè)诒韱沃幸呀?jīng)填寫的數(shù)據(jù)。
把上面的代碼復(fù)制到 session_storage.html(也可以從附件中直接下載)頁面中,用 Google Chrome 瀏覽器的不同 PAGE 或 WINDOW 打開,在輸入框中分別輸入不同的文字,再點(diǎn)擊“Save”,然后分別刷新。每個(gè) PAGE 或 WINDOW 顯示都是當(dāng)前PAGE輸入的內(nèi)容,互不影響。關(guān)閉 PAGE,再重新打開,上一次輸入保存的內(nèi)容已經(jīng)沒有了。
 
 
Local Storage 的接口、用法與 Session Storage 一樣,唯一不同的是:Local Storage 保存的數(shù)據(jù)是持久性的。當(dāng)前 PAGE 關(guān)閉(Page Session 結(jié)束后),保存的數(shù)據(jù)依然存在。重新打開PAGE,上次保存的數(shù)據(jù)可以獲取到。另外,Local Storage 是全局性的,同時(shí)打開兩個(gè) PAGE 會(huì)共享一份存數(shù)據(jù),在一個(gè)PAGE中修改數(shù)據(jù),另一個(gè) PAGE 中是可以感知到的。
<script>
  //通過localStorage直接引用key, 另一種寫法,等價(jià)于:
  //localStorage.getItem("pageLoadCount");
  //localStorage.setItem("pageLoadCount", value);
  if (!localStorage.pageLoadCount)
localStorage.pageLoadCount = 0;
     localStorage.pageLoadCount = parseInt(localStorage.pageLoadCount) + 1;
     document.getElementById('count').textContent = localStorage.pageLoadCount;
</script>
 
<p>
    You have viewed this page
    <span id="count">an untold number of</span>
    time(s).
</p> 
將上面代碼復(fù)制到 local_storage.html 的頁面中,用瀏覽器打開,pageLoadCount 的值是1;關(guān)閉 PAGE 重新打開,pageLoadCount 的值是2。這是因?yàn)榈谝淮蔚闹狄呀?jīng)保存了。
用兩個(gè) PAGE 同時(shí)打開 local_storage.html,并分別交替刷新,發(fā)現(xiàn)兩個(gè) PAGE 是共享一個(gè) pageLoadCount 的。
 
分析:Dom Storage 給 Web 提供了一種更錄活的數(shù)據(jù)存儲(chǔ)方式,存儲(chǔ)空間更大(相對(duì) Cookies),用法也比較簡單,方便存儲(chǔ)服務(wù)器或本地的一些臨時(shí)數(shù)據(jù)。
從 DomStorage 提供的接口來看,DomStorage 適合存儲(chǔ)比較簡單的數(shù)據(jù),如果要存儲(chǔ)結(jié)構(gòu)化的數(shù)據(jù),可能要借助 JASON了,將要存儲(chǔ)的對(duì)象轉(zhuǎn)為 JASON 字串。不太適合存儲(chǔ)比較復(fù)雜或存儲(chǔ)空間要求比較大的數(shù)據(jù),也不適合存儲(chǔ)靜態(tài)的文件等。
在 Android 內(nèi)嵌 Webview 中,需要通過 Webview 設(shè)置接口啟用 Dom Storage。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setDomStorageEnabled(true);
拿 Android 類比的話,Web 的 Dom Storage 機(jī)制類似于 Android 的 SharedPreference 機(jī)制。
2.3 Web SQL Database存儲(chǔ)機(jī)制
H5 也提供基于 SQL 的數(shù)據(jù)庫存儲(chǔ)機(jī)制,用于存儲(chǔ)適合數(shù)據(jù)庫的結(jié)構(gòu)化數(shù)據(jù)。根據(jù)官方的標(biāo)準(zhǔn)文檔,Web SQL Database 存儲(chǔ)機(jī)制不再推薦使用,將來也不再維護(hù),而是推薦使用 AppCache 和 IndexedDB。
現(xiàn)在主流的瀏覽器(點(diǎn)擊查看瀏覽器支持情況)都還是支持 Web SQL Database 存儲(chǔ)機(jī)制的。Web SQL Database 存儲(chǔ)機(jī)制提供了一組 API 供 Web App 創(chuàng)建、存儲(chǔ)、查詢數(shù)據(jù)庫。
下面通過簡單的例子,演示下 Web SQL Database 的使用。
<script>
    if(window.openDatabase){
      //打開數(shù)據(jù)庫,如果沒有則創(chuàng)建
      var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024);
 
       //通過事務(wù),創(chuàng)建一個(gè)表,并添加兩條記錄
      db.transaction(function (tx) {
           tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');
           tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "foobar")');
           tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "logmsg")');
       });
 
      //查詢表中所有記錄,并展示出來
     db.transaction(function (tx) {
         tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) {
             var len = results.rows.length, i;
             msg = "<p>Found rows: " + len + "</p>";
             for(i=0; i<len; i++){
                 msg += "<p>" + results.rows.item(i).log + "</p>";
             }
             document.querySelector('#status').innerHTML =  msg;
             }, null);
      });
}
 
</script>
 
<div id="status" name="status">Status Message</div>
將上面代碼復(fù)制到 sql_database.html 中,用瀏覽器打開,可看到下面的內(nèi)容。
 
官方建議瀏覽器在實(shí)現(xiàn)時(shí),對(duì)每個(gè) HOST 的數(shù)據(jù)庫存儲(chǔ)空間作一定限制,建議默認(rèn)是 5MB(分 HOST)的配額;達(dá)到上限后,可以申請(qǐng)更多存儲(chǔ)空間。另外,現(xiàn)在主流瀏覽器 SQL Database 的實(shí)現(xiàn)都是基于 SQLite。
分析:SQL Database 的主要優(yōu)勢(shì)在于能夠存儲(chǔ)結(jié)構(gòu)復(fù)雜的數(shù)據(jù),能充分利用數(shù)據(jù)庫的優(yōu)勢(shì),可方便對(duì)數(shù)據(jù)進(jìn)行增加、刪除、修改、查詢。由于 SQL 語法的復(fù)雜性,使用起來麻煩一些。SQL Database 也不太適合做靜態(tài)文件的緩存。
在 Android 內(nèi)嵌 Webview 中,需要通過 Webview 設(shè)置接口啟用 SQL Database,同時(shí)還要設(shè)置數(shù)據(jù)庫文件的存儲(chǔ)路徑。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setDatabaseEnabled(true);
final String dbPath = getApplicationContext().getDir("db", Context.MODE_PRIVATE).getPath();
webSettings.setDatabasePath(dbPath); 
Android 系統(tǒng)也使用了大量的數(shù)據(jù)庫用來存儲(chǔ)數(shù)據(jù),比如聯(lián)系人、短消息等;數(shù)據(jù)庫的格式也 SQLite。Android 也提供了 API 來操作 SQLite。Web SQL Database 存儲(chǔ)機(jī)制就是通過提供一組 API,借助瀏覽器的實(shí)現(xiàn),將這種 Native 的功能提供給了 Web App。
2.4 Application Cache 機(jī)制
Application Cache(簡稱 AppCache)似乎是為支持 Web App 離線使用而開發(fā)的緩存機(jī)制。它的緩存機(jī)制類似于瀏覽器的緩存(Cache-Control 和 Last-Modified)機(jī)制,都是以文件為單位進(jìn)行緩存,且文件有一定更新機(jī)制。但 AppCache 是對(duì)瀏覽器緩存機(jī)制的補(bǔ)充,不是替代。
先拿 W3C 官方的一個(gè)例子,說下 AppCache 機(jī)制的用法與功能。
<!DOCTYPE html>
<html manifest="demo_html.appcache">
<body>
 
<script src="demo_time.js"></script>
 
<p id="timePara"><button onclick="getDateTime()">Get Date and Time</button></p>
<p><img src="img_logo.gif" width="336" height="69"></p>
<p>Try opening <a href="tryhtml5_html_manifest.htm" target="_blank">this page</a>, then go offline, and reload the page. The script and the image should still work.</p>
 
</body>
</html>
上面 HTML 文檔,引用外部一個(gè) JS 文件和一個(gè) GIF 圖片文件,在其 HTML 頭中通過 manifest 屬性引用了一個(gè) appcache 結(jié)尾的文件。
我們?cè)?nbsp;Google Chrome 瀏覽器中打開這個(gè) HTML 鏈接,JS 功能正常,圖片也顯示正常。禁用網(wǎng)絡(luò),關(guān)閉瀏覽器重新打開這個(gè)鏈接,發(fā)現(xiàn) JS 工作正常,圖片也顯示正常。當(dāng)然也有可能是瀏覽緩存起的作用,我們可以在文件的瀏覽器緩存過期后,禁用網(wǎng)絡(luò)再試,發(fā)現(xiàn) HTML 頁面也是正常的。
通過 Google Chrome 瀏覽器自帶的工具,我們可以查看已經(jīng)緩存的 AppCache(分 HOST)。
 
上面截圖中的緩存,就是我們剛才打開 HTML 的頁面 AppCache。從截圖中看,HTML 頁面及 HTML 引用的 JS、GIF 圖像文件都被緩存了;另外 HTML 頭中 manifest 屬性引用的 appcache 文件也緩存了。
AppCache 的原理有兩個(gè)關(guān)鍵點(diǎn):manifest 屬性和 manifest 文件。
HTML 在頭中通過 manifest 屬性引用 manifest 文件。manifest 文件,就是上面以 appcache 結(jié)尾的文件,是一個(gè)普通文件文件,列出了需要緩存的文件。
上面截圖中的 manifest 文件,就 HTML 代碼引用的 manifest 文件。文件比較簡單,第一行是關(guān)鍵字,第二、三行就是要緩存的文件路徑(相對(duì)路徑)。這只是最簡單的 manifest 文件,完整的還包括其他關(guān)鍵字與內(nèi)容。引用 manifest 文件的 HTML 和 manifest 文件中列出的要緩存的文件最終都會(huì)被瀏覽器緩存。
完整的 manifest 文件,包括三個(gè) Section,類型 Windows 中 ini 配置文件的 Section,不過不要中括號(hào)。
1. CACHE MANIFEST - Files listed under this header will be cached after they are downloaded for the first time
2. NETWORK - Files listed under this header require a connection to the server, and will never be cached
3. FALLBACK - Files listed under this header specifies fallback pages if a page is inaccessible
完整的 manifest 文件,如:
CACHE MANIFEST
# 2012-02-21 v1.0.0
/theme.css
/logo.gif
/main.js
 
NETWORK:
login.asp
 
FALLBACK:
/html/ /offline.html 
總的來說,瀏覽器在首次加載 HTML 文件時(shí),會(huì)解析 manifest 屬性,并讀取 manifest 文件,獲取 Section:CACHE MANIFEST 下要緩存的文件列表,再對(duì)文件緩存。
AppCache 的緩存文件,與瀏覽器的緩存文件分開存儲(chǔ)的,還是一份?應(yīng)該是分開的。因?yàn)?nbsp;AppCache 在本地也有 5MB(分 HOST)的空間限制。
AppCache 在首次加載生成后,也有更新機(jī)制。被緩存的文件如果要更新,需要更新 manifest 文件。因?yàn)闉g覽器在下次加載時(shí),除了會(huì)默認(rèn)使用緩存外,還會(huì)在后臺(tái)檢查 manifest 文件有沒有修改(byte by byte)。發(fā)現(xiàn)有修改,就會(huì)重新獲取 manifest 文件,對(duì) Section:CACHE MANIFEST 下文件列表檢查更新。manifest 文件與緩存文件的檢查更新也遵守瀏覽器緩存機(jī)制。
如用用戶手動(dòng)清了 AppCache 緩存,下次加載時(shí),瀏覽器會(huì)重新生成緩存,也可算是一種緩存的更新。另外, Web App 也可用代碼實(shí)現(xiàn)緩存更新。
分析:AppCache 看起來是一種比較好的緩存方法,除了緩存靜態(tài)資源文件外,也適合構(gòu)建 Web 離線 App。在實(shí)際使用中有些需要注意的地方,有一些可以說是”坑“。
1. 要更新緩存的文件,需要更新包含它的 manifest 文件,那怕只加一個(gè)空格。常用的方法,是修改 manifest 文件注釋中的版本號(hào)。如:# 2012-02-21 v1.0.0
2. 被緩存的文件,瀏覽器是先使用,再通過檢查 manifest 文件是否有更新來更新緩存文件。這樣緩存文件可能用的不是最新的版本。
3. 在更新緩存過程中,如果有一個(gè)文件更新失敗,則整個(gè)更新會(huì)失敗。
4. manifest 和引用它的HTML要在相同 HOST。
5. manifest 文件中的文件列表,如果是相對(duì)路徑,則是相對(duì) manifest 文件的相對(duì)路徑。
6. manifest 也有可能更新出錯(cuò),導(dǎo)致緩存文件更新失敗。
7. 沒有緩存的資源在已經(jīng)緩存的 HTML 中不能加載,即使有網(wǎng)絡(luò)。例如:http://appcache-demo.s3-website-us-east-1.amazonaws.com/without-network/
8. manifest 文件本身不能被緩存,且 manifest 文件的更新使用的是瀏覽器緩存機(jī)制。所以 manifest 文件的 Cache-Control 緩存時(shí)間不能設(shè)置太長。
另外,根據(jù)官方文檔,AppCache 已經(jīng)不推薦使用了,標(biāo)準(zhǔn)也不會(huì)再支持。現(xiàn)在主流的瀏覽器都是還支持 AppCache的,以后就不太確定了。
在Android 內(nèi)嵌 Webview中,需要通過 Webview 設(shè)置接口啟用 AppCache,同時(shí)還要設(shè)置緩存文件的存儲(chǔ)路徑,另外還可以設(shè)置緩存的空間大小。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setAppCacheEnabled(true);
final String cachePath = getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
webSettings.setAppCachePath(cachePath);
webSettings.setAppCacheMaxSize(5*1024*1024);
2.5 Indexed Database
IndexedDB 也是一種數(shù)據(jù)庫的存儲(chǔ)機(jī)制,但不同于已經(jīng)不再支持的 Web SQL Database。IndexedDB 不是傳統(tǒng)的關(guān)系數(shù)據(jù)庫,可歸為 NoSQL 數(shù)據(jù)庫。IndexedDB 又類似于 Dom Storage 的 key-value 的存儲(chǔ)方式,但功能更強(qiáng)大,且存儲(chǔ)空間更大。
IndexedDB 存儲(chǔ)數(shù)據(jù)是 key-value 的形式。Key 是必需,且要唯一;Key 可以自己定義,也可由系統(tǒng)自動(dòng)生成。Value 也是必需的,但 Value 非常靈活,可以是任何類型的對(duì)象。一般 Value 都是通過 Key 來存取的。
IndexedDB 提供了一組 API,可以進(jìn)行數(shù)據(jù)存、取以及遍歷。這些 API 都是異步的,操作的結(jié)果都是在回調(diào)中返回。
下面代碼演示了 IndexedDB 中 DB 的打開(創(chuàng)建)、存儲(chǔ)對(duì)象(可理解成有關(guān)系數(shù)據(jù)的”表“)的創(chuàng)建及數(shù)據(jù)存取、遍歷基本功能。
將上面的代碼復(fù)制到 indexed_db.html 中,用 Google Chrome 瀏覽器打開,就可以添加、查詢數(shù)據(jù)。在 Chrome 的開發(fā)者工具中,能查看創(chuàng)建的 DB 、存儲(chǔ)對(duì)象(可理解成表)以及表中添加的數(shù)據(jù)。
 
IndexedDB 有個(gè)非常強(qiáng)大的功能,就是 index(索引)。它可對(duì) Value 對(duì)象中任何屬性生成索引,然后可以基于索引進(jìn)行 Value 對(duì)象的快速查詢。
要生成索引或支持索引查詢數(shù)據(jù),需求在首次生成存儲(chǔ)對(duì)象時(shí),調(diào)用接口生成屬性的索引。可以同時(shí)對(duì)對(duì)象的多個(gè)不同屬性創(chuàng)建索引。如下面代碼就對(duì)name 和 email 兩個(gè)屬性都生成了索引。
var objectStore = thisDB.createObjectStore("people",{ autoIncrement:true });
//first arg is name of index, second is the path (col);
objectStore.createIndex("name","name", {unique:false});
objectStore.createIndex("email","email", {unique:true});
生成索引后,就可以基于索引進(jìn)行數(shù)據(jù)的查詢。
function getPeopleByNameIndex(e)
{
var name = document.querySelector("#name1").value;
 
var transaction = db.transaction(["people"],"readonly");
var store = transaction.objectStore("people");
var index = store.index("name");
 
//name is some value
var request = index.get(name);
request.onsuccess = function(e) {
    var result = e.target.result;
    if(result) {
        var s = "<p><h2>Name "+name+"</h2><p>";
        for(var field in result) {
            s+= field+"="+result[field]+"<br/>";
        }
        s+="</p>";
    } else {
        document.querySelector("#status3").innerHTML = "<h2>No match!</h2>";
    }
  }
}
分析:IndexedDB 是一種靈活且功能強(qiáng)大的數(shù)據(jù)存儲(chǔ)機(jī)制,它集合了 Dom Storage 和 Web SQL Database 的優(yōu)點(diǎn),用于存儲(chǔ)大塊或復(fù)雜結(jié)構(gòu)的數(shù)據(jù),提供更大的存儲(chǔ)空間,使用起來也比較簡單??梢宰鳛?nbsp;Web SQL Database 的替代。不太適合靜態(tài)文件的緩存。
1. 以key-value 的方式存取對(duì)象,可以是任何類型值或?qū)ο?,包括二進(jìn)制。
2. 可以對(duì)對(duì)象任何屬性生成索引,方便查詢。
3. 較大的存儲(chǔ)空間,默認(rèn)推薦250MB(分 HOST),比 Dom Storage 的5MB 要大的多。
4. 通過數(shù)據(jù)庫的事務(wù)(tranction)機(jī)制進(jìn)行數(shù)據(jù)操作,保證數(shù)據(jù)一致性。
5. 異步的 API 調(diào)用,避免造成等待而影響體驗(yàn)。
Android 在4.4開始加入對(duì) IndexedDB 的支持,只需打開允許 JS 執(zhí)行的開關(guān)就好了。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
2.6 File System API
File System API 是 H5 新加入的存儲(chǔ)機(jī)制。它為 Web App 提供了一個(gè)虛擬的文件系統(tǒng),就像 Native App 訪問本地文件系統(tǒng)一樣。由于安全性的考慮,這個(gè)虛擬文件系統(tǒng)有一定的限制。Web App 在虛擬的文件系統(tǒng)中,可以進(jìn)行文件(夾)的創(chuàng)建、讀、寫、刪除、遍歷等操作。
File System API 也是一種可選的緩存機(jī)制,和前面的 SQLDatabase、IndexedDB 和 AppCache 等一樣。File System API 有自己的一些特定的優(yōu)勢(shì):
1. 可以滿足大塊的二進(jìn)制數(shù)據(jù)( large binary blobs)存儲(chǔ)需求。
2. 可以通過預(yù)加載資源文件來提高性能。
3. 可以直接編輯文件。
瀏覽器給虛擬文件系統(tǒng)提供了兩種類型的存儲(chǔ)空間:臨時(shí)的和持久性的。臨時(shí)的存儲(chǔ)空間是由瀏覽器自動(dòng)分配的,但可能被瀏覽器回收;持久性的存儲(chǔ)空間需要顯示的申請(qǐng),申請(qǐng)時(shí)瀏覽器會(huì)給用戶一提示,需要用戶進(jìn)行確認(rèn)。持久性的存儲(chǔ)空間是 WebApp 自己管理,瀏覽器不會(huì)回收,也不會(huì)清除內(nèi)容。持久性的存儲(chǔ)空間大小是通過配額來管理的,首次申請(qǐng)時(shí)會(huì)一個(gè)初始的配額,配額用完需要再次申請(qǐng)。
虛擬的文件系統(tǒng)是運(yùn)行在沙盒中。不同 WebApp 的虛擬文件系統(tǒng)是互相隔離的,虛擬文件系統(tǒng)與本地文件系統(tǒng)也是互相隔離的。
File System API 提供了一組文件與文件夾的操作接口,有同步和異步兩個(gè)版本,可滿足不同的使用場景。下面通過一個(gè)文件創(chuàng)建、讀、寫的例子,演示下簡單的功能與用法。
<script type="text/javascript">
 
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
 
//請(qǐng)求臨時(shí)文件的存儲(chǔ)空間
if (window.requestFileSystem) {
     window.requestFileSystem(window.TEMPORARY, 5*1024*1024, initFS, errorHandler);
}else{
  alert('Sorry! Your browser doesn\'t support the FileSystem API');
}
 
//請(qǐng)求成功回調(diào)
function initFS(fs){
 
  //在根目錄下打開log.txt文件,如果不存在就創(chuàng)建
  //fs就是成功返回的文件系統(tǒng)對(duì)象,fs.root代表根目錄
  fs.root.getFile('log.txt', {create: true}, function(fileEntry) {
 
  //fileEntry是返回的一個(gè)文件對(duì)象,代表打開的文件
 
  //向文件寫入指定內(nèi)容
  writeFile(fileEntry);
 
  //將寫入的內(nèi)容又讀出來,顯示在頁面上
  readFile(fileEntry);
 
  }, errorHandler);
}
 
//讀取文件內(nèi)容
function readFile(fileEntry)
{
    console.log('readFile');
 
   // Get a File object representing the file,
   // then use FileReader to read its contents.
   fileEntry.file(function(file) {
 
     console.log('createReader');
 
      var reader = new FileReader();
 
      reader.onloadend = function(e) {
 
        console.log('onloadend');
 
        var txtArea = document.createElement('textarea');
        txtArea.value = this.result;
        document.body.appendChild(txtArea);
      };
 
      reader.readAsText(file);
   }, errorHandler);
}
 
//向文件寫入指定內(nèi)容
function writeFile(fileEntry)
{
    console.log('writeFile');
 
    // Create a FileWriter object for our FileEntry (log.txt).
    fileEntry.createWriter(function(fileWriter) {
 
      console.log('createWriter');
 
      fileWriter.onwriteend = function(e) {
        console.log('Write completed');
      };
 
        fileWriter.onerror = function(e) {
          console.log('Write failed: ' + e.toString());
        };
 
        // Create a new Blob and write it to log.txt.
        var blob = new Blob(['Hello, World!'], {type: 'text/plain'});
 
        fileWriter.write(blob);
 
     }, errorHandler);
}
 
function errorHandler(err){
 var msg = 'An error occured: ' + err;
 console.log(msg);
};
 
 </script>
將上面代碼復(fù)制到 file_system_api.html 文件中,用 Google Chrome 瀏覽器打開(現(xiàn)在 File System API 只有 Chrome 43+、Opera 32+ 以及 Chrome for Android 46+ 這三個(gè)瀏覽器支持)。由于 Google Chrome 禁用了本地 HTML 文件中的 File System API功能,在啟動(dòng) Chrome 時(shí),要加上”—allow-file-access-from-files“命令行參數(shù)。
 
上面截圖,左邊是 HTML 運(yùn)行的結(jié)果,右邊是 Chrome 開發(fā)者工具中看到的 Web 的文件系統(tǒng)?;旧?nbsp;H5的幾種緩存機(jī)制的數(shù)據(jù)都能在這個(gè)開發(fā)者工具看到,非常方便。
分析:File System API 給 Web App 帶來了文件系統(tǒng)的功能,Native 文件系統(tǒng)的功能在 Web App 中都有相應(yīng)的實(shí)現(xiàn)。任何需要通過文件來管理數(shù)據(jù),或通過文件系統(tǒng)進(jìn)行數(shù)據(jù)管理的場景都比較適合。
到目前,Android 系統(tǒng)的 Webview 還不支持 File System API。
 
3 移動(dòng)端 Web 加載性能(緩存)優(yōu)化
分析完 H5提供的各種緩存機(jī)制,回到移動(dòng)端(針對(duì) Android,可能也適用于 iOS)的場景?,F(xiàn)在 Android App 大多嵌入了 Webview 的組件,通過內(nèi)嵌 Webview 來加載一些H5的運(yùn)營活動(dòng)頁面或資訊頁。這樣可充分發(fā)揮Web前端的優(yōu)勢(shì):快速開發(fā)、發(fā)布,靈活上下線。但 Webview 也有一些不可忽視的問題,比較突出的就是加載相對(duì)較慢,會(huì)相對(duì)消耗較多流量。
通過對(duì)一些 H5頁面進(jìn)行調(diào)試及抓包發(fā)現(xiàn),每次加載一個(gè) H5頁面,都會(huì)有較多的請(qǐng)求。除了 HTML 主 URL 自身的請(qǐng)求外,HTML外部引用的 JS、CSS、字體文件、圖片都是一個(gè)獨(dú)立的 HTTP 請(qǐng)求,每一個(gè)請(qǐng)求都串行的(可能有連接復(fù)用)。這么多請(qǐng)求串起來,再加上瀏覽器解析、渲染的時(shí)間,Web 整體的加載時(shí)間變得較長;請(qǐng)求文件越多,消耗的流量也會(huì)越多。我們可綜合使用上面說到幾種緩存機(jī)制,來幫助我們優(yōu)化 Web 的加載性能。
 
結(jié)論:綜合各種緩存機(jī)制比較,對(duì)于靜態(tài)文件,如 JS、CSS、字體、圖片等,適合通過瀏覽器緩存機(jī)制來進(jìn)行緩存,通過緩存文件可大幅提升 Web 的加載速度,且節(jié)省流量。但也有一些不足:緩存文件需要首次加載后才會(huì)產(chǎn)生;瀏覽器緩存的存儲(chǔ)空間有限,緩存有被清除的可能;緩存的文件沒有校驗(yàn)。
對(duì)于 Web 在本地或服務(wù)器獲取的數(shù)據(jù),可以通過 Dom Storage 和 IndexedDB 進(jìn)行緩存。也在一定程度上減少和 Server 的交互,提高加載速度,同時(shí)節(jié)省流量。
當(dāng)然 Web 的性能優(yōu)化,還包括選擇合適的圖片大小,避免 JS 和 CSS 造成的阻塞等。這就需要 Web 前端的同事根據(jù)一些規(guī)范和一些調(diào)試工具進(jìn)行優(yōu)化了。
 
0 分享到:
和我們?cè)诰€交談!