WebView性能、體驗(yàn)分析與優(yōu)化

  • 在App開發(fā)中懒豹,內(nèi)嵌WebView始終占有著一席之地芙盘。它能以較低的成本實(shí)現(xiàn)Android、iOS和Web的復(fù)用脸秽,也可以冠冕堂皇的突破蘋果對(duì)熱更新的封鎖儒老。
  • 然而便利性的同時(shí),WebView的性能體驗(yàn)卻備受質(zhì)疑豹储,導(dǎo)致很多客戶端中需要?jiǎng)討B(tài)更新等頁面時(shí)不得不采用其他方案贷盲。
  • 以發(fā)展的眼光來看淘这,功能的動(dòng)態(tài)加載以及三端的融合將會(huì)是大趨勢(shì)剥扣。那么如何克服WebView固有的問題呢巩剖?我們將從性能、內(nèi)存消耗钠怯、體驗(yàn)佳魔、安全幾個(gè)維度,來系統(tǒng)的分析客戶端默認(rèn)WebView的問題晦炊,以及對(duì)應(yīng)的優(yōu)化方案鞠鲜。

性能

  • 對(duì)于WebView的性能,給人最直觀的莫過于:打開速度比native慢断国。
  • 是的贤姆,當(dāng)我們打開一個(gè)WebView頁面,頁面往往會(huì)慢吞吞的loading很久稳衬,若干秒后才出現(xiàn)你所需要看到的頁面霞捡。
  • 這是為什么呢?
  • 對(duì)于一個(gè)普通用戶來講薄疚,打開一個(gè)WebView通常會(huì)經(jīng)歷以下幾個(gè)階段:
  • 1.交互無反饋
  • 2.到達(dá)新的頁面碧信,頁面白屏
  • 3.頁面基本框架出現(xiàn),但是沒有數(shù)據(jù)街夭;頁面處于loading狀態(tài)
  • 4.出現(xiàn)所需的數(shù)據(jù)

如果從程序上觀察砰碴,WebView啟動(dòng)過程大概分為以下幾個(gè)階段:


WebView啟動(dòng)時(shí)間

如何縮短這些過程的時(shí)間,就成了優(yōu)化WebView性能的關(guān)鍵板丽。
接下來我們逐一分析各個(gè)階段的耗時(shí)情況呈枉,以及需要注意的優(yōu)化點(diǎn)。

WebView初始化

當(dāng)App首次打開時(shí)埃碱,默認(rèn)是并不初始化瀏覽器內(nèi)核的碴卧;只有當(dāng)創(chuàng)建WebView實(shí)例的時(shí)候,才會(huì)創(chuàng)建WebView的基礎(chǔ)框架乃正。
所以與瀏覽器不同住册,App中打開WebView的第一步并不是建立連接,而是啟動(dòng)瀏覽器內(nèi)核瓮具。
我們來分析一下這段耗時(shí)到底需要多久荧飞。

分析

針對(duì)WebView的初始化時(shí)間,我們可以定義兩個(gè)指標(biāo):

首次初始化時(shí)間:客戶端冷啟動(dòng)后名党,第一次打開WebView叹阔,從開始創(chuàng)建WebView到開始建立網(wǎng)絡(luò)連接之間的時(shí)間。
二次初始化時(shí)間:在打開過WebView后传睹,退出WebView耳幢,再重新打開WebView,從開始創(chuàng)建WebView到開始建立網(wǎng)絡(luò)連接之間的時(shí)間。
測(cè)試數(shù)據(jù):

  • 測(cè)試系統(tǒng)1: iOS模擬器睛藻,Titans 10.0.7
  • 測(cè)試系統(tǒng)2: OPPO R829T Android 4.2.2
  • 測(cè)試方式:測(cè)試10次取平均值
  • 測(cè)試App:美團(tuán)外賣
  • 單位:ms

| 測(cè)試 | 首次初始化時(shí)間 | 二次初始化時(shí)間 |
| ------------- |:-------------:| -----:|
| iOS(UIWebView) | 306.56 | 76.43 |
| iOS(WKWebView) | 763.26 | 457.25 |
|Android | 192.79 * | 142.53 |
Android外賣客戶端啟動(dòng)后會(huì)在后臺(tái)開啟WebView進(jìn)程启上,故并不是完全新建WebView時(shí)間。

這意味著什么呢店印?

作為前端工程師冈在,統(tǒng)計(jì)了無數(shù)次的頁面打開時(shí)間,都是以網(wǎng)絡(luò)連接開始作為起點(diǎn)的按摘。
很遺憾的通知您:WebView中用戶體驗(yàn)到的打開時(shí)間需要再增加70~700ms包券。
于是我們找到了“為什么WebView總是很慢”的原因之一:

  • 在瀏覽器中,我們輸入地址時(shí)(甚至在之前)炫贤,瀏覽器就可以開始加載頁面溅固。
  • 而在客戶端中,客戶端需要先花費(fèi)時(shí)間初始化WebView完成后兰珍,才開始加載发魄。

而這段時(shí)間,由于WebView還不存在俩垃,所有后續(xù)的過程是完全阻塞的励幼。可以這樣形容WebView初始化過程:

image.png

那么有哪些解決辦法呢口柳?

怎么優(yōu)化

由于這段過程發(fā)生在native的代碼中苹粟,單純靠前端代碼是無法優(yōu)化的;大部分的方案都是前端和客戶端協(xié)作完成跃闹,以下是幾個(gè)業(yè)界采用過的方案嵌削。

全局WebView

方法:

  • 在客戶端剛啟動(dòng)時(shí),就初始化一個(gè)全局的WebView待用望艺,并隱藏苛秕;
  • 當(dāng)用戶訪問了WebView時(shí),直接使用這個(gè)WebView加載對(duì)應(yīng)網(wǎng)頁找默,并展示艇劫。

這種方法可以比較有效的減少WebView在App中的首次打開時(shí)間。當(dāng)用戶訪問頁面時(shí)惩激,不需要初始化WebView的時(shí)間店煞。
當(dāng)然這也帶來了一些問題,包括:

  • 額外的內(nèi)存消耗风钻。
  • 頁面間跳轉(zhuǎn)需要清空上一個(gè)頁面的痕跡顷蟀,更容易內(nèi)存泄露。
    【參考東軟專利 - 加載網(wǎng)頁的方法及裝置 CN106250434A

客戶端代理數(shù)據(jù)請(qǐng)求

方法:

  • 在客戶端初始化WebView的同時(shí)骡技,直接由native開始網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)鸣个;
  • 當(dāng)頁面初始化完成后,向native獲取其代理請(qǐng)求的數(shù)據(jù)。

此方法雖然不能減小WebView初始化時(shí)間囤萤,但數(shù)據(jù)請(qǐng)求和WebView初始化可以并行進(jìn)行昼窗,總體的頁面加載時(shí)間就縮短了;縮短總體的頁面加載時(shí)間:
【參考騰訊分享:70%以上業(yè)務(wù)由H5開發(fā)阁将,手機(jī)QQ Hybrid 的架構(gòu)如何優(yōu)化演進(jìn)膏秫?

還有其他各種優(yōu)化的方式右遭,不再一一列舉做盅,總結(jié)起來都是圍繞兩點(diǎn):

  • 在使用前預(yù)先初始化好WebView,從而減小耗時(shí)窘哈。
  • 在初始化的同時(shí)吹榴,通過Native來完成一些網(wǎng)絡(luò)請(qǐng)求等過程,使得WebView初始化不是完全的阻塞后續(xù)過程滚婉。

建立連接/服務(wù)器處理

在頁面請(qǐng)求的數(shù)據(jù)返回之前图筹,主要有以下過程耗費(fèi)時(shí)間。

  • DNS
  • connection
  • 服務(wù)器處理

分析

以下為美團(tuán)中活動(dòng)頁面的鏈接時(shí)間統(tǒng)計(jì):
統(tǒng)計(jì): 美團(tuán)的活動(dòng)頁面
內(nèi)容值: n%分位值(ms)

| 內(nèi)容值 | DNS | connection | 獲取首字節(jié) |
|---------|:------------- |:-------------:| -----:|
| 50% | 1.3 | 71 | 172|
| 90% | 60 | 360 | 541|

優(yōu)化

這些時(shí)間都是發(fā)生在網(wǎng)頁加載之前让腹,但這并不意味著無法優(yōu)化远剩,有以下幾種方法。
DNS采用和客戶端API相同的域名
DNS會(huì)在系統(tǒng)級(jí)別進(jìn)行緩存骇窍,對(duì)于WebView的地址瓜晤,如果使用的域名與native的API相同,則可以直接使用緩存的DNS而不用再發(fā)起請(qǐng)求圖片腹纳。

以美團(tuán)為例痢掠,美團(tuán)的客戶端請(qǐng)求域名主要位于api.meituan.com,然而內(nèi)嵌的WebView主要位于 i.meituan.com嘲恍。

當(dāng)我們初次打開App時(shí):

  • 客戶端首次打開都會(huì)請(qǐng)求api.meituan.com足画,其DNS將會(huì)被系統(tǒng)緩存。
  • 然而當(dāng)打開WebView的時(shí)候佃牛,由于請(qǐng)求了不同的域名淹辞,需要重新獲取i.meituan.com的IP。

根據(jù)上面的統(tǒng)計(jì),至少10%的用戶打開WebView時(shí)耗費(fèi)了60ms在DNS上面思币,如果WebView的域名與App的API域名統(tǒng)一竭宰,則可以讓W(xué)ebView的DNS時(shí)間全部達(dá)到1.3ms的量級(jí)。
靜態(tài)資源同理攻冷,最好與客戶端的資源域名保持一致。

同步渲染采用chunk編碼

同步渲染時(shí)如果后端請(qǐng)求時(shí)間過長(zhǎng)遍希,可以考慮采用chunk編碼等曼,將數(shù)據(jù)放在最后,并優(yōu)先將靜態(tài)內(nèi)容flush。對(duì)于傳統(tǒng)的后端渲染頁面禁谦,往往都是使用的【瀏覽器】--> 【W(wǎng)eb API】 --> 【業(yè)務(wù) API】的加載模式胁黑,其中后端時(shí)間就指的是Web API的處理時(shí)間了。在這里Web API一般有兩個(gè)作用:

  • 確定靜態(tài)資源的版本州泊。
  • 根據(jù)用戶的請(qǐng)求丧蘸,去業(yè)務(wù)API獲取數(shù)據(jù)。

而一般確定靜態(tài)資源的版本往往是直接讀取代碼版本遥皂,基本無耗時(shí)力喷;而主要的后端時(shí)間都花費(fèi)在了業(yè)務(wù)API請(qǐng)求上面。

那么怎么優(yōu)化利用這段時(shí)間呢演训?

在HTTP協(xié)議中弟孟,我們可以在header中設(shè)置 transfer-encoding:chunked
使得頁面可以分塊輸出。如果合理設(shè)計(jì)頁面样悟,讓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ù)傳輸給瀏覽器震糖。
下圖可以直觀的看出分chunk輸出和一起輸出的區(qū)別:


分chunk加載
  • 如果采用普通方式輸出頁面录肯,則頁面會(huì)在服務(wù)器請(qǐng)求完所有API并處理完成后開始傳輸。瀏覽器要在后端所有API都加載完成后才能開始解析试伙。
  • 如果采用chunk-encoding: chunked嘁信,并優(yōu)先將頁面的靜態(tài)部分輸出;然后處理API請(qǐng)求疏叨,并最終返回頁面潘靖,可以讓后端的API請(qǐng)求和前端的資源加載同時(shí)進(jìn)行。
  • 兩者的總共后端時(shí)間并沒有區(qū)別蚤蔓,但是可以提升首字節(jié)速度卦溢,從而讓前端加載資源和后端加載API不互相阻塞。

頁面框架渲染

頁面在解析到足夠多的節(jié)點(diǎn)秀又,且所有CSS都加載完成后進(jìn)行首屏渲染单寂。在此之前,頁面保持白屏吐辙;在頁面完全下載并解析完成之前宣决,頁面處于不完整展示狀態(tài)。

分析
我們以一個(gè)美團(tuán)的活動(dòng)頁面作為樣例:
測(cè)試頁面:http://i.meituan.com/firework/meituanxianshifengqiang
在Mac上面昏苏,模擬4G情況
頁面樣式:

頁面

測(cè)試得到的時(shí)間耗費(fèi)如下:

表1

| 階段 |時(shí)間 | 大小| 備注|
|---------|:------------- |:-------------:| -----:|
| 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處理|

表2

| 指標(biāo) |時(shí)間 | 計(jì)算方法| 備注|
|---------|:------------- |:-------------:| -----:|
| HTML加載完成時(shí)間 | 218 | performance.timing.responseEnd - performance.timing.fetchStart | 4G網(wǎng)絡(luò)|
| HTML解析完成時(shí)間 | 330 | performance.timing.domInteractive - performance.timing.fetchStart | 根據(jù)估算,在手機(jī)上慢2~5倍不等|

這意味著什么呢屁商?

對(duì)于表1
可以看到烟很,隨著在網(wǎng)絡(luò)優(yōu)良的情況下,Dom的解析所占耗時(shí)比例還是不算低的蜡镶,對(duì)于低端機(jī)器更甚雾袱。Layout時(shí)間也是首屏前耗時(shí)的大頭,據(jù)猜測(cè)這與頁面使用了rem作為單位有關(guān)(待進(jìn)一步分析)帽哑。

對(duì)于表2谜酒,我們可以發(fā)現(xiàn)一個(gè)問題
一般來說HTML在開始接收到返回?cái)?shù)據(jù)的時(shí)候就開始解析HTML并構(gòu)建DOM樹叹俏。如果沒有JS(JavaScript)阻塞的話一般會(huì)相繼完成妻枕。然而,在這里時(shí)間相差了90ms……也就是說粘驰,解析被阻塞了屡谐。

進(jìn)一步分析可以發(fā)現(xiàn),頁面的header部分有這樣的代碼:

<link 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>


>通常情況下蝌数,上面代碼的link部分和script部分如果單獨(dú)出現(xiàn)愕掏,都不會(huì)阻塞頁面的解析:
* CSS不會(huì)阻止頁面繼續(xù)向下繼續(xù)。
* 內(nèi)聯(lián)的JS很快執(zhí)行完成顶伞,然后繼續(xù)解析文檔饵撑。

>然而,當(dāng)這兩部分同時(shí)出現(xiàn)的時(shí)候唆貌,問題就來了滑潘。
* CSS加載阻塞了下面的一段內(nèi)聯(lián)JS的執(zhí)行,而被阻塞的內(nèi)聯(lián)JS則阻塞了HTML的解析锨咙。

>通常情況下语卤,CSS不會(huì)阻塞HTML的解析,但如果CSS后面有JS酪刀,則會(huì)阻塞JS的執(zhí)行直到CSS加載完成(即便JS是內(nèi)聯(lián)的腳本)粹舵,從而間接阻塞HTML的解析。

##優(yōu)化
>在頁面框架加載這一部分骂倘,能夠優(yōu)化的點(diǎn)參照[雅虎14條](https://stevesouders.com/hpws/rules.php)就夠了眼滤;但注意不要犯錯(cuò),一個(gè)小小的內(nèi)聯(lián)JS放錯(cuò)位置也會(huì)讓性能下降很多历涝。
* CSS的加載會(huì)在HTML解析到CSS的標(biāo)簽時(shí)開始诅需,所以CSS的標(biāo)簽要盡量靠前情妖。
* 但是,CSS鏈接下面不能有任何的JS標(biāo)簽(包括很簡(jiǎn)單的內(nèi)聯(lián)JS)诱担,否則會(huì)阻塞HTML的解析毡证。
* 如果必須要在頭部增加內(nèi)聯(lián)腳本,一定要放在CSS標(biāo)簽之前蔫仙。

>![ CSS帶來的阻塞解析 ](http://upload-images.jianshu.io/upload_images/1242701-d45481392dc42929.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

##JS加載
>對(duì)于大型的網(wǎng)站來說料睛,在此我們先提出幾個(gè)問題:
* 將全部JS代碼打成一個(gè)包,造成首次執(zhí)行代碼過大怎么辦摇邦?
* 將JS以細(xì)粒度打包恤煞,造成請(qǐng)求過多怎么辦?
* 將JS按 "基礎(chǔ)庫" + "頁面代碼" 分別打包施籍,要怎么界定什么是基礎(chǔ)代碼居扒,什么是頁面代碼;不同頁面用的基礎(chǔ)代碼不一致怎么辦丑慎?
* 單一文件的少量代碼改的是否會(huì)導(dǎo)致緩存失效喜喂?
* 代碼模塊間有動(dòng)態(tài)依賴,怎樣合并請(qǐng)求竿裂。

>關(guān)于這些問題的解決方案數(shù)量可能會(huì)比問題還多玉吁,而它們也各有優(yōu)劣。

>具體分析太過復(fù)雜腻异,鑒于篇幅原因在這里不做具體分析了进副。您可以期待我們的后續(xù)計(jì)劃:BPM(瀏覽器包管理)。

##JS解析悔常、編譯影斑、執(zhí)行
>在PC互聯(lián)網(wǎng)時(shí)代,人們似乎都快忘記了JS的解析和執(zhí)行還需要消耗時(shí)間机打。確實(shí)矫户,在幾年前網(wǎng)速還在用kb衡量的時(shí)代里,JS的解析時(shí)間在整個(gè)頁面的打開時(shí)間里只能算是九牛一毛姐帚。

>然而吏垮,隨著網(wǎng)速越來越快,而CPU的速度反而沒有提升(從PC到手機(jī))罐旗,JS的時(shí)間開銷就成為問題了膳汪。那么JS的編譯和解析,在當(dāng)今的頁面上要消耗多少時(shí)間呢九秀?

>####分析
我們用以下方式來檢驗(yàn)JS代碼的解析/編譯和執(zhí)行時(shí)間:

<script>
window.t1 = performance.now()
</script>
<script>
window.test = function () {
// test code
}
</script>
<script>
window.t2 = performance.now()
test();
window.t3 = performance.now();

alert("編譯耗時(shí):" + (t2 - t1));
alert("執(zhí)行耗時(shí):" + (t3 - t2));

</script>

>將測(cè)試代碼放入 【test code】 位置遗嗽,然后在手機(jī)中執(zhí)行;

>* 在t1~t2期間鼓蜒,JS代碼僅僅聲明了一個(gè)函數(shù)痹换,主要時(shí)間會(huì)集中在解析和編譯過程征字;
* 在t2~t3時(shí)間段內(nèi),執(zhí)行test時(shí)時(shí)間主要為代碼的執(zhí)行時(shí)間

>在首次啟動(dòng)客戶端后娇豫,打開WebView的測(cè)試頁面匙姜,我們可以得到如下的結(jié)果:

>* 測(cè)試系統(tǒng): iPhone6 iOS 10.2.1
* 測(cè)試系統(tǒng): OPPO R829T Android 4.2.2
* 內(nèi)容值: 編譯時(shí)間(ms)/執(zhí)行時(shí)間(ms)

>|  系統(tǒng) |Zepto.js   | Vue.js| React.js + ReactDOM.js|
|---------|:------------- |:-------------:| -----:|
| iOS    | 5.2 / 8   |  12.8 / 16.1 |   13.7 / 43.3|
| Android    | 13 / 40    |     43 / 127 |  26 / 353|

>當(dāng)保持客戶端進(jìn)行不關(guān)閉情況下,關(guān)閉WebView并重新訪問測(cè)試頁面冯痢,再次測(cè)試得到如下結(jié)果:

>|  系統(tǒng) |Zepto.js   | Vue.js| React.js + ReactDOM.js|
|---------|:------------- |:-------------:| -----:|
| iOS    | 0.9 / 1.9     |  5 / 7.41 |  3.5 / 23|
| Android    | 5 / 9   |        17 / 12 |   25 / 60|

>執(zhí)行時(shí)間指的是框架代碼加載的頁面的初始化時(shí)間氮昧,沒有任何業(yè)務(wù)的調(diào)用。

>**這意味著什么**
經(jīng)過測(cè)試可以得出以下結(jié)論:

>* 偏重的框架浦楣,例如React袖肥,僅僅初始化的時(shí)間就會(huì)達(dá)到50ms ~ 350ms,這在對(duì)性能敏感的業(yè)務(wù)中時(shí)比較不利的振劳。
* 在App的啟動(dòng)周期內(nèi)椎组,統(tǒng)一域名下的代碼會(huì)被緩存編輯和初始化結(jié)果,重復(fù)調(diào)用性能較好历恐。
所以寸癌,在移動(dòng)瀏覽器上,JS的解析和執(zhí)行時(shí)間并不是不可忽略的夹供。

>在低端安卓機(jī)上灵份,(框架的初始化+異步數(shù)據(jù)請(qǐng)求+業(yè)務(wù)代碼執(zhí)行)會(huì)遠(yuǎn)高于幾KB網(wǎng)絡(luò)請(qǐng)求時(shí)間仁堪;高性能的Web網(wǎng)站需要仔細(xì)斟酌前端渲染帶來的性能問題哮洽。

>**優(yōu)化**
* 高性能要求頁面還是需要后端渲染。
* React還是太重了弦聂,面向用戶寫系統(tǒng)需要謹(jǐn)慎考慮鸟辅。
* JS代碼的編譯和執(zhí)行會(huì)有緩存,同App中網(wǎng)頁盡量統(tǒng)一框架莺葫。

##WebView性能優(yōu)化總結(jié)
>一個(gè)加載網(wǎng)頁的過程中匪凉,native、網(wǎng)絡(luò)捺檬、后端處理再层、CPU都會(huì)參與,各自都有必要的工作和依賴關(guān)系堡纬;讓他們相互并行處理而不是相互阻塞才可以讓網(wǎng)頁加載更快:

>* WebView初始化慢聂受,可以在初始化同時(shí)先請(qǐng)求數(shù)據(jù),讓后端和網(wǎng)絡(luò)不要閑著烤镐。
* 后端處理慢蛋济,可以讓服務(wù)器分trunk輸出,在后端計(jì)算的同時(shí)前端也加載網(wǎng)絡(luò)靜態(tài)資源炮叶。
* 腳本執(zhí)行慢碗旅,就讓腳本在最后運(yùn)行渡处,不阻塞頁面解析。
* 同時(shí)祟辟,合理的預(yù)加載医瘫、預(yù)緩存可以讓加載速度的瓶頸更小。
* WebView初始化慢旧困,就隨時(shí)初始化好一個(gè)WebView待用登下。
* DNS和鏈接慢,想辦法復(fù)用客戶端使用的域名和鏈接叮喳。
* 腳本執(zhí)行慢被芳,可以把框架代碼拆分出來,在請(qǐng)求頁面之前就執(zhí)行好馍悟。

#WebView內(nèi)存消耗
>分析
>為了測(cè)試WebView會(huì)消耗多少內(nèi)存畔濒,我們?cè)O(shè)計(jì)了如下的測(cè)試方案:

>* 客戶端啟動(dòng)后,記錄消耗的內(nèi)存锣咒。
* 打開空頁面侵状,記錄內(nèi)存的上漲。
* 退出毅整。
* 打開空頁面趣兄,記錄內(nèi)存上漲。
* 退出悼嫉。
* 打開加載了代碼的頁面艇潭,記錄內(nèi)存的額外增加。

>得到如下測(cè)試結(jié)果:
>* 測(cè)試系統(tǒng): iOS模擬器戏蔑,Titans 10.0.7
* 測(cè)試系統(tǒng): OPPO R829T Android 4.2.2
* 測(cè)試方式:測(cè)試10次取平均值

>|  系統(tǒng) |首次打開增加內(nèi)存   | 二次打開增加內(nèi)存| 加載KNB+VUE+靈犀|
|---------|:------------- |:-------------:| -----:|
| iOS UIWebView    | 31.1M   |  5.52M |         2M|
| iOS WKWebView    | 1.95M   |          1.6M |  2M|
|Android    | 32.2M  |  6.62M |         1.7M|

>WKWebView的內(nèi)存消耗相比其他低了一個(gè)數(shù)量級(jí)蹋凝,在此方面相當(dāng)占優(yōu)。

>* UIWebView和Android的WebView在首次初始化時(shí)都要消耗大量?jī)?nèi)存总棵,之后每次新建WebView會(huì)額外增加一些鳍寂。
* UIWebView的內(nèi)存占用不會(huì)在關(guān)閉WebView時(shí)主動(dòng)回收,每次新開WebView都會(huì)消耗額外內(nèi)存情龄。

>相比于性能迄汛,對(duì)于內(nèi)存的優(yōu)化可以做的還是比較有限的。
* WKWebView的內(nèi)存占用優(yōu)勢(shì)比較大(代價(jià)是初始化比較慢)骤视。
* 頁面內(nèi)代碼消耗的內(nèi)存相比與WebView系統(tǒng)的內(nèi)存消耗相比可以說是很低鞍爱。

#WebView體驗(yàn)
>除了打開的速度,WebView通常體驗(yàn)也沒有native的實(shí)現(xiàn)更好尚胞,我們可以找到以下幾個(gè)例子:

>**長(zhǎng)按選擇**
在WebView中硬霍,長(zhǎng)按文字會(huì)使得WebView默認(rèn)開始選擇文字;長(zhǎng)按鏈接會(huì)彈出提示是否在新頁面打開笼裳。
解決方法:可以通過給body增加CSS來禁止這些默認(rèn)規(guī)則唯卖。

>**點(diǎn)擊延遲**
在WebView中粱玲,click通常會(huì)有大約300ms的延遲(同時(shí)包括鏈接的點(diǎn)擊,表單的提交拜轨,控件的交互等任何用戶點(diǎn)擊行為)抽减。
唯一的例外是設(shè)置的`meta:viewpoint`為禁止縮放的Chrome(然而并不是Android默認(rèn)的瀏覽器)。
解決方法:使用`fastclick`一般可以解決這個(gè)問題橄碾。

>**頁面滑動(dòng)期間不渲染/執(zhí)行**
在很多需求中會(huì)有一些吸頂?shù)脑芈殉粒鐚?dǎo)航條,購買按鈕等法牲;當(dāng)頁面滾動(dòng)超出元素高度后史汗,元素吸附在屏幕頂部。
這個(gè)功能在PC和native中都能夠?qū)崿F(xiàn)拒垃,然而在WebView中卻成了難題:
 在頁面滾動(dòng)期間停撞,`Scroll Event`不觸發(fā)
不僅如此,WebView在滾動(dòng)期間還有各種限定:
>* setTimeout和setInterval不觸發(fā)悼瓮。
* GIF動(dòng)畫不播放戈毒。
* 很多回調(diào)會(huì)延遲到頁面停止?jié)L動(dòng)之后。
* background-position: fixed不支持横堡。
* 這些限制讓W(xué)ebView在滾動(dòng)期間很難有較好的體驗(yàn)埋市。

> 這些限制大部分是不可突破的,但至少對(duì)于吸頂功能還是可以做一些支持:

>**解決方法:**

>* 在iOS上命贴,使用position: sticky可以做到元素吸頂道宅。
* 在Android上,監(jiān)聽touchmove事件可以在滑動(dòng)期間做元素的position切換(慣性運(yùn)動(dòng)期間就無效了)套么。

>**crash**
通常WebView并不能直接接觸到底層的API培己,因此比較穩(wěn)定;但仍然有使用不當(dāng)造成整個(gè)App崩潰的情況胚泌。

>目前發(fā)現(xiàn)的案例包括:
* 使用過大的圖片(2M)
* 不正常使用WebGL

#WebView安全  
>###WebView被運(yùn)營(yíng)商劫持、注入問題
>由于WebView加載的頁面代碼是從服務(wù)器動(dòng)態(tài)獲取的肃弟,這些代碼將會(huì)很容易被中間環(huán)節(jié)所竊取或者修改玷室,其中最主要的問題出自地方運(yùn)營(yíng)商(浙江尤其明顯)和一些WiFi。

>我們監(jiān)測(cè)到的問題包括:
* 無視通信規(guī)則強(qiáng)制緩存頁面笤受。
* header被篡改穷缤。
* 頁面被注入廣告。
* 頁面被重定向箩兽。
* 頁面被重定向并重新iframe到新頁面津肛,框架嵌入廣告。
* HTTPS請(qǐng)求被攔截汗贫。
* DNS劫持身坐。

>這些問題輕則影響用戶體驗(yàn)秸脱,重則泄露數(shù)據(jù),或影響公司信譽(yù)部蛇。

>針對(duì)頁面注入的行為摊唇,有一些解決方案:
>**使用CSP(Content Security Policy)**
CSP可以有效的攔截頁面中的非白名單資源,而且兼容性較好涯鲁。在美團(tuán)移動(dòng)版的使用中巷查,能夠阻止大部分的頁面內(nèi)容注入。

>但在使用中還是存在以下問題:
* 由于業(yè)務(wù)的需要抹腿,通常inline腳本還是在白名單中岛请,會(huì)導(dǎo)致完全依賴內(nèi)聯(lián)的頁面代碼注入可以通過檢測(cè)。
* 如果注入的內(nèi)容是純HTML+CSS的內(nèi)容警绩,則CSP無能為力髓需。
* 無法解決頁面被劫持的問題。
* 會(huì)帶來額外的一些維護(hù)成本房蝉。

>總體來說CSP是一個(gè)行之有效的防注入方案僚匆,但是如果對(duì)于安全要求更高的網(wǎng)站,這些還不夠搭幻。

>**HTTPS**
HTTPS可以防止頁面被劫持或者注入咧擂,然而其副作用也是明顯的,網(wǎng)絡(luò)傳輸?shù)男阅芎统晒β识紩?huì)下降檀蹋,而且HTTPS的頁面會(huì)要求頁面內(nèi)所有引用的資源也是HTTPS的松申,對(duì)于大型網(wǎng)站其遷移成本并不算低。

>HTTPS的一個(gè)問題在于:一旦底層想要篡改或者劫持俯逾,會(huì)導(dǎo)致整個(gè)鏈接失效贸桶,頁面無法展示。這會(huì)帶來一個(gè)問題:本來頁面只是會(huì)被注入廣告桌肴,而且廣告會(huì)被CSP攔截皇筛,而采用了HTTPS后,整個(gè)網(wǎng)頁由于受到劫持完全無法展示坠七。

>對(duì)于安全要求不高的靜態(tài)頁面水醋,就需要權(quán)衡HTTPS帶來的利與弊了。

##App使用Socket代理請(qǐng)求
>如果HTTP請(qǐng)求容易被攔截彪置,那么讓App將其轉(zhuǎn)換為一個(gè)Socket請(qǐng)求拄踪,并代理WebView的訪問也是一個(gè)辦法。

>通常不法運(yùn)營(yíng)商或者WiFi都只能攔截HTTP(S)請(qǐng)求拳魁,對(duì)于自定義的包內(nèi)容則無法攔截惶桐,因此可以基本解決注入和劫持的問題。

>Socket代理請(qǐng)求也存在問題。
* 首先姚糊,使用客戶端代理的頁面HTML請(qǐng)求將喪失邊下載邊解析的能力贿衍;根據(jù)前面所述,瀏覽器在HTML收到部分內(nèi)容后就立刻開始解析叛拷,并加載解析出來的外鏈舌厨、圖片等,執(zhí)行內(nèi)聯(lián)的腳本……而目前WebView對(duì)外并沒有暴露這種流式的HTML接口忿薇,只能由客戶端完全下載好HTML后裙椭,注入到WebView中。因此其性能將會(huì)受到影響署浩。

>* 其次揉燃,其技術(shù)問題也是較多的,例如對(duì)跳轉(zhuǎn)的處理筋栋,對(duì)緩存的處理炊汤,對(duì)CDN的處理等等……稍不留神就會(huì)埋下若干大坑。
此外還有一些其他的辦法弊攘,例如頁面的MD5檢測(cè)抢腐,頁面靜態(tài)頁打包下載等等方式,具體如何選擇還要根據(jù)具體的場(chǎng)景抉擇襟交。

#客戶端內(nèi)打開第三方WebView
>一般來說迈倍,客戶端內(nèi)的WebView都是可以通過客戶端的某個(gè)schema打開的,而要打開頁面的URL很多都并不寫在客戶端內(nèi)捣域,而是可以由URL中的參數(shù)傳遞過去的啼染。

>那么,一旦此URL可以通過外界輸入自定義焕梅,那么就有可能在客戶端內(nèi)部打開一個(gè)外部的網(wǎng)頁迹鹅。

>例:作案過程
* 某個(gè)App有個(gè)WebView,打開的schema為 appxx://web?url={weburl}贞言。
* App中有個(gè)掃碼的功能斜棚,可以掃描某個(gè)二維碼并打開對(duì)應(yīng)的schema鏈接。
* 某個(gè)壞人制作了一個(gè)二維碼并張貼到街上蜗字,內(nèi)容符合 : appxx://web?url={some_hack_weburl}打肝。
* 用戶掃碼打開了some_hack_weburl。
* 如果some_hack_weburl是一個(gè)高仿的登錄頁面挪捕,那么用戶將會(huì)很可能將用戶名密碼提交到其他網(wǎng)站。

>解決方法:在內(nèi)嵌的WebView中應(yīng)該限制允許打開的WebView的域名争便,并設(shè)置運(yùn)行訪問的白名單级零。或者當(dāng)用戶打開外部鏈接前給用戶強(qiáng)烈而明顯的提示。

#發(fā)展
>在一個(gè)客戶端內(nèi)奏纪,native目前主要功能是提供高效而基礎(chǔ)的功能鉴嗤;內(nèi)部的WebView則添加一些性能體驗(yàn)要求不高但動(dòng)態(tài)化要求高的能力。

>提高客戶端的動(dòng)態(tài)能力序调,或者提高WebView的性能醉锅,都是提升App功能覆蓋的方式。

>而目前的各種框架发绢,ReactNative硬耍、Week包括微信小程序,都是這個(gè)趨勢(shì)的嘗試边酒。

>隨著技術(shù)的發(fā)展经柴,WebView的性能、體驗(yàn)和安全問題也將會(huì)逐漸的改善墩朦,在App中占有越來越多比重的同時(shí)坯认,也將會(huì)為App開拓新的能力,為用戶帶來更優(yōu)質(zhì)的體驗(yàn)氓涣。
![ dest ](http://upload-images.jianshu.io/upload_images/1242701-b896d4034c18d126.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#=======> [傳送門](http://tech.meituan.com/WebViewPerf.html) <=======
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牛哺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子劳吠,更是在濱河造成了極大的恐慌引润,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赴背,死亡現(xiàn)場(chǎng)離奇詭異椰拒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凰荚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門燃观,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人便瑟,你說我怎么就攤上這事缆毁。” “怎么了到涂?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵脊框,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我践啄,道長(zhǎng)浇雹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任屿讽,我火速辦了婚禮昭灵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己烂完,他們只是感情好试疙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抠蚣,像睡著了一般祝旷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嘶窄,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天怀跛,我揣著相機(jī)與錄音,去河邊找鬼护侮。 笑死敌完,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的羊初。 我是一名探鬼主播滨溉,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼长赞!你這毒婦竟也來了晦攒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤得哆,失蹤者是張志新(化名)和其女友劉穎脯颜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贩据,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栋操,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饱亮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾芙。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖近上,靈堂內(nèi)的尸體忽然破棺而出剔宪,到底是詐尸還是另有隱情,我是刑警寧澤壹无,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布葱绒,位于F島的核電站,受9級(jí)特大地震影響斗锭,放射性物質(zhì)發(fā)生泄漏地淀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一岖是、第九天 我趴在偏房一處隱蔽的房頂上張望骚秦。 院中可真熱鬧她倘,春花似錦璧微、人聲如沸作箍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胞得。三九已至,卻和暖如春屹电,著一層夾襖步出監(jiān)牢的瞬間阶剑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工危号, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牧愁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓外莲,卻偏偏與公主長(zhǎng)得像猪半,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偷线,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容