H5瀏覽器和webview后退緩存機制

一、背景

用戶點擊瀏覽器工具欄中的后退按鈕,或者移動設備上的返回鍵時,或者JS執行history.go(-1);時,瀏覽器會在當前窗口“打開”歷史紀錄中的前一個頁面。不同的瀏覽器在“打開”前一個頁面的表現上并不統一,這和瀏覽器的實現以及頁面本身的設置都有關系。
 在移動端HTML5瀏覽器和webview中,“后退到前一個頁面”意味著:前一個頁面的html/js/css等靜態資源的請求(甚至是ajax動態接口請求)根本不會重新發送,直接使用緩存的響應,而不管這些靜態資源響應的緩存策略是否被設置了禁用狀態。(這點我在自己的項目中也確實得到了驗證,按回退按鈕的時候抓包并沒有抓到任何請求)。
 在我自己項目中因為涉及到存取cookie的原因,由于返回不刷新而導致一系列的bug,所以需要‘回退刷新’的需求。
 “回退刷新”的目標是瀏覽器在后退返回到前一個頁面時,能從server端請求到一個全新的的頁面內容(即status code 200 ok或status code 304 not modified的頁面響應,而不是status 200 from cache根本不向server端請求)進行加載展示并重新執行JS代碼。

二、解決方案

瀏覽器歷史紀錄和HTTP 緩存

PC瀏覽器實現后退刷新的方法是給響應添加Cache-Control的header,如果server返回頁面響應的headers中包含如下內容:

Cache-Control: no-cache,no-store,must-revalidate
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache

瀏覽器在前進后退到該頁面時,就會重新發送請求。
 我們自己控制的話需要在頭部加相關的meta標簽

<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" /> //設置頁面過期時間
<meta http-equiv="pragma" content="no-cache" /> //

或者設置響應頭

res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
res.header('Expires', '-1');
res.header('Pragma', 'no-cache');

相比較而言,在header中設置比設置meta標簽更為靠譜一些,但是也存在兩者都沒效果的情況。
 這樣看上去,瀏覽器歷史紀錄和HTTP緩存是有關系的。事實上不是這樣的,參考
 You Do Not Understand Browser History,里面的結論是:
 The browser does not respect HTTP caching rules when you click the back button.(當你點擊返回按鈕的時候瀏覽器不會遵循http緩存機制)
 看來瀏覽器也是很任性的...

bfcache和page cache

bfcache和page cache是webkit和firefox有一項優化技術。可參考:
 1、Using_Firefox_1.5_caching
 2、WebKit Page Cache I – The Basics 和 WebKit Page Cache II – The unload Event
 這里簡單介紹一下:
 對于支持bfcache/page cache的瀏覽器,“后退”不光意味著html/js/css/接口等動靜態資源不會重新請求,連JS也不會重新執行。因為前一個頁面沒有被unload,最后離開時的狀態和數據被完整地保留在內存中,發生后退時瀏覽器直接把“離開時”的頁面狀態展示給用戶。
 就好像,你在頁面A,點擊鏈接要在當前窗口打開頁面B,這時瀏覽器在不卸載頁面A的情況下去加載頁面B。這時你看到的是頁面B,那頁面A呢? 頁面A只是被隱藏了,JS暫停執行(我們稱之為pagehide)。如果用戶點擊“返回”,瀏覽器快速把頁面B隱藏,并把頁面A再顯示出來,JS恢復執行(我們稱之為頁面B pagehide, 頁面A pageshow)。
 pageshow事件在頁面全新加載并展現時也會觸發,與從bfcache/page cache中加載并展示的區分依據是pageshow event的persisted屬性。如果從緩存獲取那么persisted的值就為true。
 實際觀察中發現,一些移動端瀏覽器的pageshow event的persisted屬性值一直是false,盡管頁面看上去確實是從bfcache/page cache中加載展示。(另外一個理論上的point,頁面綁定了unload事件時,不再會進入bfcache/page cache,一些移動端瀏覽器上觀察來看實際上也不是這樣的)。
 可行的方案是:JS監聽pagehide/pageshow來阻止頁面進入bfcache/page cache,或者監測到頁面從bfcache/page cache中加載展現時進行刷新。參考:
Forcing mobile Safari to re-evaluate the cached page when user presses back button
 示例代碼:

window.onpageshow = function(event) {
    if (event.persisted) {
        window.location.reload()
    }
};

安卓webview cache的問題

安卓webview,包括安卓微信里面內嵌的QQ X5內核瀏覽器,都存在后退不會重新請求頁面的問題,無論頁面是否禁用緩存。上面的pageshow/pagehide方案也都失效。可行的方法,如下:
 1. 給每個需要后退刷新的頁面上加一個hidden input,存儲頁面在服務端的生成時間,作為頁面的服務端版本號。
 2. 并附加一段JS讀取讀取頁面的版本號,同時也記錄在瀏覽器/webview本地(cookie/localStorage/sessionStorage)進行存儲,作為本地版本號。
 3. JS檢查頁面的服務端版本號和本地存儲中的版本號,如果服務端版本號大于本地存儲中版本號,說明頁面是從服務端重新生成的;否則頁面就是本地緩存的,即發生了后退行為。
 4. JS在監測到后退時,強制頁面重新從服務端獲取。
 該方案的前提是瀏覽器在向server請求頁面時,每次都用jsp重新生成html。需要頁面本身有禁用緩存的配置。
 方案的代碼示例如下:

<!-- 安卓webview 后退強制刷新解決方案 START -->
<jsp:useBean id="now" class="java.util.Date" />
<input type="hidden" id="SERVER_TIME" value="${now.getTime()}"/>
<script>
//每次webview重新打開H5首頁,就把server time記錄本地存儲
var SERVER_TIME = document.getElementById("SERVER_TIME");
var REMOTE_VER = SERVER_TIME && SERVER_TIME.value;
if(REMOTE_VER){
    var LOCAL_VER = sessionStorage && sessionStorage.PAGEVERSION;
    if(LOCAL_VER && parseInt(LOCAL_VER) >= parseInt(REMOTE_VER)){
        //說明html是從本地緩存中讀取的
        location.reload(true);
    }else{
        //說明html是從server端重新生成的,更新LOCAL_VER
        sessionStorage.PAGEVERSION = REMOTE_VER;
    }
}
</script>
<!-- 安卓webview 后退強制刷新解決方案 END -->

三、總結

1. PC瀏覽器,設置禁用頁面緩存header即可實現后退刷新
 2. 支持bfcache/page cache的移動端瀏覽器,JS監聽pageshow/pagehide,在檢測到后退時強制刷新
 3. 在前2個方案都不work的情況下,可以在HTML中寫入服務端頁面生成版本號,與本地存儲中的版本號對比判斷是否發生了后退并使用緩存中的頁面

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容