前言
資料表明,即使在資源有快取的情況下,頁面首次訪問的耗時也是非首次訪問的兩倍。
為什麼首次訪問這麼耗時呢,時間去哪裡了?本文詳細分析頁面首次訪問耗時的原因。
常見的初始化
我們先看看開啟一個頁面,需要經過那些流程。可能會包括,外殼初始化,核心初始化,建立WebView,建立Renderrer程式,初始化V8 JS引擎,初始化IPC,初始化CC,初始化網路庫,初始化檔案系統,初始化資料庫,啟動ServiceWorker執行緒,DNS解析,建立網路連線,頁面伺服器初始化,等等。這些流程前端一般是看不見的。
在討論具體的耗時之前,我們先約定,下文所有的資料都是基於Nexus 5手機。不同的手機的效能資料差異極大,一些高階手機(比如,iPhone X),效能可能是中低端機的好幾倍。
外殼初始化
我們先看看瀏覽器外殼的初始化,使用者點選桌面圖示啟動瀏覽器,瀏覽器會進入一個狀態機,按步驟初始化各個模組,很多模組的初始化會涉及網路,檔案IO,JNI,等等操作,這些都會有一定的耗時。
當然,全新安裝首次啟動,外殼初始化的過程中,一般最耗時的是載入SO和JAR,其中使用DexClassLoader去載入JAR檔案,在一些中低端機器,特別是Android 5.0以前的系統,耗時是以秒計算的,有些甚至可以達到10秒。非全新安裝首次啟動,載入SO和JAR的耗時會大幅下降,大概在500ms。
我們為什麼需要關心瀏覽器啟動的耗時呢?一些場景下,使用者通過掃碼或者點選桌面圖示去訪問頁面,這個過程就會包含瀏覽器的啟動流程,我們有必要了解這其中發生了什麼。
對於內建瀏覽器核心的App,比如,支付寶,手淘,情況又是怎樣的呢?我們這邊暫時沒有支付寶和手淘的啟動效能資料,但模組初始化,載入SO和JAR,這些流程都會有,時間不會很小。
在外殼初始化耗時方面,有沒有一些比較好的解決辦法呢?
最好的辦法就是程式保活,現在國內很多手機廠商都會給微信,支付寶,等超級App去程式保活,使用者在任務列表殺掉了應用,其實程式還在。
如果是多程式的情況,可以提前建立程式,比如,微信和支付寶的小程式,使用者訪問時可以直接使用預建立的程式。
核心初始化
我們再來看看核心的初始化,與外殼的初始化類似,核心的初始化也需要載入SO和JAR,建立WebView和初始化各個功能模組。
在建立WebView方面,全新安裝首次建立約1秒,非全新安裝首次建立約300ms,第二次建立約15ms。
首次建立Renderrer程式,初始化IPC,初始化CC,這些耗時在百毫秒的級別;
V8 引擎相關的初始化耗時也在百毫秒的級別,其中首次NewContext要20ms。
總的來說,首次訪問載入SO和JAR一般需要500ms,建立WebView和走完核心流程一般需要消耗500ms,也就是說,提前初始化核心和預建立WebView載入一個URL,跑一趟核心流程,可以帶來約1秒的收益。
業務初始化
在頁面載入的過程中,核心會有很多回撥通知外殼,這些回撥的處理上是否可能存在效能問題呢?
我們發現,在一些App上,一些介面很可能會出現效能問題,比如,onPageStarted,shouldOverrideUrlLoading,shouldInterceptRequest。
這些介面為什麼會出現效能問題呢?一般很多應用會在首次onPageStarted回撥時執行復雜的業務邏輯,比如,初始化一些統計模組,進行JS注入,等等。需要說明的是,onPageStarted並不是同步介面,為什麼也會有影響呢?因為它是在UI執行緒執行的,長期佔用UI執行緒,會對核心有較大的影響,核心很多操作需要拋轉到UI執行緒去處理,比如,ServiceWorker執行緒啟動就有拋轉UI的過程,在UI執行完之前,它只能等待。
shouldOverrideUrlLoading 是客戶端攔截請求的關鍵介面,核心會同步等待,很多應用會有比較複雜的攔截規則。
shouldInterceptRequest 是客戶端離線包的關鍵介面,核心會同步等待,很多應用會在這個介面首次回撥時去解壓離線包和初始化離線模組。
在一些實際應用中,優化這些回撥的處理,可以給全部H5頁面帶來 10% 以上的效能提升。
ServiceWorker初始化
ServiceWorker是PWA的關鍵技術,它具有非常強大的能力,Fetch,Cache,Push和Add to home screen,能讓前端開發者非常靈活的操控頁面快取。
同時,它也是有比較大的初始化成本的,比如,ServiceWorker執行緒啟動平均要200ms,而每次訪問頁面,一般ServiceWorker執行緒至少都需要啟動一次。當然,Chrome也在不斷優化這塊的耗時,最終預計能優化到100ms以內。
網路初始化
在網路初始化方面,一般核心網路庫的初始化並不太耗時,耗時的是DNS和Connection。
使用者首次訪問,一般都需要去進行DNS解析和建立連線,而在後續訪問時,一般都可以用上快取或者預連線。
DNS解析,一般耗時在200ms以上,建立HTTP連線,一般耗時也在200ms以上,而建立HTTPS連線則需要600ms以上。
也就是說,使用者首次訪問時,如果不能提前建立連線,從效能的角度來說,是非常危險的。
這個方面我們的建議是,使用HTTPDNS提前解析和快取DNS,提前建立連線(比如,使用者點選時)。
瀏覽器也有這方面的優化,比如,在載入主文件時,提前發起子資源的預連線,但在一些託管網路庫的應用來說,這些策略可能不會生效。
伺服器初始化
頁面伺服器和資源伺服器,是否也需要初始化呢?一般也是需要的,比如,頁面訪問過之後,頁面伺服器也會有一些快取,使用者再次訪問時可以直接使用快取而無需走完整的流程,但這些快取應該是大部分使用者都能共享的,所以實際影響不好評估。資源伺服器也一樣,比如,圖床,很多是按使用者手機螢幕和網路型別來返回不同圖片的,使用者訪問過就會放到CDN快取中。
暫時未有資料表明伺服器初始化對頁面整體效能產生明顯影響。但我們有另外一份資料,在一個業務中,預建立WebView提前載入一次模版頁面,能讓全網平均效能優化100ms。其中,模版頁是304的,裡面的資源都是可快取的,也就是說,這100ms的收益並不來於快取,而是來於某些模組的初始化。
JS初始化
這裡提到的JS初始化,並不是前面說的JS引擎相關的初始化。JS初始化是指JS檔案快取到httpcache和解析編譯生成V8 Cache檔案。很多資料表明,JS解析編譯佔JS耗時的35%以上,一些有巨型JS的頁面甚至可以達到80%。在U4 2.0中,一般JS執行一次之後,就可以生成V8 Cache,雖然V8 Cache可以重複使用,但也存在被自動清理的情況,所以提前執行一次還是有收益的。
一些業務中,提前執行一次JS,在使用者真實訪問時,耗時從500ms降到200ms。特別是在一些超級App中,基礎JS基本都一樣,提前執行一次可能會帶來非常明顯的收益。
結束語
上面介紹了一些常見的初始化對頁面效能的影響,希望大家能瞭解到一些隱藏的資訊,能開闊Web優化的思路。當然,這些點不一定會存在很大的效能問題,比如,一些業務模組處理的非常好的App,在業務初始化方面不一定會有效能問題,需要根據自己的實際場景,具體問題具體分析。
作者:小扎zack