摘要:經(jīng)過(guò)一系列優(yōu)化后,在平臺(tái)上,點(diǎn)擊到頁(yè)面首屏展示的耗時(shí)從平均多降低為,優(yōu)化以上。而現(xiàn)在頁(yè)面為了更好地為用戶推薦喜歡的內(nèi)容,我們后臺(tái)引入機(jī)器學(xué)習(xí)和隨機(jī)算法來(lái)做智能個(gè)性化推薦。另外還有部分的內(nèi)容是隨機(jī)算法推薦的。
VasSonic成長(zhǎng)歷程
2017.8.8 14時(shí),SNG增值產(chǎn)品部Vas團(tuán)隊(duì)研發(fā)的輕量級(jí)高性能Hybrid框架VasSonic通過(guò)了公司最終審核,作為騰訊開(kāi)源組件分享給大家。從當(dāng)初立項(xiàng)優(yōu)化頁(yè)面加載速度,到不斷摸索、優(yōu)化,再到整理代碼、文檔,最終在Github上開(kāi)源,并且在24小時(shí)內(nèi)獲取star數(shù)超過(guò)1600。我們非常高興看到我們的成果收到這么多的關(guān)注,趁此機(jī)會(huì),正好回顧一下VasSonic的成長(zhǎng)歷程,也希望能夠讓大家更了解VasSonic。
項(xiàng)目背景Web相信大家再熟悉不過(guò)了,它具有快速迭代發(fā)布的天然優(yōu)勢(shì),但也存在中一些讓人詬病的問(wèn)題,比如加載速度慢,體驗(yàn)差等。在此之前,手Q上很多頁(yè)面首屏打開(kāi)速度居高不下,甚至有些耗時(shí)達(dá)到3s以上,這意味著用戶打開(kāi)頁(yè)面必須經(jīng)過(guò)3秒之后才能進(jìn)行交互操作,體驗(yàn)相當(dāng)差,很多用戶忍受不了這個(gè)漫長(zhǎng)的時(shí)間直接流失掉了。
為了提升用戶體驗(yàn)和業(yè)務(wù)用戶留存率,我們很多業(yè)務(wù)一開(kāi)始通過(guò)Web開(kāi)發(fā),等頁(yè)面模型驗(yàn)證符合預(yù)期后,再將H5頁(yè)面轉(zhuǎn)化成原生界面。我們很快意識(shí)到這不是一種健康的可持續(xù)的開(kāi)發(fā)模式,一方面存在重復(fù)人力浪費(fèi),另外一方面原生商城除了速度快一點(diǎn),要運(yùn)營(yíng)活動(dòng)改版都很難。
所以后來(lái)團(tuán)隊(duì)改了切入方向,安排人力專(zhuān)心研究如何加快頁(yè)面打開(kāi)速度,經(jīng)過(guò)了一系列的摸爬滾打和優(yōu)化探索,最終我們研發(fā)出了VasSonic框架,讓H5頁(yè)面首屏達(dá)到秒開(kāi),給用戶一個(gè)更好的H5體驗(yàn)。下面就和大家分享VasSonic框架的發(fā)展歷程。
業(yè)務(wù)形態(tài)任何一個(gè)技術(shù)框架都是結(jié)合具體的業(yè)務(wù)形態(tài)來(lái)進(jìn)行發(fā)展優(yōu)化的,技術(shù)是為了更好地服務(wù)業(yè)務(wù),業(yè)務(wù)也會(huì)驅(qū)動(dòng)技術(shù)的發(fā)展。在此首先介紹一下業(yè)務(wù)形態(tài),我們是來(lái)自手Q增值產(chǎn)品部門(mén)的VAS團(tuán)隊(duì),負(fù)責(zé)手機(jī)QQ上很多深受年輕人喜歡的個(gè)性化增值服務(wù),比如氣泡、掛件、主題等等。手Q上大部分的業(yè)務(wù)還是基于H5開(kāi)發(fā)的,大家對(duì)手Q的業(yè)務(wù)形態(tài)可能有簡(jiǎn)單的了解。比如下圖的游戲分發(fā)中心、會(huì)員特權(quán)中心、個(gè)性化裝扮商城等。這部分商城的特點(diǎn)比較明顯,頁(yè)面的很多數(shù)據(jù)都是動(dòng)態(tài)的,是由我們的產(chǎn)品經(jīng)理在后臺(tái)配置的。
這些都是很常見(jiàn)頁(yè)面,我們通常將html/js/css等靜態(tài)資源放到CDN上,然后頁(yè)面加載后,再通過(guò)CGI去拉取最新的數(shù)據(jù),進(jìn)行拼接展示, 這樣子可以利用到CDN的多地部署和就近接入等優(yōu)勢(shì),同時(shí)提高了服務(wù)器的并發(fā)能力。這種傳統(tǒng)模式的加載流程如下所示:
用戶點(diǎn)擊后,經(jīng)過(guò)終端一系列初始化流程,比如進(jìn)程啟動(dòng)、Runtime初始化、創(chuàng)建WebView等等。
完成初始化后,WebView開(kāi)始去CDN上面請(qǐng)求Html加載頁(yè)面。
頁(yè)面發(fā)起CGI請(qǐng)求對(duì)應(yīng)的數(shù)據(jù)或者通過(guò)localStorage獲取數(shù)據(jù),數(shù)據(jù)回來(lái)后再對(duì)DOM進(jìn)行操作更新
可以看出上述流程存在著幾個(gè)問(wèn)題:
從外網(wǎng)統(tǒng)計(jì)數(shù)據(jù)來(lái)看,用戶的終端耗時(shí)在1s以上,這意味著在這1s多的時(shí)間里,網(wǎng)絡(luò)完全是空閑在等待的,非常浪費(fèi);
頁(yè)面的資源和數(shù)據(jù)完全依賴(lài)于網(wǎng)絡(luò),特別是用戶在弱網(wǎng)絡(luò)場(chǎng)景下,頁(yè)面會(huì)出現(xiàn)很長(zhǎng)時(shí)間的白屏,體驗(yàn)非常差;
因?yàn)轫?yè)面的數(shù)據(jù)依賴(lài)于動(dòng)態(tài)拉取,加載完頁(yè)面后,往往是看到一些模塊先轉(zhuǎn)菊花,再展示,體驗(yàn)也是不好的。同時(shí)這里涉及到較多數(shù)據(jù)更新,經(jīng)常要更新DOM,性能上也有不少開(kāi)銷(xiāo)。
所以針對(duì)以上幾個(gè)問(wèn)題,我們也對(duì)應(yīng)做了很多優(yōu)化和探索。
VasSonic的前世 優(yōu)化終端針對(duì)終端耗時(shí)1s以上的情況,我們對(duì)手Q WebView框架進(jìn)行了重構(gòu):
啟動(dòng)流程徹底拆分,設(shè)計(jì)為一個(gè)狀態(tài)機(jī)按序按需執(zhí)行
View相關(guān)拆分模塊化設(shè)計(jì),盡可能懶加載,IO異步化
X5內(nèi)核在手Q中的獨(dú)立進(jìn)程中提前預(yù)加載
創(chuàng)建WebView對(duì)象復(fù)用池
關(guān)于第四點(diǎn),我們想分享一些Android平臺(tái)上的細(xì)節(jié),由于Android系統(tǒng)的生態(tài)原因,導(dǎo)致用戶的系統(tǒng)版本和系統(tǒng)Webkit內(nèi)核處于極其分裂狀態(tài),所以我們公司在手Q和微信統(tǒng)一使用X5內(nèi)核。相對(duì)系統(tǒng)WebView來(lái)說(shuō),首次啟動(dòng)X5內(nèi)核時(shí),創(chuàng)建WebView比較耗時(shí),因此我們盡量想復(fù)用WebView,但是WebView卻是與Activity Context綁定。銷(xiāo)毀復(fù)用的時(shí)候,需要釋放Activity的Context,否則會(huì)內(nèi)存泄露。針對(duì)這種情況,有沒(méi)有一種兩全其美的辦法呢?
計(jì)算機(jī)有一句經(jīng)典的名言:計(jì)算機(jī)領(lǐng)域任何一個(gè)問(wèn)題都可以通過(guò)引入中間層來(lái)解決。于是我們通過(guò)包裝的方式,實(shí)現(xiàn)了一個(gè)Context的殼,真正的實(shí)現(xiàn)體包裝在里面,邏輯調(diào)用真正調(diào)用到對(duì)應(yīng)的實(shí)現(xiàn)體的函數(shù)。 經(jīng)過(guò)實(shí)驗(yàn)發(fā)現(xiàn),Android系統(tǒng)本身提供了這么一個(gè)MutableContextWrapper,作為Context的一個(gè)中間層。
我們會(huì)將Activity context包在MutableContextWrapper里面,destory的時(shí)候,會(huì)將WebView的Context設(shè)置為Application的Context,從而釋放Activity Context。
類(lèi)似如下:
//precreate WebView MutableContextWrapper contextWrapper = new MutableContextWrapper(BaseApplicationImpl.sApplication); mPool[0] = new WebView(contextWrapper); //reset WebView ct =(MutableContextWrapper)webview.getContext(); ct.setBaseContext(getApplication()); //reuse WebView ((MutableContextWrapper)webview.getContext()).setBaseContext(activityContext);靜態(tài)直出
“直出”這個(gè)概念對(duì)前端同學(xué)來(lái)說(shuō),并不陌生。為了優(yōu)化首屏體驗(yàn),大部分主流的頁(yè)面都會(huì)在服務(wù)器端拉取首屏數(shù)據(jù)后通過(guò)NodeJs進(jìn)行渲染,然后生成一個(gè)包含了首屏數(shù)據(jù)的Html文件,這樣子展示首屏的時(shí)候,就可以解決內(nèi)容轉(zhuǎn)菊花的問(wèn)題了。
當(dāng)然這種頁(yè)面“直出”的方式也會(huì)帶來(lái)一個(gè)問(wèn)題,服務(wù)器需要拉取首屏數(shù)據(jù),意味著服務(wù)端處理耗時(shí)增加。
不過(guò)因?yàn)楝F(xiàn)在Html都會(huì)發(fā)布到CDN上,WebView直接從CDN上面獲取,這塊耗時(shí)沒(méi)有對(duì)用戶造成影響。
手Q里面有一套自動(dòng)化的構(gòu)建系統(tǒng)Vnues,當(dāng)產(chǎn)品經(jīng)理修改數(shù)據(jù)發(fā)布后,可以一鍵啟動(dòng)構(gòu)建任務(wù),Vnues系統(tǒng)就會(huì)自動(dòng)同步最新的代碼和數(shù)據(jù),然后生成新的含首屏Html,并發(fā)布到CDN上面去。
頁(yè)面發(fā)布到CDN上面去后,那么WebView需要發(fā)起網(wǎng)絡(luò)請(qǐng)求去拉取。當(dāng)用戶在弱網(wǎng)絡(luò)或者網(wǎng)速比較差的環(huán)境下,這個(gè)加載時(shí)間會(huì)很長(zhǎng)。于是我們通過(guò)離線預(yù)推的方式,把頁(yè)面的資源提前拉取到本地,當(dāng)用戶加載資源的時(shí)候,相當(dāng)于從本地加載,即使沒(méi)有網(wǎng)絡(luò),也能展示首屏頁(yè)面。這個(gè)也就是大家熟悉的離線包。
手Q使用7Z生成離線包, 同時(shí)離線包服務(wù)器將新的離線包跟業(yè)務(wù)對(duì)應(yīng)的歷史離線包進(jìn)行BsDiff做二進(jìn)制差分,生成增量包,進(jìn)一步降低下載離線包時(shí)的帶寬成本,下載所消耗的流量從一個(gè)完整的離線包(253KB)降低為一個(gè)增量包(3KB)。
經(jīng)過(guò)一系列優(yōu)化后,在Android平臺(tái)上,點(diǎn)擊到頁(yè)面首屏展示的耗時(shí)從平均3s多降低為1.8s,優(yōu)化40% 以上。
VasSonic的誕生雖然通過(guò)靜態(tài)直出和離線預(yù)推等方式優(yōu)化后,速度已經(jīng)達(dá)到1.8s,但還存在很大的優(yōu)化空間,當(dāng)我們準(zhǔn)備持續(xù)深入優(yōu)化時(shí),我們的業(yè)務(wù)形態(tài)發(fā)生了新的變化。
之前我們頁(yè)面內(nèi)容的數(shù)據(jù)主要是由產(chǎn)品經(jīng)理要配置的,用戶看到的內(nèi)容基本都是一樣的。而現(xiàn)在頁(yè)面為了更好地為用戶推薦喜歡的內(nèi)容,我們后臺(tái)引入機(jī)器學(xué)習(xí)和隨機(jī)算法來(lái)做智能個(gè)性化推薦。比如左邊新用戶推薦的是新貨精選,而右邊活躍用戶展示的是潮品推薦。另外還有部分的內(nèi)容是隨機(jī)算法推薦的。這意味著不同用戶看到的內(nèi)容是不同的,同一個(gè)用戶不同時(shí)間看到的內(nèi)容也有可能不同。
所以為了滿足業(yè)務(wù)的需求,我們只能實(shí)時(shí)拉取用戶數(shù)據(jù)并在服務(wù)端渲染后返回給客戶端,也就是動(dòng)態(tài)直出的方案。
但是動(dòng)態(tài)直出方案存在幾個(gè)比較明顯的問(wèn)題:
服務(wù)端實(shí)時(shí)拉取數(shù)據(jù)渲染導(dǎo)致白屏?xí)r間長(zhǎng),因?yàn)榉?wù)器要先實(shí)時(shí)拉取個(gè)人數(shù)據(jù),然后進(jìn)行渲染直出,這個(gè)耗時(shí)不可控;
首屏無(wú)法使用離線預(yù)推等緩存策略,因?yàn)槊總€(gè)用戶看到的內(nèi)容不一樣,我們無(wú)法通過(guò)靜態(tài)直出的方式那樣把Html全部發(fā)布到CDN;
雖然動(dòng)態(tài)直出方案下,頁(yè)面首屏無(wú)法通過(guò)離線預(yù)推等方式進(jìn)行加載優(yōu)化,但前面優(yōu)化積累的經(jīng)驗(yàn)給我們提供了思路:要優(yōu)化白屏問(wèn)題,核心還是得從提升資源加載速度方向入手。所以我們重點(diǎn)在資源加載方面進(jìn)行了深度優(yōu)化。
并行加載首先在加載流程方面,我們發(fā)現(xiàn)這里WebView訪問(wèn)依然是串行的, WebView要等終端初始化完成之后,才發(fā)起請(qǐng)求。雖然終端耗時(shí)優(yōu)化了不少,但是從外網(wǎng)的統(tǒng)計(jì)數(shù)據(jù)來(lái)看,終端初始化還是存在幾百毫秒的耗時(shí),而這段時(shí)間內(nèi)網(wǎng)絡(luò)是在空等的。
因此性能上不夠極致,我們優(yōu)化代碼,這兩個(gè)操作并行處理,流程改為:
并行處理后速度有所改善,但我們發(fā)現(xiàn)在某些場(chǎng)景下,終端初始化比較快,但數(shù)據(jù)沒(méi)有完成返回,這意味著內(nèi)核在空等,而內(nèi)核是支持邊加載邊渲染的,我們?cè)诓⑿械耐瑫r(shí),能否也利用內(nèi)核的這個(gè)特性呢?
于是我們加入了一個(gè)中間層來(lái)橋接內(nèi)核和數(shù)據(jù),內(nèi)部稱(chēng)為流式攔截:
啟動(dòng)子線程請(qǐng)求頁(yè)面主資源,子線程中不斷講網(wǎng)絡(luò)數(shù)據(jù)讀取到內(nèi)存中,也就是網(wǎng)絡(luò)流(NetStream)和內(nèi)存流(MemStream)之間的轉(zhuǎn)換;
當(dāng)WebView初始化完成的時(shí)候,提供一個(gè)中間層BridgeStream來(lái)連接WebView和數(shù)據(jù)流;
當(dāng)WebView讀取數(shù)據(jù)的時(shí)候,中間層BridgeStream會(huì)先把內(nèi)存的數(shù)據(jù)讀取返回后,再繼續(xù)讀取網(wǎng)絡(luò)的數(shù)據(jù)。
通過(guò)這種橋接流的方式,整個(gè)內(nèi)核無(wú)需等待,繼續(xù)做到邊加載邊解析。這種并行的方式讓首屏的速度優(yōu)化15%以上,進(jìn)一步提升了頁(yè)面加載速度。
動(dòng)態(tài)緩存通過(guò)并行加載,我們極大地提升了WebView請(qǐng)求的速度,但是在弱網(wǎng)絡(luò)場(chǎng)景下白屏?xí)r間還是非常長(zhǎng),用戶體驗(yàn)非常糟糕。于是我們?cè)谒伎迹欠衲軌驅(qū)⒂脩舻囊呀?jīng)加載的頁(yè)面內(nèi)容緩存下來(lái),等用戶下此點(diǎn)擊頁(yè)面的時(shí)候,我們先加載展示頁(yè)面緩存,第一時(shí)間讓用戶看到內(nèi)容,然后同時(shí)去請(qǐng)求新的頁(yè)面數(shù)據(jù),等新的頁(yè)面數(shù)據(jù)拉取下來(lái)之后,我們?cè)僦匦录虞d一遍即可。
保存頁(yè)面內(nèi)容這個(gè)工作很簡(jiǎn)單,因?yàn)楝F(xiàn)在我們資源讀取都是通過(guò)中間層BridgeStream來(lái)管理的,只需要將整個(gè)讀取的內(nèi)容緩存下來(lái)即可。
于是我們就按動(dòng)態(tài)緩存這種方案去實(shí)現(xiàn)了,但很快就發(fā)現(xiàn)了問(wèn)題。用戶打開(kāi)頁(yè)面之后,先是看到歷史頁(yè)面,等用戶準(zhǔn)備去操作的時(shí)候,突然頁(yè)面白閃一下,重新加載了一遍,這種體驗(yàn)非常差,特別在一些低端機(jī)器上,這個(gè)白閃的過(guò)程太明顯,非常影響體驗(yàn),這是用戶和產(chǎn)品經(jīng)理都不能接受的。于是我們?cè)谒伎迹芊裰蛔鼍植康乃⑿拢瑑H刷新變化的元素呢?
通過(guò)分析,我們發(fā)現(xiàn)同一個(gè)用戶的頁(yè)面,大部分?jǐn)?shù)據(jù)都是不變的,經(jīng)常變化的只有少量數(shù)據(jù),于是我們提出了模板(template)和數(shù)據(jù)塊(data)的概念:頁(yè)面中經(jīng)常變化的數(shù)據(jù)我們稱(chēng)為數(shù)據(jù)塊,除了數(shù)據(jù)塊之外的數(shù)據(jù)稱(chēng)為模板。
頁(yè)面分離我們將整個(gè)頁(yè)面html通過(guò)VasSonic標(biāo)簽進(jìn)行劃分,包裹在標(biāo)簽中的內(nèi)容為data,標(biāo)簽外的內(nèi)容為模版。
首先我們對(duì)Html內(nèi)容進(jìn)行了擴(kuò)展,通過(guò)代碼注釋的方式,增加了“sonicdiff-xxx”來(lái)標(biāo)注一個(gè)數(shù)據(jù)塊的開(kāi)始與結(jié)束。
而模板就是將數(shù)據(jù)塊摳掉之后的Html,然后通過(guò){albums}來(lái)表示這個(gè)是一個(gè)數(shù)據(jù)塊占位。
數(shù)據(jù)就是JSON格式,直接Key-Value。
當(dāng)然,為了完美地兼容Html,我們對(duì)協(xié)議頭部進(jìn)行了擴(kuò)展,比如增加accept-diff來(lái)標(biāo)注是否支持增量更新、template-tag來(lái)標(biāo)注模板的md5是多少等。OK,有了上面這個(gè)規(guī)則或者公式后,我們就可以實(shí)現(xiàn)增量更新了。
VasSonic為了支持區(qū)分客戶端是否支持增量更新等能力,對(duì)頭部字段進(jìn)行了擴(kuò)展
字段 | 說(shuō)明 | 請(qǐng)求頭(Y/N) | 響應(yīng)頭(Y/N) |
---|---|---|---|
accept-diff | 表示終端是否支持VasSonic模式,true為支持,否則不支持 | Y | N |
If-none-match | 本地緩存的etag,給服務(wù)端判斷是否命中304 | Y | N |
etag | 頁(yè)面內(nèi)容的唯一標(biāo)識(shí)(哈希值) | N | Y |
template-tag | 模版唯一標(biāo)識(shí)(哈希值),客戶端使用本地校驗(yàn) 或 服務(wù)端使用判斷是模板有變更 | Y | Y |
template-change | 標(biāo)記模版是否變更,客戶端使用 | N | Y |
cache-offline | 客戶端端使用,根據(jù)不同類(lèi)型進(jìn)行不同行為 | N | Y |
字段 | 說(shuō)明 |
---|---|
true | 緩存到磁盤(pán)并展示返回內(nèi)容 |
false | 展示返回內(nèi)容,無(wú)需緩存到磁盤(pán) |
store | 緩存到磁盤(pán),如果已經(jīng)加載緩存,則下次加載,否則展示返回內(nèi)容 |
http | 容災(zāi)字段,如果http表示終端六個(gè)小時(shí)之內(nèi)不會(huì)采用sonic請(qǐng)求該URL |
VasSonic根據(jù)本地是否有緩存以及本地緩存數(shù)據(jù)跟服務(wù)器數(shù)據(jù)的差異情況分為以下四種模式。
模式 | 說(shuō)明 | 條件 |
---|---|---|
首次加載 | 本地沒(méi)有緩存,即第一次加載頁(yè)面 | etag為空值或template_tag為空值 |
完全緩存 | 本地有緩存,且緩存內(nèi)容跟服務(wù)器內(nèi)容完全一樣 | etag一致 |
數(shù)據(jù)更新 | 本地有緩存,本地模版內(nèi)容跟服務(wù)器模版內(nèi)容一樣,但數(shù)據(jù)塊有變化 | etag不一致 且 template_tag一致 |
模版更新 | 本地有緩存,緩存的模版內(nèi)容跟服務(wù)器的模版內(nèi)容不一樣 | etag不一致 且 template_tag不一致 |
我們會(huì)在請(qǐng)求頭部帶上支持accept-diff為true和sdk版本號(hào)等標(biāo)識(shí)著首次加載的信息。當(dāng)請(qǐng)求返回后,VasSonic會(huì)在延遲幾秒后(避免激烈IO競(jìng)爭(zhēng))將頁(yè)面抽離成模板和數(shù)據(jù)并保存到本地。此時(shí)終端緩存目錄下,該頁(yè)面將對(duì)應(yīng)三個(gè)緩存文件xxx.html、xxx.template、xxx.data,其中xxx是該頁(yè)面的唯一標(biāo)識(shí)(即sonicSessionId)。
對(duì)于頁(yè)面非首次加載場(chǎng)景,VasSonic優(yōu)先加載本地緩存, 同時(shí)我們會(huì)在請(qǐng)求頭部帶上當(dāng)前緩存和模板的md5,后臺(tái)進(jìn)行模板md5對(duì)比之后,分為以下幾種情況:
非首次加載之完全緩存本地有緩存,且緩存內(nèi)容跟服務(wù)器內(nèi)容完全一樣.
非首次加載之增量數(shù)據(jù)如果模板發(fā)現(xiàn)沒(méi)有變化,那么會(huì)在響應(yīng)頭部返回template-change=false,同時(shí)響應(yīng)包體返回的數(shù)據(jù)不再是完整的html,而是一段JSON數(shù)據(jù),及全部的數(shù)據(jù)塊。我們現(xiàn)在需要跟本地?cái)?shù)據(jù)進(jìn)行差分,找出真正的增量數(shù)據(jù),如上圖中,后臺(tái)返回了N個(gè)數(shù)據(jù),實(shí)際上僅有一個(gè)數(shù)據(jù)是有變化的,那么我們僅需要將這個(gè)變化的數(shù)據(jù)提交到頁(yè)面即可。一般場(chǎng)景下,這個(gè)差異的數(shù)據(jù)比全部數(shù)據(jù)要小很多。如果頁(yè)面拆分?jǐn)?shù)據(jù)得更細(xì),那么頁(yè)面的變動(dòng)就更小,這個(gè)取決于前端同學(xué)對(duì)數(shù)據(jù)塊的細(xì)化程度。
獲得變化數(shù)據(jù)塊(diff_data)后,客戶端只需要通知頁(yè)面頁(yè)面設(shè)置的回調(diào)接口(getDiffDataCallback)進(jìn)行界面元素更新即可。這里javascript的通信方式也可以自由定義(可以使用webview標(biāo)準(zhǔn)的javascript通信方式,也可以使用偽協(xié)議的方式),只要頁(yè)面跟終端協(xié)商一致就可以。
對(duì)于數(shù)據(jù)更新這種場(chǎng)景,終端還會(huì)將新的數(shù)據(jù)和模板拼接成為新的頁(yè)面,保持緩存最新。當(dāng)終端初始化比較慢的時(shí)候,WebView去加載緩存的時(shí)候,這個(gè)頁(yè)面可能已經(jīng)是最新的了,連數(shù)據(jù)刷新都不需要。
非首次加載之模板更新與數(shù)據(jù)更新模式不一樣,由于業(yè)務(wù)需求,頁(yè)面的模板會(huì)發(fā)生更改。當(dāng)終端在獲取到新的模板和數(shù)據(jù)后,本地在子線程中進(jìn)行合并,生成一個(gè)新的緩存,然后回調(diào)通知終端,刷新WebView來(lái)加載新的緩存。
我們來(lái)看一下最終的流程圖,跟動(dòng)態(tài)緩存對(duì)比,有不少細(xì)節(jié)優(yōu)化:
我們從第2步開(kāi)始,SonicSession首先會(huì)去讀取緩存。會(huì)拋個(gè)消息通知WebView讀取緩存,如果Webview已經(jīng)準(zhǔn)備好,則直接加載緩存,如果沒(méi)有,則緩存先放在內(nèi)存里面。同時(shí)SonicSession也會(huì)帶上模板等信息到后臺(tái)拉取新的內(nèi)容,后臺(tái)經(jīng)過(guò)Sonic-Diff之后,會(huì)返回新的數(shù)據(jù)。SonicSession拿到新的數(shù)據(jù)后,首先會(huì)跟本地?cái)?shù)據(jù)進(jìn)行Diff,如果發(fā)現(xiàn)WebView已經(jīng)加載緩存,則直接提交增量數(shù)據(jù)給頁(yè)面。否則繼續(xù)拼接最新的頁(yè)面,替換掉內(nèi)存里面的緩存,同時(shí)保存到本地。這個(gè)時(shí)候WebView如果Ready,則直接進(jìn)行第5步load最新的內(nèi)容即可。
效果統(tǒng)計(jì)這個(gè)是我們外網(wǎng)的統(tǒng)計(jì)數(shù)據(jù)。在數(shù)據(jù)更新模式下,首屏的耗時(shí)在1s左右,相比普通的動(dòng)態(tài)直出,優(yōu)化了50%以上。模板更新這個(gè)會(huì)比首次高,是因?yàn)榧虞d了兩次頁(yè)面,不過(guò)從模式占比上來(lái)看,我們大部分頁(yè)面都是數(shù)據(jù)更新。針對(duì)模板更新這種耗時(shí)比較高的情況,前面優(yōu)化積累的經(jīng)驗(yàn)給我們提供了思路,核心還是從提前獲取資源方向入手,因此我們優(yōu)先考慮如何預(yù)加載模板更新。
預(yù)加載實(shí)際上整個(gè)SonicSession在沒(méi)有WebView的情況下,也是可以獨(dú)立完成所有邏輯的,當(dāng)用戶點(diǎn)擊頁(yè)面的時(shí)候,我們?cè)趯ebView和SonicSession綁定起來(lái)即可。于是我們支持了兩種預(yù)加載的模式,一種是通過(guò)后臺(tái)push的方式,來(lái)提前獲取數(shù)據(jù)。還有一種就是JSAPI,頁(yè)面可以調(diào)用JSAPI來(lái)預(yù)加載用戶可能操作的下一個(gè)頁(yè)面。通過(guò)這兩種方式,我們可以把需要的增量更新數(shù)據(jù)提前拉取回來(lái)
Pic 1: 沒(méi)有使用VasSonic | Pic 2: 使用VasSonic |
---|---|
開(kāi)源只是故事的開(kāi)始,我們?nèi)詴?huì)持續(xù)對(duì) VasSonic 做改進(jìn),包括更易用的接口、更好的性能、更高的可靠性,同時(shí)快速響應(yīng)解決開(kāi)源后的issue和PR。這些改進(jìn)最終也會(huì)原封不動(dòng)地在手Q內(nèi)使用,這一切都是為了更快的WebView加載速度。
Talk is cheap,read the fucking code. If you are interested in VasSonic, don"t forget to STAR VasSonic.
Thank you for reading ~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/51132.html
2017-08-18 前端日?qǐng)?bào) 精選 [譯] 關(guān)于 React Router 4 的一切騰訊祭出大招VasSonic,讓你的H5頁(yè)面首屏秒開(kāi)用簡(jiǎn)單的方法學(xué)習(xí)ECMAScript 6【譯】一個(gè)小時(shí)搭建一個(gè)全棧Web應(yīng)用框架你應(yīng)該知道的Debug技巧Understanding V8’s Bytecode – DailyJS – MediumAnnouncing TypeScript 2.5 RC 中文...
閱讀 2928·2021-10-14 09:42
閱讀 3694·2021-08-11 11:19
閱讀 3542·2019-08-30 13:57
閱讀 3120·2019-08-30 13:49
閱讀 1534·2019-08-29 18:38
閱讀 898·2019-08-29 13:16
閱讀 1850·2019-08-26 13:25
閱讀 3230·2019-08-26 13:24