深度理解 圖片預(yù)加載和緩存機(jī)制

本文通過兩個(gè)圖片預(yù)加載案例引起的緩存相關(guān)問題,探討了圖片預(yù)加載處理技術(shù),和瀏覽器網(wǎng)絡(luò)請求以及緩存機(jī)制的一些問題。

2017-04-25 By Herbert Chow

問題起源分析:

一、騰訊游戲:征服星際 戰(zhàn)出未來 移動(dòng)視頻h5中(如圖1),采用了預(yù)加載圖片技術(shù),先有一個(gè)loading頁,然后一個(gè)視頻,之后是一個(gè)落地頁結(jié)束。

在預(yù)加載圖片時(shí),圖片會(huì)有一定的概率發(fā)生重復(fù)請求(重復(fù)加載)的情況并且被重復(fù)加載的圖片會(huì)出現(xiàn)閃爍,就號像根本就沒有預(yù)加載一樣,原因不明,如(圖2),而且如果通過自己手寫函數(shù)進(jìn)行預(yù)加載圖片,會(huì)有極大幾率發(fā)生重復(fù)請求的想象;而該用pxloader.js進(jìn)行圖片加載的話,幾率大大減少,但不為100%不出現(xiàn)。

a.png

圖1

a2.png

圖2

二、陰陽師扭蛋預(yù)約h5(如圖3):與上問題一相似,使用網(wǎng)易內(nèi)部組件trueload組件進(jìn)行加載,在預(yù)加載圖片時(shí),會(huì)有一定幾率會(huì)重復(fù)加載zhaohuanzhen_的序列幀套圖,本來只有46張,后面重復(fù)加載到了85張,或者92張(完全重復(fù)請求了一遍)。流程是,先loading頁,再到登陸頁,再到抽獎(jiǎng)頁面,最后是結(jié)果頁面。在抽獎(jiǎng)頁面,會(huì)用到canvas調(diào)用loading時(shí)緩存的序列幀圖片。

a3.png

圖3

注:當(dāng)圖片重復(fù)請求時(shí),會(huì)造成圖片閃爍,有可能是因?yàn)樾蛄袔瑒?dòng)畫切換比較連貫,所以看起來閃爍會(huì)比較明顯,而平時(shí)hover時(shí)看可能不那么明顯。本質(zhì)問題應(yīng)該還是圖片重復(fù)請求的問題。

問題假設(shè):

一、是否只要在頁面中請求一次圖片A并且成功后,那么圖片A在后續(xù)的設(shè)置img.src或者設(shè)置bgi時(shí),都不會(huì)發(fā)生重復(fù)請求,而是直接用緩存;

二、是否只要圖片的路徑是一樣的,讀取了一次成功后,就不需再次請求,直接讀緩存。

論證猜測(不供證明,僅供參考):

一、用Pxloader或者trueLoad等組件進(jìn)行加載時(shí),會(huì)比自己手寫加載函數(shù)(大神請忽略)會(huì)好很多,實(shí)驗(yàn)證明,自己手寫預(yù)加載函數(shù)一般會(huì)缺少一些邏輯判斷,導(dǎo)致效果不好,會(huì)有較大概率出現(xiàn)重復(fù)請求的情況。查閱一下Pxloader的源碼,發(fā)覺,簡單的一個(gè)圖片加載函數(shù),代碼量也還是挺大的,不是我們自己一百幾十行就能搞定的。

舉例:有可能出現(xiàn)bug原因是,我們自己寫的加載函數(shù)還沒加載完圖片,然后業(yè)務(wù)邏輯代碼已經(jīng)又進(jìn)行了一次圖片的請求,這樣就會(huì)造成同一張圖同時(shí)被請求了兩次。

成因剖析(第一部分):預(yù)備概念的理解

1、圖片src或者background-image (下面簡稱bgi)設(shè)置和切換問題:

參考鏈接: https://github.com/jieyou/lazyload#%E4%B8%8Esrcset%E5%B1%9E%E6%80%A7%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8

參考鏈接中提到,“創(chuàng)建的Image對象會(huì)加載一次,實(shí)際DOM樹中的元素設(shè)置 src 或 background-image 時(shí)又會(huì)加載一次”,只要image對象被創(chuàng)建,并且設(shè)置其src屬性時(shí),瀏覽器就會(huì)發(fā)起請求,或者h(yuǎn)tml中dom結(jié)構(gòu)中的圖片img標(biāo)簽src發(fā)生變化,big發(fā)生變化時(shí),也會(huì)發(fā)生請求。

2、瀏覽器對于圖片請求的流程

參考鏈接:http://er.dadaaierer.com/?p=431

參考鏈接中提到:

關(guān)于圖片緩存

如果圖片已經(jīng)在緩存中:

1)瀏覽器不會(huì)發(fā)起請求去請求圖片,瀏覽器直接從緩存中拿圖片。

2)而設(shè)置過期時(shí)間會(huì)強(qiáng)迫瀏覽器在訪問頁面的時(shí)候去請求圖片,如果圖片已經(jīng)在緩存中,并且正在被重新請求,瀏覽器會(huì)把最后修改時(shí)間加入在HTTP頭中,這就是傳統(tǒng)的GET請求,如果圖片沒有被修改,服務(wù)器會(huì)返回一個(gè)304代碼,所以對于瀏覽器的請求服務(wù)器會(huì)返回下面的兩種代碼:

200–瀏覽器沒有緩存。

304–瀏覽器已經(jīng)緩存了圖片,但是需要驗(yàn)證最后修改時(shí)間

也就是說,瀏覽器在需要進(jìn)行圖片操作之前,會(huì)先查看本地是否有緩存,如果有,會(huì)先讀取緩存;如果沒有,才會(huì)去發(fā)起網(wǎng)絡(luò)請求。

成因剖析(第二部分):分析原因

一、如圖4,是騰訊外星人h5在一定概率刷新時(shí),發(fā)生重復(fù)請求,此刻注意到網(wǎng)絡(luò)部分,size圖片請求時(shí)間是0ms,而圖片大小其實(shí)是沒有標(biāo)明的,寫著 from menory cachefrom disk cache,此刻還注意到,網(wǎng)絡(luò)請求時(shí)200而不是應(yīng)該是語氣中的304,十分奇怪。

a4.png

圖4

二、如圖5,陰陽師扭蛋h5,有一定概率,在發(fā)起200請求之后預(yù)加載圖片后,發(fā)起重復(fù)請求并且返回狀態(tài)碼304。

a5.png

圖5

三、這里有幾點(diǎn)要注意

1、200和304跟緩存的聯(lián)系

2、請求發(fā)起者initiator

3、size(from memory cache和from disk cache)

四、知識點(diǎn)再次分析

對于上面三點(diǎn)

1、參考鏈接: https://www.oschina.net/question/1395553_175941

參考文中提到,

其實(shí), 200 OK (from cache) 是瀏覽器沒有跟服務(wù)器確認(rèn),直接用了瀏覽器緩存;而 304 Not Modified 是瀏覽器和服務(wù)器多確認(rèn)了一次緩存有效性,再用的緩存。200(from cache) 是速度最快的,因?yàn)椴恍枰L問遠(yuǎn)程服務(wù)器,直接使用本地緩存.304 的過程是, 先請求服務(wù)器, 然后服務(wù)器告訴我們這個(gè)資源沒變, 瀏覽器再使用本地緩存.

結(jié)論: 需要設(shè)置 200 from cache. 這樣才是解決問題之道.

所以說,其實(shí)200(from cache)并沒有重新請求下載圖片,而是和304是相似的原理,就是瀏覽器并沒有重新下載圖片,只是用了本地緩存,只不過瀏覽器會(huì)多了一重請求驗(yàn)證。至于這個(gè)具體是返回200from cache還是304,貌似需要看服務(wù)器返回的東西是否有設(shè)置ETag值了(這部分需要詳細(xì)了解的話,請自行查閱資料)。

2、initiator是請求發(fā)起者,說白了就是你在哪里執(zhí)行了設(shè)置img.src屬性或者設(shè)置了bgi。在扭蛋項(xiàng)目中,筆者進(jìn)行了預(yù)加載(由trueload.js發(fā)起)完成之后,馬上執(zhí)行了一段由index.js執(zhí)行的設(shè)置new Image()數(shù)組保存圖片這么一個(gè)操作,用于后面的序列幀動(dòng)畫。而其實(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的時(shí)候,瀏覽器會(huì)向服務(wù)器發(fā)起請求,這個(gè)請求容量非常小,相對于200完整請求的20k完整圖片而且,只有20B左右的大小,實(shí)際上,它返回的是304的結(jié)果本身,而不是圖片數(shù)據(jù)。

重中之重的是這個(gè)from menory cache(內(nèi)存緩存)from disk cache(磁盤緩存)

參考鏈接:http://blog.csdn.net/myloveyaqiong/article/details/52762795

(圖6)文中提及,安卓系統(tǒng)對于網(wǎng)絡(luò)圖片緩存機(jī)制是,優(yōu)先讀取Memorycache,找不到之后會(huì)讀取Diskcache,最后都沒有才會(huì)發(fā)起網(wǎng)絡(luò)請求。

翻閱《webkit技術(shù)內(nèi)幕》,書中提及,瀏覽器也是如此,當(dāng)m cache和d cache中都沒找到資源時(shí),會(huì)發(fā)起網(wǎng)絡(luò)請求獲取最新資源。

a6.png

圖6

成因剖析(第三部分):結(jié)論總結(jié)

一、回顧問題假設(shè)一,答案是否定的。原因是進(jìn)了頁面以后,即使用了預(yù)加載技術(shù),在后面的邏輯中再次創(chuàng)建并設(shè)置新的img.src或者bgi時(shí),依然要經(jīng)過上面提及的圖片資源獲取步驟:

優(yōu)先讀取Memorycache,找不到之后會(huì)讀取Diskcache,最后都沒有才會(huì)發(fā)起網(wǎng)絡(luò)請求。也就是說,舉例,一開始用了trueload預(yù)加載了圖片a0.jpg之后(此時(shí)是trueload發(fā)起),圖片被加入到了內(nèi)存緩存和磁盤緩存中,而一段時(shí)間(或者一段邏輯跑完之后,來到index.js的某段代碼),在index.js里面再次設(shè)置新的img.src時(shí),由于某種原因,index.js發(fā)起獲取圖片a0.jpg在memorycache內(nèi)存緩存里面找不到了,于是只能去diskcache磁盤緩存里面找,這個(gè)時(shí)候就會(huì)引起一個(gè)重復(fù)請求的現(xiàn)象。

二、回顧問題假設(shè)二:答案也是否定的。根據(jù)上述獲取資源原理,同一路徑資源的圖片,即使在預(yù)加載或頁面中有了第一次加載之后,后續(xù)代碼段中再次訪問該路徑圖片,依然有可能會(huì)造成前者能成功訪問memorycache里的資源,而后者則失敗從而導(dǎo)致要到diskcache里面去找圖,這樣的情況。

三、筆者尚不熟悉m cache的d cache處理數(shù)據(jù)的具體原理,尚未弄清為何導(dǎo)致memorycache內(nèi)圖片資源丟失,或者訪問不了的情況,導(dǎo)致別的邏輯代碼在二次訪問的時(shí)候需要去d cache里面找資源,這可能和時(shí)間,系統(tǒng)內(nèi)存,或者initiator請求發(fā)起者有關(guān)。還請各路大神指教一下。

四、至于返回結(jié)果是200from cache還是304,則需要服務(wù)器那邊設(shè)置。但是可以明確一點(diǎn)的是,如果圖片在訪問頁面時(shí)被請求成果過一次之后,那么后續(xù)再次訪問則會(huì)有緩存,無論了是否重新發(fā)出請求,磁盤緩存中已經(jīng)是有緩存到的了,所以在一定程度上已經(jīng)是成功達(dá)到了圖片預(yù)加載的目的。也就是說即使是重復(fù)請求了,那也是返回200from cache還是304,能夠快速使用緩存,不必重新加載200成功完成圖片請求。只不過再進(jìn)一步優(yōu)化的話,就是重復(fù)請求都不用了而已。

解決方案:(暫時(shí)只提供思路,后面會(huì)補(bǔ)上詳細(xì)說明以及demo等)

一、采用專業(yè)的圖片預(yù)加載插件,會(huì)比自己重新寫的預(yù)加載函數(shù)要嚴(yán)謹(jǐn)和高效(高手請忽略),一般移動(dòng)推薦pxloader,pc推薦jq的imgpreload。

二、一般情況下,大家只會(huì)在預(yù)加載時(shí)load一遍圖片,然后后面訪問圖片時(shí)就直接設(shè)置img.src或者bgi:url(地址),這樣在通常情況沒有問題,但是有可能在一定概率下發(fā)生重復(fù)請求。建議改成在load圖片時(shí),把需要load的圖片中的重點(diǎn)圖片,比如序列幀這種需要同一個(gè)階段大量調(diào)用一組圖片,這時(shí)就可以把這組圖片保存在一個(gè)圖片數(shù)組變量中imgs[ ],然后后面就直接使用這個(gè)imgs[ ],不要重新創(chuàng)建對象再賦值圖片地址。

三、調(diào)整預(yù)加載和訪問的代碼邏輯順序。如果不想像方案二那樣浪費(fèi)一個(gè)全局變量,可以再頁面loading頁加載完成后,不要馬上調(diào)用“設(shè)置imgsrc保存到新數(shù)組,以便后續(xù)調(diào)用”的邏輯代碼,而應(yīng)該放到后面業(yè)務(wù)中的,比如點(diǎn)擊按鈕之后才調(diào)用,這樣可以解決剛進(jìn)入頁面時(shí)并發(fā)請求過多導(dǎo)致頁面卡頓的問題。

四、如果是采用bgi較多的項(xiàng)目,在調(diào)用預(yù)加載函數(shù)時(shí),同時(shí)加載css,會(huì)發(fā)生css內(nèi)的bgi地址和預(yù)加載js調(diào)用的地址相撞上,而發(fā)生重復(fù)請求的想象。所以,解決方法是,進(jìn)頁面時(shí)先把該css文件設(shè)置disabled="",屏蔽掉css加載,完了之后再remove掉disabled屬性。

五、根據(jù)垃圾回收機(jī)制,可以把只調(diào)用一次的大量圖片集,用全局圖片數(shù)組變量保存起來,并在完成業(yè)務(wù)調(diào)用后,清除該全局變量imgs=null,回收不必要的內(nèi)存。

其他補(bǔ)充:

待定

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容