本文通過兩個圖片預(yù)加載案例引起的緩存相關(guān)問題旺上,探討了圖片預(yù)加載處理技術(shù),和瀏覽器網(wǎng)絡(luò)請求以及緩存機制的一些問題捂人。
2017-04-25 By Herbert Chow
問題起源分析:
一纵揍、騰訊游戲:征服星際 戰(zhàn)出未來 移動視頻h5中(如圖1),采用了預(yù)加載圖片技術(shù)刷允,先有一個loading頁冤留,然后一個視頻,之后是一個落地頁結(jié)束树灶。
在預(yù)加載圖片時纤怒,圖片會有一定的概率發(fā)生重復(fù)請求(重復(fù)加載)的情況并且被重復(fù)加載的圖片會出現(xiàn)閃爍,就號像根本就沒有預(yù)加載一樣天通,原因不明泊窘,如(圖2),而且如果通過自己手寫函數(shù)進行預(yù)加載圖片像寒,會有極大幾率發(fā)生重復(fù)請求的想象烘豹;而該用pxloader.js進行圖片加載的話,幾率大大減少诺祸,但不為100%不出現(xiàn)携悯。
圖1
圖2
二、陰陽師扭蛋預(yù)約h5(如圖3):與上問題一相似筷笨,使用網(wǎng)易內(nèi)部組件trueload組件進行加載憔鬼,在預(yù)加載圖片時,會有一定幾率會重復(fù)加載zhaohuanzhen_的序列幀套圖胃夏,本來只有46張轴或,后面重復(fù)加載到了85張,或者92張(完全重復(fù)請求了一遍)仰禀。流程是照雁,先loading頁,再到登陸頁悼瘾,再到抽獎頁面囊榜,最后是結(jié)果頁面。在抽獎頁面亥宿,會用到canvas調(diào)用loading時緩存的序列幀圖片卸勺。
圖3
注:當(dāng)圖片重復(fù)請求時,會造成圖片閃爍烫扼,有可能是因為序列幀動畫切換比較連貫曙求,所以看起來閃爍會比較明顯,而平時hover時看可能不那么明顯映企。本質(zhì)問題應(yīng)該還是圖片重復(fù)請求的問題悟狱。
問題假設(shè):
一、是否只要在頁面中請求一次圖片A并且成功后堰氓,那么圖片A在后續(xù)的設(shè)置img.src或者設(shè)置bgi時挤渐,都不會發(fā)生重復(fù)請求,而是直接用緩存双絮;
二浴麻、是否只要圖片的路徑是一樣的得问,讀取了一次成功后,就不需再次請求软免,直接讀緩存宫纬。
論證猜測(不供證明,僅供參考):
一膏萧、用Pxloader或者trueLoad等組件進行加載時漓骚,會比自己手寫加載函數(shù)(大神請忽略)會好很多,實驗證明榛泛,自己手寫預(yù)加載函數(shù)一般會缺少一些邏輯判斷蝌蹂,導(dǎo)致效果不好,會有較大概率出現(xiàn)重復(fù)請求的情況曹锨。查閱一下Pxloader的源碼叉信,發(fā)覺,簡單的一個圖片加載函數(shù)艘希,代碼量也還是挺大的,不是我們自己一百幾十行就能搞定的硅急。
舉例:有可能出現(xiàn)bug原因是覆享,我們自己寫的加載函數(shù)還沒加載完圖片,然后業(yè)務(wù)邏輯代碼已經(jīng)又進行了一次圖片的請求营袜,這樣就會造成同一張圖同時被請求了兩次撒顿。
成因剖析(第一部分):預(yù)備概念的理解
1、圖片src或者background-image (下面簡稱bgi)設(shè)置和切換問題:
參考鏈接中提到荚板,“創(chuàng)建的Image對象會加載一次凤壁,實際DOM樹中的元素設(shè)置 src 或 background-image 時又會加載一次”,只要image對象被創(chuàng)建跪另,并且設(shè)置其src屬性時拧抖,瀏覽器就會發(fā)起請求,或者html中dom結(jié)構(gòu)中的圖片img標(biāo)簽src發(fā)生變化免绿,big發(fā)生變化時唧席,也會發(fā)生請求。
2嘲驾、瀏覽器對于圖片請求的流程
參考鏈接:http://er.dadaaierer.com/?p=431
參考鏈接中提到:
關(guān)于圖片緩存
如果圖片已經(jīng)在緩存中:
1)瀏覽器不會發(fā)起請求去請求圖片淌哟,瀏覽器直接從緩存中拿圖片。
2)而設(shè)置過期時間會強迫瀏覽器在訪問頁面的時候去請求圖片辽故,如果圖片已經(jīng)在緩存中徒仓,并且正在被重新請求,瀏覽器會把最后修改時間加入在HTTP頭中誊垢,這就是傳統(tǒng)的GET請求掉弛,如果圖片沒有被修改症见,服務(wù)器會返回一個304代碼,所以對于瀏覽器的請求服務(wù)器會返回下面的兩種代碼:
200–瀏覽器沒有緩存狰晚。
304–瀏覽器已經(jīng)緩存了圖片筒饰,但是需要驗證最后修改時間
也就是說,瀏覽器在需要進行圖片操作之前壁晒,會先查看本地是否有緩存瓷们,如果有,會先讀取緩存秒咐;如果沒有谬晕,才會去發(fā)起網(wǎng)絡(luò)請求。
成因剖析(第二部分):分析原因
一携取、如圖4攒钳,是騰訊外星人h5在一定概率刷新時,發(fā)生重復(fù)請求雷滋,此刻注意到網(wǎng)絡(luò)部分不撑,size圖片請求時間是0ms,而圖片大小其實是沒有標(biāo)明的晤斩,寫著 from menory cache 和 from disk cache焕檬,此刻還注意到,網(wǎng)絡(luò)請求時200而不是應(yīng)該是語氣中的304澳泵,十分奇怪实愚。
圖4
二、如圖5兔辅,陰陽師扭蛋h5腊敲,有一定概率,在發(fā)起200請求之后預(yù)加載圖片后维苔,發(fā)起重復(fù)請求并且返回狀態(tài)碼304碰辅。
圖5
三、這里有幾點要注意
1蕉鸳、200和304跟緩存的聯(lián)系
2乎赴、請求發(fā)起者initiator
3、size(from memory cache和from disk cache)
四潮尝、知識點再次分析
對于上面三點
1榕吼、參考鏈接: https://www.oschina.net/question/1395553_175941
參考文中提到,
其實勉失, 200 OK (from cache) 是瀏覽器沒有跟服務(wù)器確認(rèn)羹蚣,直接用了瀏覽器緩存;而 304 Not Modified 是瀏覽器和服務(wù)器多確認(rèn)了一次緩存有效性乱凿,再用的緩存顽素。200(from cache) 是速度最快的,因為不需要訪問遠程服務(wù)器,直接使用本地緩存.304 的過程是, 先請求服務(wù)器, 然后服務(wù)器告訴我們這個資源沒變, 瀏覽器再使用本地緩存.
結(jié)論: 需要設(shè)置 200 from cache. 這樣才是解決問題之道.
所以說咽弦,其實200(from cache)并沒有重新請求下載圖片,而是和304是相似的原理胁出,就是瀏覽器并沒有重新下載圖片型型,只是用了本地緩存,只不過瀏覽器會多了一重請求驗證全蝶。至于這個具體是返回200from cache還是304闹蒜,貌似需要看服務(wù)器返回的東西是否有設(shè)置ETag值了(這部分需要詳細了解的話,請自行查閱資料)抑淫。
2绷落、initiator是請求發(fā)起者,說白了就是你在哪里執(zhí)行了設(shè)置img.src屬性或者設(shè)置了bgi始苇。在扭蛋項目中砌烁,筆者進行了預(yù)加載(由trueload.js發(fā)起)完成之后,馬上執(zhí)行了一段由index.js執(zhí)行的設(shè)置new Image()數(shù)組保存圖片這么一個操作催式,用于后面的序列幀動畫函喉。而其實initiator:other標(biāo)明的就是一些html的資源文件,包括了js荣月,html函似,css之類,所以圖5這里的other喉童,就是index.js。相同的顿天,在騰訊外星人h5中的首次請求和重復(fù)請求的發(fā)起者分別就是pxload.js和index.js(indexjs中執(zhí)行到了設(shè)置bgi的代碼)堂氯。
3、核心關(guān)鍵
size標(biāo)明牌废,304的時候咽白,瀏覽器會向服務(wù)器發(fā)起請求,這個請求容量非常小鸟缕,相對于200完整請求的20k完整圖片而且晶框,只有20B左右的大小,實際上懂从,它返回的是304的結(jié)果本身授段,而不是圖片數(shù)據(jù)。
重中之重的是這個from menory cache(內(nèi)存緩存) 和 from disk cache(磁盤緩存)
參考鏈接:http://blog.csdn.net/myloveyaqiong/article/details/52762795
(圖6)文中提及番甩,安卓系統(tǒng)對于網(wǎng)絡(luò)圖片緩存機制是侵贵,優(yōu)先讀取Memorycache,找不到之后會讀取Diskcache缘薛,最后都沒有才會發(fā)起網(wǎng)絡(luò)請求窍育。
翻閱《webkit技術(shù)內(nèi)幕》卡睦,書中提及,瀏覽器也是如此漱抓,當(dāng)m cache和d cache中都沒找到資源時表锻,會發(fā)起網(wǎng)絡(luò)請求獲取最新資源。
圖6
成因剖析(第三部分):結(jié)論總結(jié)
一乞娄、回顧問題假設(shè)一瞬逊,答案是否定的。原因是進了頁面以后补胚,即使用了預(yù)加載技術(shù)码耐,在后面的邏輯中再次創(chuàng)建并設(shè)置新的img.src或者bgi時,依然要經(jīng)過上面提及的圖片資源獲取步驟:
優(yōu)先讀取Memorycache溶其,找不到之后會讀取Diskcache骚腥,最后都沒有才會發(fā)起網(wǎng)絡(luò)請求。也就是說瓶逃,舉例束铭,一開始用了trueload預(yù)加載了圖片a0.jpg之后(此時是trueload發(fā)起),圖片被加入到了內(nèi)存緩存和磁盤緩存中厢绝,而一段時間(或者一段邏輯跑完之后契沫,來到index.js的某段代碼),在index.js里面再次設(shè)置新的img.src時昔汉,由于某種原因懈万,index.js發(fā)起獲取圖片a0.jpg在memorycache內(nèi)存緩存里面找不到了,于是只能去diskcache磁盤緩存里面找靶病,這個時候就會引起一個重復(fù)請求的現(xiàn)象会通。
二、回顧問題假設(shè)二:答案也是否定的娄周。根據(jù)上述獲取資源原理涕侈,同一路徑資源的圖片,即使在預(yù)加載或頁面中有了第一次加載之后煤辨,后續(xù)代碼段中再次訪問該路徑圖片裳涛,依然有可能會造成前者能成功訪問memorycache里的資源,而后者則失敗從而導(dǎo)致要到diskcache里面去找圖众辨,這樣的情況端三。
三、筆者尚不熟悉m cache的d cache處理數(shù)據(jù)的具體原理鹃彻,尚未弄清為何導(dǎo)致memorycache內(nèi)圖片資源丟失技肩,或者訪問不了的情況,導(dǎo)致別的邏輯代碼在二次訪問的時候需要去d cache里面找資源,這可能和時間虚婿,系統(tǒng)內(nèi)存旋奢,或者initiator請求發(fā)起者有關(guān)。還請各路大神指教一下然痊。
四至朗、至于返回結(jié)果是200from cache還是304,則需要服務(wù)器那邊設(shè)置剧浸。但是可以明確一點的是锹引,如果圖片在訪問頁面時被請求成果過一次之后,那么后續(xù)再次訪問則會有緩存唆香,無論了是否重新發(fā)出請求嫌变,磁盤緩存中已經(jīng)是有緩存到的了,所以在一定程度上已經(jīng)是成功達到了圖片預(yù)加載的目的躬它。也就是說即使是重復(fù)請求了腾啥,那也是返回200from cache還是304,能夠快速使用緩存冯吓,不必重新加載200成功完成圖片請求倘待。只不過再進一步優(yōu)化的話,就是重復(fù)請求都不用了而已组贺。
解決方案:(暫時只提供思路凸舵,后面會補上詳細說明以及demo等)
一、采用專業(yè)的圖片預(yù)加載插件失尖,會比自己重新寫的預(yù)加載函數(shù)要嚴(yán)謹(jǐn)和高效(高手請忽略)啊奄,一般移動推薦pxloader,pc推薦jq的imgpreload掀潮。
二增热、一般情況下,大家只會在預(yù)加載時load一遍圖片胧辽,然后后面訪問圖片時就直接設(shè)置img.src或者bgi:url(地址),這樣在通常情況沒有問題公黑,但是有可能在一定概率下發(fā)生重復(fù)請求邑商。建議改成在load圖片時,把需要load的圖片中的重點圖片凡蚜,比如序列幀這種需要同一個階段大量調(diào)用一組圖片人断,這時就可以把這組圖片保存在一個圖片數(shù)組變量中imgs[ ],然后后面就直接使用這個imgs[ ]朝蜘,不要重新創(chuàng)建對象再賦值圖片地址恶迈。
三、調(diào)整預(yù)加載和訪問的代碼邏輯順序。如果不想像方案二那樣浪費一個全局變量暇仲,可以再頁面loading頁加載完成后步做,不要馬上調(diào)用“設(shè)置imgsrc保存到新數(shù)組,以便后續(xù)調(diào)用”的邏輯代碼奈附,而應(yīng)該放到后面業(yè)務(wù)中的全度,比如點擊按鈕之后才調(diào)用,這樣可以解決剛進入頁面時并發(fā)請求過多導(dǎo)致頁面卡頓的問題斥滤。
四将鸵、如果是采用bgi較多的項目,在調(diào)用預(yù)加載函數(shù)時佑颇,同時加載css顶掉,會發(fā)生css內(nèi)的bgi地址和預(yù)加載js調(diào)用的地址相撞上,而發(fā)生重復(fù)請求的想象挑胸。所以痒筒,解決方法是,進頁面時先把該css文件設(shè)置disabled=""嗜暴,屏蔽掉css加載凸克,完了之后再remove掉disabled屬性。
五闷沥、根據(jù)垃圾回收機制萎战,可以把只調(diào)用一次的大量圖片集,用全局圖片數(shù)組變量保存起來舆逃,并在完成業(yè)務(wù)調(diào)用后蚂维,清除該全局變量imgs=null,回收不必要的內(nèi)存路狮。
其他補充:
待定