來源:atuanxy 發(fā)布時間:2018-08-13 14:14:59 閱讀量:1317
前言
數(shù)據(jù)表明,即使在資源有緩存的情況下,首次訪問頁面的耗時也是非首次訪問的兩倍。為什么首次訪問會這么耗時呢?本文詳細分析頁面首次訪問耗時的原因。
常見的初始化
我們先看看打開一個頁面,需要經(jīng)過那些流程??赡軙?,庫加載(Load so,jar文件),外殼初始化,內(nèi)核初始化,創(chuàng)建WebView,創(chuàng)建Renderer進程,初始化V8 JS引擎,初始化IPC,初始化CC,初始化網(wǎng)絡(luò)庫,初始化文件系統(tǒng),初始化數(shù)據(jù)庫,啟動ServiceWorker線程(PWA頁面),DNS解析,創(chuàng)建網(wǎng)絡(luò)連接,頁面服務(wù)器初始化,等等。這些流程前端一般是看不見的。
在討論具體的耗時之前,我們先約定,下文所有的數(shù)據(jù)都是基于Nexus 5手機。不同的手機的性能數(shù)據(jù)差異極大,一些高端手機(比如,iPhone X),性能可能是中低端機的好幾倍。
庫加載
啟動應(yīng)用程序,需要先把一些庫文件加載進內(nèi)存,這個過程是特別耗時的,一般瀏覽器內(nèi)核庫有30M,大概需要300ms。如果是全新安裝首次啟動,還涉及到解壓so和使用DexClassLoader去load dex,這些極其耗時,甚至?xí)^5秒。如果是使用multidex方案,在4.x及以下系統(tǒng),會進一步增加啟動耗時。
外殼初始化
瀏覽器外殼的初始化,用戶點擊桌面圖標啟動瀏覽器,瀏覽器會進入一個狀態(tài)機,按步驟初始化各個功能模塊,很多模塊的初始化會涉及網(wǎng)絡(luò),文件IO,JNI,等等操作,這些都會有一定的耗時。
我們?yōu)槭裁葱枰P(guān)心瀏覽器啟動的耗時呢?一些場景下,用戶通過掃碼或者點擊桌面圖標去訪問頁面,這個過程就會包含瀏覽器的啟動流程,我們有必要了解這其中發(fā)生了什么。對于內(nèi)置瀏覽器內(nèi)核的App,比如,支付寶,手淘,情況又是怎樣的呢?我們這邊暫時沒有支付寶和手淘的啟動性能數(shù)據(jù),但模塊初始化,加載SO和JAR,這些流程都會有,時間不會很小。
在外殼初始化耗時方面,有沒有一些比較好的解決辦法呢?最好的辦法就是進程保活,現(xiàn)在國內(nèi)很多手機廠商都會給微信,支付寶,等超級App的進程?;睿脩粼谌蝿?wù)列表殺掉了應(yīng)用,其實進程還在。如果是多進程的情況,可以提前創(chuàng)建進程,比如,微信和支付寶的小程序,用戶訪問時可以直接使用預(yù)創(chuàng)建的進程。
內(nèi)核初始化
瀏覽器內(nèi)核的初始化,與外殼的初始化類似,內(nèi)核的初始化也需要加載SO和JAR,創(chuàng)建WebView和初始化各個功能模塊。
在創(chuàng)建WebView方面,全新安裝首次創(chuàng)建約1秒,非全新安裝首次創(chuàng)建約300ms,第二次創(chuàng)建約15ms。首次創(chuàng)建Renderrer進程,初始化IPC,初始化CC,這些耗時在百毫秒的級別;V8 引擎相關(guān)的初始化耗時也在百毫秒的級別,其中首次NewContext要20ms。
總的來說,首次訪問加載SO和JAR一般需要500ms,創(chuàng)建WebView和走完內(nèi)核流程一般需要消耗500ms,也就是說,應(yīng)用程序如果提前初始化內(nèi)核,預(yù)創(chuàng)建WebView和加載一個空白內(nèi)容頁面,跑一趟內(nèi)核流程,可以為用戶首次訪問頁面帶來1秒的收益。
業(yè)務(wù)初始化
在頁面加載的過程中,內(nèi)核會有很多回調(diào)通知外殼,這些回調(diào)的處理上是否可能存在性能問題呢?
我們發(fā)現(xiàn),在一些App上,一些接口很可能會出現(xiàn)性能問題,比如,onPageStarted,shouldOverrideUrlLoading,shouldInterceptRequest。
這些接口為什么會出現(xiàn)性能問題呢?一般很多應(yīng)用會在首次onPageStarted回調(diào)時執(zhí)行復(fù)雜的業(yè)務(wù)邏輯,比如,初始化一些統(tǒng)計模塊,進行JS注入,等等。需要說明的是,onPageStarted并不是同步接口,為什么也會有影響呢?因為它是在UI線程執(zhí)行的,長期占用UI線程,會對內(nèi)核有較大的影響,內(nèi)核很多操作需要拋轉(zhuǎn)到UI線程去處理,比如,ServiceWorker線程啟動就有拋轉(zhuǎn)UI的過程,在UI執(zhí)行完之前,它只能等待。
shouldOverrideUrlLoading 是客戶端攔截請求的關(guān)鍵接口,內(nèi)核會同步等待,很多應(yīng)用會有比較復(fù)雜的攔截規(guī)則,往往比較耗時。
shouldInterceptRequest 是客戶端離線包的關(guān)鍵接口,內(nèi)核會同步等待,很多應(yīng)用會在這個接口首次回調(diào)時去解壓離線包和初始化離線模塊。
在一些實際應(yīng)用中,優(yōu)化這些回調(diào)的處理,可以給全部H5頁面帶來 10% 以上的性能提升。
ServiceWorker初始化
ServiceWorker是PWA的關(guān)鍵技術(shù),它具有非常強大的能力,F(xiàn)etch,Cache,Push和Add to home screen,能讓前端開發(fā)者非常靈活的操控頁面緩存。
同時,它也是有比較大的初始化成本的,比如,ServiceWorker線程啟動平均要200ms,而每次訪問頁面,一般ServiceWorker線程至少都需要啟動一次。當(dāng)然,Chrome也在不斷優(yōu)化這塊的耗時,最終預(yù)計能優(yōu)化到100ms以內(nèi)。
關(guān)于ServiceWorker啟動的性能,請參考:PWA系列 - Service Workers 啟動性能
網(wǎng)絡(luò)初始化
在網(wǎng)絡(luò)初始化方面,一般內(nèi)核網(wǎng)絡(luò)庫的初始化并不太耗時,耗時的是DNS和Connection。
用戶首次訪問,一般都需要去進行DNS解析和創(chuàng)建連接,而在后續(xù)訪問時,一般都可以用上緩存或者預(yù)連接。
DNS解析,一般耗時在200ms以上,創(chuàng)建HTTP連接,一般耗時也在200ms以上,而創(chuàng)建HTTPS連接則需要600ms以上。
也就是說,用戶首次訪問時,如果不能提前創(chuàng)建連接,從性能的角度來說,是非常影響的。這個方面我們的建議是,使用HTTPDNS提前解析和緩存DNS,提前創(chuàng)建連接(比如,用戶點擊時)。瀏覽器也有這方面的優(yōu)化,比如,在加載主文檔時,提前發(fā)起子資源的預(yù)連接。
服務(wù)器初始化
頁面服務(wù)器和資源服務(wù)器,是否也需要初始化呢?一般也是需要的,比如,頁面訪問過之后,頁面服務(wù)器也會有一些緩存,用戶再次訪問時可以直接使用緩存而無需走完整的流程,但這些緩存應(yīng)該是大部分用戶都能共享的,所以實際影響不好評估。資源服務(wù)器也一樣,比如,圖床,很多是按用戶手機屏幕和網(wǎng)絡(luò)類型來返回不同圖片的,用戶訪問過就會放到CDN緩存中。
暫時未有數(shù)據(jù)表明服務(wù)器初始化對頁面整體性能產(chǎn)生明顯影響。但我們有另外一份數(shù)據(jù),在一個業(yè)務(wù)中,預(yù)創(chuàng)建WebView提前加載一次模版頁面,能讓全網(wǎng)平均性能優(yōu)化100ms。其中,模版頁是304的,里面的資源都是可緩存的,也就是說,這100ms的收益并不來于緩存,而是來于某些模塊的初始化。
JS初始化
這里提到的JS初始化,并不是前面說的JS引擎相關(guān)的初始化。JS初始化是指JS文件緩存到httpcache和解析編譯生成V8 Cache文件。很多數(shù)據(jù)表明,JS解析編譯占JS耗時的35%以上,一些有巨型JS的頁面甚至可以達到80%。在U4 2.0內(nèi)核中,一般JS執(zhí)行一次之后,就可以生成V8 Cache,雖然V8 Cache可以重復(fù)使用,但也存在被自動清理的情況,所以提前執(zhí)行一次還是有收益的。
一些業(yè)務(wù)中,提前執(zhí)行一次JS,在用戶真實訪問時,耗時從500ms降到200ms。特別是在一些超級App中,基礎(chǔ)JS基本都一樣,提前執(zhí)行一次可能會帶來非常明顯的收益。
結(jié)束語
上面介紹了一些常見的初始化對頁面性能的影響,希望大家能了解到一些隱藏的信息,能開闊Web優(yōu)化的思路。當(dāng)然,這些點不一定都會存在很大的性能問題,比如,一些業(yè)務(wù)模塊處理的非常好的App,在業(yè)務(wù)初始化方面不一定會有性能問題,需要根據(jù)自己的實際場景,具體問題具體分析。