在App開發(fā)中自脯,內(nèi)嵌WebView始終占有著一席之地。它能以較低的成本實(shí)現(xiàn)Android嗤疯、iOS和Web的復(fù)用冤今,也可以冠冕堂皇的突破蘋果對熱更新的封鎖。
然而便利性的同時茂缚,WebView的性能體驗(yàn)卻備受質(zhì)疑戏罢,導(dǎo)致很多客戶端中需要動態(tài)更新等頁面時不得不采用其他方案屋谭。
以發(fā)展的眼光來看,功能的動態(tài)加載以及三端的融合將會是大趨勢龟糕。那么如何克服WebView固有的問題呢桐磁?我們將從性能、內(nèi)存消耗讲岁、體驗(yàn)我擂、安全幾個維度,來系統(tǒng)的分析客戶端默認(rèn)WebView的問題缓艳,以及對應(yīng)的優(yōu)化方案校摩。
性能
對于WebView的性能,給人最直觀的莫過于:打開速度比native慢阶淘。
是的衙吩,當(dāng)我們打開一個WebView頁面,頁面往往會慢吞吞的loading很久溪窒,若干秒后才出現(xiàn)你所需要看到的頁面坤塞。
這是為什么呢?
對于一個普通用戶來講澈蚌,打開一個WebView通常會經(jīng)歷以下幾個階段:
- 交互無反饋
- 到達(dá)新的頁面摹芙,頁面白屏
- 頁面基本框架出現(xiàn),但是沒有數(shù)據(jù)宛瞄;頁面處于loading狀態(tài)
- 出現(xiàn)所需的數(shù)據(jù)
如果從程序上觀察浮禾,WebView啟動過程大概分為以下幾個階段:
如何縮短這些過程的時間,就成了優(yōu)化WebView性能的關(guān)鍵坛悉。
接下來我們逐一分析各個階段的耗時情況伐厌,以及需要注意的優(yōu)化點(diǎn)。
WebView初始化
當(dāng)App首次打開時裸影,默認(rèn)是并不初始化瀏覽器內(nèi)核的;只有當(dāng)創(chuàng)建WebView實(shí)例的時候军熏,才會創(chuàng)建WebView的基礎(chǔ)框架轩猩。
所以與瀏覽器不同,App中打開WebView的第一步并不是建立連接荡澎,而是啟動瀏覽器內(nèi)核均践。
我們來分析一下這段耗時到底需要多久。
分析
針對WebView的初始化時間摩幔,我們可以定義兩個指標(biāo):
- 首次初始化時間:客戶端冷啟動后彤委,第一次打開WebView,從開始創(chuàng)建WebView到開始建立網(wǎng)絡(luò)連接之間的時間或衡。
- 二次初始化時間:在打開過WebView后焦影,退出WebView车遂,再重新打開WebView,從開始創(chuàng)建WebView到開始建立網(wǎng)絡(luò)連接之間的時間斯辰。
測試數(shù)據(jù):
測試系統(tǒng)1: iOS模擬器舶担,Titans 10.0.7
測試系統(tǒng)2: OPPO R829T Android 4.2.2
測試方式:測試10次取平均值
測試App:美團(tuán)外賣
單位:ms
| | 首次初始化時間 | 二次初始化時間 |
| -- ------|:-------------:| -----:|
| iOS(UIWebView) |306.56 | 76.43 |
| iOS(WKWebView) | 763.26 | 457.25 |
| Android | 192.79 * | 142.53 |
*Android外賣客戶端啟動后會在后臺開啟WebView進(jìn)程,故并不是完全新建WebView時間彬呻。
這意味著什么呢衣陶?
作為前端工程師,統(tǒng)計了無數(shù)次的頁面打開時間闸氮,都是以網(wǎng)絡(luò)連接開始作為起點(diǎn)的剪况。
很遺憾的通知您:WebView中用戶體驗(yàn)到的打開時間需要再增加70~700ms。
于是我們找到了“為什么WebView總是很慢”的原因之一:
- 在瀏覽器中蒲跨,我們輸入地址時(甚至在之前)拯欧,瀏覽器就可以開始加載頁面。
- 而在客戶端中财骨,客戶端需要先花費(fèi)時間初始化WebView完成后镐作,才開始加載。
而這段時間隆箩,由于WebView還不存在该贾,所有后續(xù)的過程是完全阻塞的“齐可以這樣形容WebView初始化過程:
那么有哪些解決辦法呢杨蛋?
怎么優(yōu)化
由于這段過程發(fā)生在native的代碼中,單純靠前端代碼是無法優(yōu)化的理澎;大部分的方案都是前端和客戶端協(xié)作完成逞力,以下是幾個業(yè)界采用過的方案。
全局WebView
方法:
- 在客戶端剛啟動時糠爬,就初始化一個全局的WebView待用寇荧,并隱藏;
- 當(dāng)用戶訪問了WebView時执隧,直接使用這個WebView加載對應(yīng)網(wǎng)頁揩抡,并展示。
這種方法可以比較有效的減少WebView在App中的首次打開時間镀琉。當(dāng)用戶訪問頁面時峦嗤,不需要初始化WebView的時間。
當(dāng)然這也帶來了一些問題屋摔,包括:
- 額外的內(nèi)存消耗烁设。
- 頁面間跳轉(zhuǎn)需要清空上一個頁面的痕跡,更容易內(nèi)存泄露钓试。
【參考東軟專利 - 加載網(wǎng)頁的方法及裝置 CN106250434A】
客戶端代理數(shù)據(jù)請求
方法:
- 在客戶端初始化WebView的同時装黑,直接由native開始網(wǎng)絡(luò)請求數(shù)據(jù)副瀑;
- 當(dāng)頁面初始化完成后,向native獲取其代理請求的數(shù)據(jù)曹体。
此方法雖然不能減小WebView初始化時間俗扇,但數(shù)據(jù)請求和WebView初始化可以并行進(jìn)行,總體的頁面加載時間就縮短了箕别;縮短總體的頁面加載時間:
【參考騰訊分享:70%以上業(yè)務(wù)由H5開發(fā)铜幽,手機(jī)QQ Hybrid 的架構(gòu)如何優(yōu)化演進(jìn)?】
還有其他各種優(yōu)化的方式串稀,不再一一列舉除抛,總結(jié)起來都是圍繞兩點(diǎn):
- 在使用前預(yù)先初始化好WebView,從而減小耗時母截。
- 在初始化的同時到忽,通過Native來完成一些網(wǎng)絡(luò)請求等過程,使得WebView初始化不是完全的阻塞后續(xù)過程清寇。
建立連接/服務(wù)器處理
在頁面請求的數(shù)據(jù)返回之前喘漏,主要有以下過程耗費(fèi)時間。
- DNS
- connection
- 服務(wù)器處理
分析
以下為美團(tuán)中活動頁面的鏈接時間統(tǒng)計:
統(tǒng)計: 美團(tuán)的活動頁面
內(nèi)容值: n%分位值(ms)
| | DNS | connection | 獲取首字節(jié) |
|-- ---------| -- ------ |:-------------:| -----:|
|50% |1.3 | 71| 172 |
| 90% | 60 | 360 | 541 |
優(yōu)化
這些時間都是發(fā)生在網(wǎng)頁加載之前华烟,但這并不意味著無法優(yōu)化翩迈,有以下幾種方法。
DNS采用和客戶端API相同的域名
DNS會在系統(tǒng)級別進(jìn)行緩存盔夜,對于WebView的地址负饲,如果使用的域名與native的API相同,則可以直接使用緩存的DNS而不用再發(fā)起請求圖片喂链。
以美團(tuán)為例返十,美團(tuán)的客戶端請求域名主要位于api.meituan.com,然而內(nèi)嵌的WebView主要位于 i.meituan.com椭微。
當(dāng)我們初次打開App時:
- 客戶端首次打開都會請求api.meituan.com洞坑,其DNS將會被系統(tǒng)緩存。
- 然而當(dāng)打開WebView的時候赏表,由于請求了不同的域名检诗,需要重新獲取i.meituan.com的IP。
根據(jù)上面的統(tǒng)計瓢剿,至少10%的用戶打開WebView時耗費(fèi)了60ms在DNS上面,如果WebView的域名與App的API域名統(tǒng)一悠轩,則可以讓W(xué)ebView的DNS時間全部達(dá)到1.3ms的量級间狂。
靜態(tài)資源同理,最好與客戶端的資源域名保持一致火架。
同步渲染采用chunk編碼
同步渲染時如果后端請求時間過長鉴象,可以考慮采用chunk編碼忙菠,將數(shù)據(jù)放在最后,并優(yōu)先將靜態(tài)內(nèi)容flush纺弊。對于傳統(tǒng)的后端渲染頁面牛欢,往往都是使用的【瀏覽器】--> 【W(wǎng)eb API】 --> 【業(yè)務(wù) API】的加載模式,其中后端時間就指的是Web API的處理時間了淆游。在這里Web API一般有兩個作用:
- 確定靜態(tài)資源的版本傍睹。
- 根據(jù)用戶的請求,去業(yè)務(wù)API獲取數(shù)據(jù)犹菱。
而一般確定靜態(tài)資源的版本往往是直接讀取代碼版本拾稳,基本無耗時;而主要的后端時間都花費(fèi)在了業(yè)務(wù)API請求上面腊脱。
那么怎么優(yōu)化利用這段時間呢访得?
在HTTP協(xié)議中,我們可以在header中設(shè)置 transfer-encoding:chunked
使得頁面可以分塊輸出陕凹。如果合理設(shè)計頁面悍抑,讓head部分都是確定的靜態(tài)資源版本相關(guān)內(nèi)容,而body部分是業(yè)務(wù)數(shù)據(jù)相關(guān)內(nèi)容杜耙,那么我們可以在用戶請求的時候搜骡,首先將Web API可以確定的部分先輸出給瀏覽器,然后等API完全獲取后泥技,再將API數(shù)據(jù)傳輸給瀏覽器浆兰。
下圖可以直觀的看出分chunk輸出和一起輸出的區(qū)別:
- 如果采用普通方式輸出頁面,則頁面會在服務(wù)器請求完所有API并處理完成后開始傳輸珊豹。瀏覽器要在后端所有API都加載完成后才能開始解析簸呈。
- 如果采用chunk-encoding: chunked,并優(yōu)先將頁面的靜態(tài)部分輸出店茶;然后處理API請求蜕便,并最終返回頁面,可以讓后端的API請求和前端的資源加載同時進(jìn)行贩幻。
- 兩者的總共后端時間并沒有區(qū)別轿腺,但是可以提升首字節(jié)速度,從而讓前端加載資源和后端加載API不互相阻塞丛楚。
頁面框架渲染
頁面在解析到足夠多的節(jié)點(diǎn)族壳,且所有CSS都加載完成后進(jìn)行首屏渲染。在此之前趣些,頁面保持白屏仿荆;在頁面完全下載并解析完成之前,頁面處于不完整展示狀態(tài)。
分析
我們以一個美團(tuán)的活動頁面作為樣例:
測試頁面:http://i.meituan.com/firework/meituanxianshifengqiang
在Mac上面拢操,模擬4G情況
頁面樣式:
測試得到的時間耗費(fèi)如下:
表1
| 階段 | 時間 | 大小 | 備注 |
|-- ---------| -- ------ |:-------------:| -----:|
|DOM下載 |58ms | 29.5?KB| 4G網(wǎng)絡(luò) |
| DOM解析 | 12.5ms | 198?KB | 根據(jù)估算锦亦,在手機(jī)上慢2~5倍不等 |
| CSS請求+下載 | 58ms | 11.7?KB | 4G網(wǎng)絡(luò)(包含鏈接時間,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處理 |
同時舔庶,對HTML的加載時間進(jìn)行分析抛蚁,可以得到如下時間點(diǎn)。
表2
| 指標(biāo) | 時間 | 計算方法 |
| -- ------ |:-------------:| :-----:|
|HTML加載完成時間 |218 | performance.timing.responseEnd - performance.timing.fetchStart|
|HTML解析完成時間 |330 | performance.timing.domInteractive - performance.timing.fetchStart|
這意味著什么呢栖茉?
對于表1
可以看到篮绿,隨著在網(wǎng)絡(luò)優(yōu)良的情況下,Dom的解析所占耗時比例還是不算低的吕漂,對于低端機(jī)器更甚亲配。Layout時間也是首屏前耗時的大頭,據(jù)猜測這與頁面使用了rem作為單位有關(guān)(待進(jìn)一步分析)惶凝。
對于表2吼虎,我們可以發(fā)現(xiàn)一個問題
一般來說HTML在開始接收到返回數(shù)據(jù)的時候就開始解析HTML并構(gòu)建DOM樹。如果沒有JS(JavaScript)阻塞的話一般會相繼完成苍鲜。然而思灰,在這里時間相差了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)坯屿,都不會阻塞頁面的解析:
- CSS不會阻止頁面繼續(xù)向下繼續(xù)油湖。
- 內(nèi)聯(lián)的JS很快執(zhí)行完成,然后繼續(xù)解析文檔领跛。
然而乏德,當(dāng)這兩部分同時出現(xiàn)的時候,問題就來了吠昭。
- CSS加載阻塞了下面的一段內(nèi)聯(lián)JS的執(zhí)行喊括,而被阻塞的內(nèi)聯(lián)JS則阻塞了HTML的解析。
通常情況下矢棚,CSS不會阻塞HTML的解析郑什,但如果CSS后面有JS,則會阻塞JS的執(zhí)行直到CSS加載完成(即便JS是內(nèi)聯(lián)的腳本)蒲肋,從而間接阻塞HTML的解析蹦误。
優(yōu)化
在頁面框架加載這一部分劫拢,能夠優(yōu)化的點(diǎn)參照雅虎14條就夠了肉津;但注意不要犯錯强胰,一個小小的內(nèi)聯(lián)JS放錯位置也會讓性能下降很多。
CSS的加載會在HTML解析到CSS的標(biāo)簽時開始妹沙,所以CSS的標(biāo)簽要盡量靠前偶洋。
但是,CSS鏈接下面不能有任何的JS標(biāo)簽(包括很簡單的內(nèi)聯(lián)JS)距糖,否則會阻塞HTML的解析玄窝。
如果必須要在頭部增加內(nèi)聯(lián)腳本,一定要放在CSS標(biāo)簽之前悍引。
JS加載
對于大型的網(wǎng)站來說恩脂,在此我們先提出幾個問題:
- 將全部JS代碼打成一個包,造成首次執(zhí)行代碼過大怎么辦趣斤?
- 將JS以細(xì)粒度打包俩块,造成請求過多怎么辦?
- 將JS按 "基礎(chǔ)庫" + "頁面代碼" 分別打包浓领,要怎么界定什么是基礎(chǔ)代碼玉凯,什么是頁面代碼;不同頁面用的基礎(chǔ)代碼不一致怎么辦联贩?
- 單一文件的少量代碼改的是否會導(dǎo)致緩存失效漫仆?
- 代碼模塊間有動態(tài)依賴,怎樣合并請求泪幌。
關(guān)于這些問題的解決方案數(shù)量可能會比問題還多盲厌,而它們也各有優(yōu)劣。
具體分析太過復(fù)雜祸泪,鑒于篇幅原因在這里不做具體分析了吗浩。您可以期待我們的后續(xù)計劃:BPM(瀏覽器包管理)。
JS解析浴滴、編譯拓萌、執(zhí)行
在PC互聯(lián)網(wǎng)時代,人們似乎都快忘記了JS的解析和執(zhí)行還需要消耗時間升略。確實(shí)微王,在幾年前網(wǎng)速還在用kb衡量的時代里,JS的解析時間在整個頁面的打開時間里只能算是九牛一毛品嚣。
然而炕倘,隨著網(wǎng)速越來越快,而CPU的速度反而沒有提升(從PC到手機(jī))翰撑,JS的時間開銷就成為問題了罩旋。那么JS的編譯和解析啊央,在當(dāng)今的頁面上要消耗多少時間呢?
分析
我們用以下方式來檢驗(yàn)JS代碼的解析/編譯和執(zhí)行時間:
<script>
window.t1 = performance.now()
</script>
<script>
window.test = function () {
// test code
}
</script>
<script>
window.t2 = performance.now()
test();
window.t3 = performance.now();
alert("編譯耗時:" + (t2 - t1));
alert("執(zhí)行耗時:" + (t3 - t2));
</script>
將測試代碼放入 【test code】 位置涨醋,然后在手機(jī)中執(zhí)行瓜饥;
在t1~t2期間,JS代碼僅僅聲明了一個函數(shù)浴骂,主要時間會集中在解析和編譯過程乓土;
在t2~t3時間段內(nèi),執(zhí)行test時時間主要為代碼的執(zhí)行時間
在首次啟動客戶端后溯警,打開WebView的測試頁面趣苏,我們可以得到如下的結(jié)果:
測試系統(tǒng): iPhone6 iOS 10.2.1
測試系統(tǒng): OPPO R829T Android 4.2.2
內(nèi)容值: 編譯時間(ms)/執(zhí)行時間(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并重新訪問測試頁面梯轻,再次測試得到如下結(jié)果:
系統(tǒng)
| 系統(tǒng) | Zepto.js | Vue.js | React.js + ReactDOM.js |
|-- ---------| -- ------ |:-------------:| -----:|
| iOS | 0.9 / 1.9 | 5 / 7.4 | 3.5 / 23 |
| Android | 5 / 9 | 17 / 12 | 25 / 60 |
執(zhí)行時間指的是框架代碼加載的頁面的初始化時間食磕,沒有任何業(yè)務(wù)的調(diào)用。
這意味著什么
經(jīng)過測試可以得出以下結(jié)論:
- 偏重的框架喳挑,例如React彬伦,僅僅初始化的時間就會達(dá)到50ms ~ 350ms,這在對性能敏感的業(yè)務(wù)中時比較不利的蟀悦。
- 在App的啟動周期內(nèi)媚朦,統(tǒng)一域名下的代碼會被緩存編輯和初始化結(jié)果,重復(fù)調(diào)用性能較好日戈。
所以询张,在移動瀏覽器上,JS的解析和執(zhí)行時間并不是不可忽略的浙炼。
在低端安卓機(jī)上份氧,(框架的初始化+異步數(shù)據(jù)請求+業(yè)務(wù)代碼執(zhí)行)會遠(yuǎn)高于幾KB網(wǎng)絡(luò)請求時間;高性能的Web網(wǎng)站需要仔細(xì)斟酌前端渲染帶來的性能問題弯屈。
優(yōu)化
- 高性能要求頁面還是需要后端渲染蜗帜。
- React還是太重了,面向用戶寫系統(tǒng)需要謹(jǐn)慎考慮资厉。
- JS代碼的編譯和執(zhí)行會有緩存厅缺,同App中網(wǎng)頁盡量統(tǒng)一框架。
WebView性能優(yōu)化總結(jié)
一個加載網(wǎng)頁的過程中宴偿,native湘捎、網(wǎng)絡(luò)、后端處理窄刘、CPU都會參與窥妇,各自都有必要的工作和依賴關(guān)系;讓他們相互并行處理而不是相互阻塞才可以讓網(wǎng)頁加載更快:
- WebView初始化慢娩践,可以在初始化同時先請求數(shù)據(jù)活翩,讓后端和網(wǎng)絡(luò)不要閑著烹骨。
- 后端處理慢,可以讓服務(wù)器分trunk輸出材泄,在后端計算的同時前端也加載網(wǎng)絡(luò)靜態(tài)資源沮焕。
- 腳本執(zhí)行慢,就讓腳本在最后運(yùn)行脸爱,不阻塞頁面解析遇汞。
- 同時,合理的預(yù)加載簿废、預(yù)緩存可以讓加載速度的瓶頸更小。
- WebView初始化慢络它,就隨時初始化好一個WebView待用族檬。
- DNS和鏈接慢,想辦法復(fù)用客戶端使用的域名和鏈接化戳。
- 腳本執(zhí)行慢单料,可以把框架代碼拆分出來,在請求頁面之前就執(zhí)行好点楼。
WebView內(nèi)存消耗
分析
為了測試WebView會消耗多少內(nèi)存扫尖,我們設(shè)計了如下的測試方案:
- 客戶端啟動后,記錄消耗的內(nèi)存掠廓。
- 打開空頁面换怖,記錄內(nèi)存的上漲。
- 退出蟀瞧。
- 打開空頁面沉颂,記錄內(nèi)存上漲。
- 退出悦污。
- 打開加載了代碼的頁面铸屉,記錄內(nèi)存的額外增加。
得到如下測試結(jié)果:
測試系統(tǒng): iOS模擬器切端,Titans 10.0.7
測試系統(tǒng): OPPO R829T Android 4.2.2
測試方式:測試10次取平均值
| | 首次打開增加內(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)存消耗相比其他低了一個數(shù)量級彻坛,在此方面相當(dāng)占優(yōu)。
UIWebView和Android的WebView在首次初始化時都要消耗大量內(nèi)存踏枣,之后每次新建WebView會額外增加一些昌屉。
UIWebView的內(nèi)存占用不會在關(guān)閉WebView時主動回收,每次新開WebView都會消耗額外內(nèi)存椰于。
相比于性能怠益,對于內(nèi)存的優(yōu)化可以做的還是比較有限的。
- WKWebView的內(nèi)存占用優(yōu)勢比較大(代價是初始化比較慢)瘾婿。
- 頁面內(nèi)代碼消耗的內(nèi)存相比與WebView系統(tǒng)的內(nèi)存消耗相比可以說是很低蜻牢。
WebView體驗(yàn)
除了打開的速度烤咧,WebView通常體驗(yàn)也沒有native的實(shí)現(xiàn)更好,我們可以找到以下幾個例子:
長按選擇
在WebView中抢呆,長按文字會使得WebView默認(rèn)開始選擇文字煮嫌;長按鏈接會彈出提示是否在新頁面打開。
解決方法:可以通過給body增加CSS來禁止這些默認(rèn)規(guī)則抱虐。
點(diǎn)擊延遲
在WebView中昌阿,click通常會有大約300ms的延遲(同時包括鏈接的點(diǎn)擊,表單的提交恳邀,控件的交互等任何用戶點(diǎn)擊行為)懦冰。
唯一的例外是設(shè)置的meta:viewpoint為禁止縮放的Chrome(然而并不是Android默認(rèn)的瀏覽器)。
解決方法:使用fastclick一般可以解決這個問題谣沸。
頁面滑動期間不渲染/執(zhí)行
在很多需求中會有一些吸頂?shù)脑厮⒏郑鐚?dǎo)航條,購買按鈕等乳附;當(dāng)頁面滾動超出元素高度后内地,元素吸附在屏幕頂部。
這個功能在PC和native中都能夠?qū)崿F(xiàn)赋除,然而在WebView中卻成了難題:
在頁面滾動期間阱缓,Scroll Event不觸發(fā)
不僅如此,WebView在滾動期間還有各種限定:
- setTimeout和setInterval不觸發(fā)举农。
- GIF動畫不播放荆针。
- 很多回調(diào)會延遲到頁面停止?jié)L動之后。
- background-position: fixed不支持并蝗。
- 這些限制讓W(xué)ebView在滾動期間很難有較好的體驗(yàn)祭犯。
這些限制大部分是不可突破的,但至少對于吸頂功能還是可以做一些支持:
解決方法:
- 在iOS上滚停,使用position: sticky可以做到元素吸頂沃粗。
- 在Android上,監(jiān)聽touchmove事件可以在滑動期間做元素的position切換(慣性運(yùn)動期間就無效了)键畴。
crash
通常WebView并不能直接接觸到底層的API最盅,因此比較穩(wěn)定;但仍然有使用不當(dāng)造成整個App崩潰的情況起惕。
目前發(fā)現(xiàn)的案例包括:
- 使用過大的圖片(2M)
- 不正常使用WebGL
WebView安全
WebView被運(yùn)營商劫持涡贱、注入問題
由于WebView加載的頁面代碼是從服務(wù)器動態(tài)獲取的,這些代碼將會很容易被中間環(huán)節(jié)所竊取或者修改惹想,其中最主要的問題出自地方運(yùn)營商(浙江尤其明顯)和一些WiFi问词。
我們監(jiān)測到的問題包括:
- 無視通信規(guī)則強(qiáng)制緩存頁面。
- header被篡改嘀粱。
- 頁面被注入廣告激挪。
- 頁面被重定向辰狡。
- 頁面被重定向并重新iframe到新頁面,框架嵌入廣告垄分。
- HTTPS請求被攔截宛篇。
- DNS劫持。
這些問題輕則影響用戶體驗(yàn)薄湿,重則泄露數(shù)據(jù)叫倍,或影響公司信譽(yù)。
針對頁面注入的行為豺瘤,有一些解決方案:
使用CSP(Content Security Policy)
CSP可以有效的攔截頁面中的非白名單資源吆倦,而且兼容性較好。在美團(tuán)移動版的使用中炉奴,能夠阻止大部分的頁面內(nèi)容注入逼庞。
但在使用中還是存在以下問題:
- 由于業(yè)務(wù)的需要,通常inline腳本還是在白名單中瞻赶,會導(dǎo)致完全依賴內(nèi)聯(lián)的頁面代碼注入可以通過檢測。
- 如果注入的內(nèi)容是純HTML+CSS的內(nèi)容派任,則CSP無能為力砸逊。
- 無法解決頁面被劫持的問題。
- 會帶來額外的一些維護(hù)成本掌逛。
總體來說CSP是一個行之有效的防注入方案师逸,但是如果對于安全要求更高的網(wǎng)站,這些還不夠豆混。
HTTPS
HTTPS可以防止頁面被劫持或者注入篓像,然而其副作用也是明顯的,網(wǎng)絡(luò)傳輸?shù)男阅芎统晒β识紩陆得笏牛襀TTPS的頁面會要求頁面內(nèi)所有引用的資源也是HTTPS的员辩,對于大型網(wǎng)站其遷移成本并不算低。
HTTPS的一個問題在于:一旦底層想要篡改或者劫持鸵鸥,會導(dǎo)致整個鏈接失效奠滑,頁面無法展示。這會帶來一個問題:本來頁面只是會被注入廣告妒穴,而且廣告會被CSP攔截宋税,而采用了HTTPS后,整個網(wǎng)頁由于受到劫持完全無法展示讼油。
對于安全要求不高的靜態(tài)頁面杰赛,就需要權(quán)衡HTTPS帶來的利與弊了。
App使用Socket代理請求
如果HTTP請求容易被攔截矮台,那么讓App將其轉(zhuǎn)換為一個Socket請求乏屯,并代理WebView的訪問也是一個辦法根时。
通常不法運(yùn)營商或者WiFi都只能攔截HTTP(S)請求,對于自定義的包內(nèi)容則無法攔截瓶珊,因此可以基本解決注入和劫持的問題啸箫。
Socket代理請求也存在問題。
首先伞芹,使用客戶端代理的頁面HTML請求將喪失邊下載邊解析的能力忘苛;根據(jù)前面所述,瀏覽器在HTML收到部分內(nèi)容后就立刻開始解析唱较,并加載解析出來的外鏈扎唾、圖片等,執(zhí)行內(nèi)聯(lián)的腳本……而目前WebView對外并沒有暴露這種流式的HTML接口南缓,只能由客戶端完全下載好HTML后胸遇,注入到WebView中。因此其性能將會受到影響汉形。
其次纸镊,其技術(shù)問題也是較多的,例如對跳轉(zhuǎn)的處理概疆,對緩存的處理逗威,對CDN的處理等等……稍不留神就會埋下若干大坑。
此外還有一些其他的辦法岔冀,例如頁面的MD5檢測凯旭,頁面靜態(tài)頁打包下載等等方式,具體如何選擇還要根據(jù)具體的場景抉擇使套。
客戶端內(nèi)打開第三方WebView
一般來說罐呼,客戶端內(nèi)的WebView都是可以通過客戶端的某個schema打開的,而要打開頁面的URL很多都并不寫在客戶端內(nèi)侦高,而是可以由URL中的參數(shù)傳遞過去的嫉柴。
那么,一旦此URL可以通過外界輸入自定義矫膨,那么就有可能在客戶端內(nèi)部打開一個外部的網(wǎng)頁差凹。
例:作案過程
- 某個App有個WebView,打開的schema為 appxx://web?url={weburl}侧馅。
- App中有個掃碼的功能危尿,可以掃描某個二維碼并打開對應(yīng)的schema鏈接。
- 某個壞人制作了一個二維碼并張貼到街上馁痴,內(nèi)容符合 : appxx://web?url={some_hack_weburl}谊娇。
- 用戶掃碼打開了some_hack_weburl。
- 如果some_hack_weburl是一個高仿的登錄頁面,那么用戶將會很可能將用戶名密碼提交到其他網(wǎng)站济欢。
解決方法:在內(nèi)嵌的WebView中應(yīng)該限制允許打開的WebView的域名赠堵,并設(shè)置運(yùn)行訪問的白名單》ㄈ欤或者當(dāng)用戶打開外部鏈接前給用戶強(qiáng)烈而明顯的提示茫叭。
發(fā)展
在一個客戶端內(nèi),native目前主要功能是提供高效而基礎(chǔ)的功能半等;內(nèi)部的WebView則添加一些性能體驗(yàn)要求不高但動態(tài)化要求高的能力揍愁。
提高客戶端的動態(tài)能力,或者提高WebView的性能,都是提升App功能覆蓋的方式。
而目前的各種框架介衔,ReactNative、Week包括微信小程序撞羽,都是這個趨勢的嘗試。
隨著技術(shù)的發(fā)展,WebView的性能、體驗(yàn)和安全問題也將會逐漸的改善话肖,在App中占有越來越多比重的同時,也將會為App開拓新的能力葡幸,為用戶帶來更優(yōu)質(zhì)的體驗(yàn)狼牺。