WebView优化提升H5加载速度方案
WebView優(yōu)化提升H5加載速度方案
WebView加載H5經(jīng)歷的過(guò)程圖示
上圖體現(xiàn)的是用戶打開(kāi)一個(gè)H5頁(yè)面,經(jīng)歷的過(guò)程與代碼內(nèi)部所做的事情的對(duì)應(yīng)關(guān)系。
用戶:無(wú)感知(WebView進(jìn)行初始化)->看到白屏(DNS,Connection,接收頁(yè)面)->看到Loading界面(靜態(tài)資源加載完畢后,拉取數(shù)據(jù))->展現(xiàn)(數(shù)據(jù)請(qǐng)求成功)
再來(lái)看一下,一個(gè)Url是如何被瀏覽器加載并且呈現(xiàn)的,之前學(xué)習(xí)Java web知識(shí)的時(shí)候,接觸到一個(gè)頁(yè)面的加載流程為:
接下來(lái)分析一下各個(gè)階段的耗時(shí)情況
1."WebView初始化"階段
眾所周知,談?wù)揂ndroid APP的啟動(dòng)時(shí)間,我們喜歡說(shuō)冷啟和熱啟,那么談?wù)揥ebView的初始化時(shí)間,我們也從應(yīng)用首次初始化和二次初始化,兩個(gè)方面進(jìn)行對(duì)比。
使用同一Android機(jī)器,對(duì)同一個(gè)App的活動(dòng)頁(yè),進(jìn)行20次測(cè)試,取平均值,得到的結(jié)果如下:
| 192.79 ms | 142.53 ms |
從上面的測(cè)試數(shù)據(jù),我們大概可以找到一個(gè)“頁(yè)面加載總是很慢”的原因之一:
前端H5開(kāi)發(fā)人員在開(kāi)發(fā)頁(yè)面時(shí),測(cè)算的頁(yè)面加載速度,是從“頁(yè)面建立連接后”作為起始時(shí)間,“頁(yè)面數(shù)據(jù)展示完成”作為結(jié)束時(shí)間。而這樣的話,到手機(jī)移動(dòng)端上,他們需要再加上200ms左右的時(shí)間。
那么如何解決這個(gè)問(wèn)題呢?
這個(gè)做法很簡(jiǎn)單,只要在客戶端啟動(dòng)后(或者某個(gè)契機(jī)點(diǎn),只要在使用WebView之前),就初始化一個(gè)全局WebView,隱藏起來(lái)備用,當(dāng)用戶通過(guò)WebView訪問(wèn)H5頁(yè)面時(shí),使用這個(gè)全局的WebView來(lái)加載對(duì)應(yīng)的網(wǎng)頁(yè)即可。這種方案可以省去用戶感知WebView的初始化這段時(shí)間。
缺點(diǎn):內(nèi)存消耗,WebView之前使用的痕跡需清除,使用不當(dāng)容易造成內(nèi)存泄露。
2."建立連接與服務(wù)器端處理請(qǐng)求并返回"階段
這個(gè)階段,主要耗時(shí)在于:DNS解析,客戶端與服務(wù)器端建立Connection,服務(wù)器處理完后返回值(首字節(jié)獲取到,做為返回值的時(shí)間點(diǎn))
以美團(tuán)的某個(gè)活動(dòng)頁(yè)面的鏈接時(shí)間作為測(cè)試統(tǒng)計(jì):
| 50% | 1.3 ms | 71 ms | 172 ms |
| 90% | 60 ms | 360 ms | 541 ms |
這些時(shí)間都發(fā)生在網(wǎng)頁(yè)加載之前,那么優(yōu)化的方案無(wú)法從代碼上入手,但是不表示完全無(wú)法優(yōu)化,讓我們看看幾個(gè)過(guò)程中可以優(yōu)化的點(diǎn)。
(1) DNS和Connection:由于系統(tǒng)會(huì)進(jìn)行DNS緩存,即比如客戶端App使用過(guò)域名api.meitu.com進(jìn)行接口數(shù)據(jù)請(qǐng)求,那么該域名對(duì)應(yīng)DNS解析出來(lái)的IP已經(jīng)在系統(tǒng)級(jí)別上被緩存過(guò)了。那么得出結(jié)論,我們想優(yōu)化這塊時(shí)間,只需要盡可能將頁(yè)面中的靜態(tài)資源或者接口請(qǐng)求的域名保持與客戶端App中一致。
(如上表格,分位值90%,說(shuō)明至少有10%的用戶是60ms的DNS解析,當(dāng)我們控制了此操作后,能把大部分DNS解析控制到1.3ms的范圍內(nèi)即可)
(2) 服務(wù)器處理API:根據(jù)請(qǐng)求,通過(guò)業(yè)務(wù)API獲取數(shù)據(jù)并返回。這段時(shí)間的優(yōu)化,我們可以采用chunk編碼,通過(guò)在header中設(shè)置 transfer-encoding:chunked 使得頁(yè)面可以分塊輸出,讓前端H5開(kāi)發(fā)人員合理設(shè)計(jì)頁(yè)面,讓head部分都是確定的靜態(tài)資源版本相關(guān)內(nèi)容,而body部分是業(yè)務(wù)數(shù)據(jù)相關(guān)內(nèi)容,那么我們可以在用戶請(qǐng)求的時(shí)候,首先將Web API可以確定的部分先輸出給瀏覽器,然后等API完全獲取后,再將API數(shù)據(jù)傳輸給瀏覽器。
如果采用普通方式輸出頁(yè)面,則頁(yè)面會(huì)在服務(wù)器請(qǐng)求完所有API并處理完成后開(kāi)始傳輸。瀏覽器要在后端所有API都加載完成后才能開(kāi)始解析。
如果采用chunk-encoding: chunked,并優(yōu)先將頁(yè)面的靜態(tài)部分輸出;然后處理API請(qǐng)求,并最終返回頁(yè)面,可以讓后端的API請(qǐng)求和前端的資源加載同時(shí)進(jìn)行。
兩者的總共后端時(shí)間并沒(méi)有區(qū)別,但是可以提升首字節(jié)速度,從而讓前端加載資源和后端加載API不互相阻塞。
3.瀏覽器解析與頁(yè)面渲染
頁(yè)面在解析到足夠多的節(jié)點(diǎn),且所有CSS都加載完成后進(jìn)行首屏渲染。
在此之前,頁(yè)面都會(huì)保持白屏;在頁(yè)面完全下載并解析完成之前,頁(yè)面處于不完整展示狀態(tài)。
測(cè)試頁(yè)面:http://i.meituan.com/firework/meituanxianshifengqiang
在Mac上面,模擬4G情況
測(cè)試得到的時(shí)間耗費(fèi)如下:
| DOM下載 | 58ms | 29.5?KB | 4G網(wǎng)絡(luò) |
| DOM解析 | 12.5ms | 198?KB | 根據(jù)估算,在手機(jī)上慢2~5倍不等 |
| CSS請(qǐng)求+下載 | 58ms | 11.7?KB | 4G網(wǎng)絡(luò)(包含鏈接時(shí)間,CDN) |
| CSS解析 | 2.89ms | 54.1?KB | 根據(jù)估算,在手機(jī)上慢2~5倍不等 |
| 渲染 | 23ms | 1361節(jié)點(diǎn) | 根據(jù)估算,在手機(jī)上慢2~5倍不等 |
| 繪制 | 4.1ms | 根據(jù)估算,在手機(jī)上慢2~5倍不等 | |
| 合成 | 0.23ms | GPU處理 |
同時(shí),對(duì)HTML的加載時(shí)間進(jìn)行分析,可以得到如下時(shí)間:
| HTML加載完成時(shí)間 | 218ms | performance.timing.responseEnd - performance.timing.fetchStart |
| HTML解析完成時(shí)間 | 330ms | performance.timing.domInteractive - performance.timing.fetchStart |
(1) CSS與JS的HTML順序排布優(yōu)化頁(yè)面解析
可以看到HTML加載完成時(shí)間和HTML解析完成時(shí)間,中間間隔了112ms,那么這里面有什么貓膩,為什么一個(gè)DOM解析需要耗費(fèi)這么久的時(shí)間,我們進(jìn)一步看前端頁(yè)面代碼可以發(fā)現(xiàn),在H5頁(yè)面的Header部分有這段代碼:
..... <link href="//ms0.meituan.net/css/eve.9d9eee71.css" rel="stylesheet" onload="MT.pageData.eveTime=Date.now()"/> <script> window.fk = function (callback) { require(['util/native/risk.js'], function (risk) {risk.getFk(callback); }); } </script> </head> ....那么這段代碼有什么異常嗎,我們需要了解一些HTML解析的知識(shí):
通常情況下,CSS不會(huì)阻塞HTML的解析,但如果CSS后面有JS,則會(huì)阻塞JS的執(zhí)行直到CSS加載完成(即便JS是內(nèi)聯(lián)的腳本),從而間接阻塞HTML的解析。
CSS不會(huì)阻止頁(yè)面繼續(xù)向下繼續(xù)。
內(nèi)聯(lián)的JS很快執(zhí)行完成,然后繼續(xù)解析文檔。
然而,當(dāng)這兩部分同時(shí)出現(xiàn)的時(shí)候,問(wèn)題就來(lái)了。
CSS加載阻塞了下面的一段內(nèi)聯(lián)JS的執(zhí)行,而被阻塞的內(nèi)聯(lián)JS則阻塞了HTML的解析。
一個(gè)小小的內(nèi)聯(lián)JS放錯(cuò)位置也會(huì)讓性能下降很多,所以:
- CSS的加載會(huì)在HTML解析到CSS的標(biāo)簽時(shí)就開(kāi)始,所以CSS的標(biāo)簽要盡量靠前。
- CSS鏈接下面不能有任何的JS標(biāo)簽(包括很簡(jiǎn)單的內(nèi)聯(lián)JS),否則會(huì)阻塞HTML的解析。
- 如果必須要在頭部增加內(nèi)聯(lián)腳本,一定要放在CSS標(biāo)簽之前。
(2) JS加載優(yōu)化頁(yè)面解析
對(duì)于一個(gè)大型網(wǎng)站來(lái)說(shuō),JS代碼可以說(shuō)是非常多的,下載JS代碼,解析,編譯,執(zhí)行的時(shí)間都非常影響頁(yè)面顯示時(shí)間。
我們用以下方式來(lái)檢驗(yàn)JS代碼的解析/編譯和執(zhí)行時(shí)間:
在t1~t2期間,JS代碼僅僅聲明了一個(gè)函數(shù),主要時(shí)間會(huì)集中在解析和編譯過(guò)程;
在t2~t3時(shí)間段內(nèi),執(zhí)行test時(shí)時(shí)間主要為代碼的執(zhí)行時(shí)間
| 13ms / 40ms | 43ms / 127ms | 26ms / 353ms |
從這個(gè)數(shù)據(jù)來(lái)看,我們可以知道偏重的框架,例如React,僅僅JS的編譯和執(zhí)行時(shí)間就會(huì)達(dá)到30ms ~ 350ms,因此JS的優(yōu)化也不容小覷。
優(yōu)化建議:
- JS盡量按照“基礎(chǔ)庫(kù)” + “頁(yè)面代碼”分別打包
- 高性能要求頁(yè)面盡量使用后端渲染。
- React太重了,使用需要謹(jǐn)慎考慮。
- JS代碼的編譯和執(zhí)行會(huì)有緩存,同App中網(wǎng)頁(yè)盡量統(tǒng)一框架。
4.H5靜態(tài)資源模板包預(yù)加載與服務(wù)器端差分包配合優(yōu)化
(1) H5靜態(tài)資源模板包預(yù)加載
居然我們知道HTML靜態(tài)資源的請(qǐng)求需要耗費(fèi)我們比較多的時(shí)間,那么可以將需求變更較小,使用較為頻繁的H5頁(yè)面的靜態(tài)資源部分打包成靜態(tài)資源Zip包(Zip包的版本信息帶在Zip包的包名上),當(dāng)客戶端app啟動(dòng)后或者在該H5頁(yè)面入口曝光的地方,進(jìn)行預(yù)加載H5靜態(tài)資源模板Zip包,并且Zip包解壓到本地的固定緩存位置。
而WebView解析HTML頁(yè)面的時(shí)候,當(dāng)頁(yè)面中的靜態(tài)資源請(qǐng)求時(shí),WebViewClient的shouldInterceptRequest中,我們攔截下來(lái),指向?qū)?yīng)的資源文件位置即可。
(2) 服務(wù)器端差分包
由于(1)中我們的方案,我們需要預(yù)下載Zip模板包,但是每個(gè)頁(yè)面的資源文件,可能非常大而改動(dòng)其實(shí)非常小,所以常用的方案是我們采用dsdiff的算法,服務(wù)器端將每個(gè)模板包的版本記錄下來(lái),當(dāng)客戶端請(qǐng)求預(yù)加載的時(shí)候,帶上客戶端之前zip包的版本信息與服務(wù)器端的最新版本進(jìn)行匹配,服務(wù)器端將對(duì)應(yīng)的差分包地址下發(fā)到客戶端,一般修改也就10+kb的差分zip包,客戶端再將老版本zip + diff差分包zip合并得到一個(gè)完整的最新zip包,解壓即可。
5.客戶端預(yù)加載HTML中的API數(shù)據(jù)
由于HTML的解析與API數(shù)據(jù)請(qǐng)求是串行的操作,所以我們可以考慮通過(guò)服務(wù)器端下發(fā)部分URL對(duì)應(yīng)需要的API數(shù)據(jù)接口,當(dāng)某個(gè)URL地址接口曝光時(shí),我們可以預(yù)先去請(qǐng)求API數(shù)據(jù)接口,進(jìn)入WebView后,通過(guò)JsBridge將數(shù)據(jù)傳遞給HTML頁(yè)面,由HTML完成數(shù)據(jù)的填充到頁(yè)面中去。
總結(jié)
加載網(wǎng)頁(yè)的過(guò)程中,客戶端,網(wǎng)絡(luò),服務(wù)器端,CPU,GPU等都會(huì)參與,各自都有相關(guān)的修改方案和工作,盡可能的并行處理或者預(yù)先處理,不相互阻塞,相互等待,才可以讓網(wǎng)頁(yè)加載得更快。
- WebView初始化慢,初始化好一個(gè)全局的WebView待用
- 服務(wù)端數(shù)據(jù)處理慢,可以讓服務(wù)端進(jìn)行分塊輸出,在后端進(jìn)行數(shù)據(jù)處理時(shí),讓前端也可以有網(wǎng)絡(luò)靜態(tài)資源加載。
- JS腳本執(zhí)行慢,讓腳本最后運(yùn)行,不要阻塞HTML頁(yè)面解析。可以采用“基礎(chǔ)庫(kù)”+“頁(yè)面代碼”的方式把框架拆分出來(lái)。
- WebView初始化慢,可以通過(guò)客服端預(yù)先或者同時(shí)先請(qǐng)求數(shù)據(jù),讓后端和網(wǎng)絡(luò)不要閑著等待WebView初始化。
- 合理的預(yù)加載和緩存,增量更新可以讓頁(yè)面展示速度提升得更多。
- DNS和Connection慢,盡量復(fù)用客戶端使用的域名和鏈接。
一些做頁(yè)面加載優(yōu)化的Github開(kāi)源項(xiàng)目:
騰訊VasSonic : https://github.com/Tencent/VasSonic
CacheWebView : https://github.com/yale8848/CacheWebView
參考文章:
https://mp.weixin.qq.com/s/evzDnTsHrAr2b9jcevwBzA
https://tech.meituan.com/2017/06/09/webviewperf.html
總結(jié)
以上是生活随笔為你收集整理的WebView优化提升H5加载速度方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 服务器常见错误代码500、501、502
- 下一篇: 海媚服务器怎么进系统,海媚v3服务器配置