在開發(fā)小程序應(yīng)用中,QA發(fā)現(xiàn)過幾次頁面白屏的情況,苦于難易復(fù)現(xiàn)和調(diào)試,故想對小程序白屏問題進(jìn)行一番探究。
從小程序官方開發(fā)者文檔得知,微信小程序運行在三端:iOS(iPhone/iPad)、Android和用于調(diào)試的開發(fā)者工具。三端的腳本執(zhí)行環(huán)境以及用于渲染非原生組件的環(huán)境是各不相同的[1]:
下面說說WKWebView、Mobile Chrome 53/57、Mobile Chrome 53是什么。
在Apple公司的開發(fā)者文檔網(wǎng)站上,有對WKWebView進(jìn)行介紹,簡單來說,WKWebView是一個為app內(nèi)置瀏覽器渲染交互式網(wǎng)頁內(nèi)容的組件,用于替換老版本的UIWebView組件[2]。不管是UIWebView,還是WKWebView,它們都屬于IOS WebView。我們可以把WebView理解為手機(jī)操作系統(tǒng)的一個系統(tǒng)級的組件。不管是手機(jī)內(nèi)置的瀏覽器,還是其他app,比如微信等,只要你想呈現(xiàn)交互式的網(wǎng)頁內(nèi)容,都可以調(diào)用WebView去完成這件事情。Android WebView亦是如此[3]。
現(xiàn)在我們可以把WKWebView稱為IOS端的WebView,那么Android端的Mobile Chrome 53/57,或者M(jìn)obile Chrome 53又是什么,這兩個跟WebView又是什么關(guān)系呢? 我們可以把Mobile Chrome 53/57理解為Chrome for Android 537版本,這里的537是指Chrome的排版引擎(layout engine)采用的WebKit內(nèi)核版本,具體參考Google Chrome version history[4]。需要指出的是,53/57是不是就是537,這里存疑,沒有查到有效的參考資料,但是這個對我們的研究應(yīng)該沒有什么影響,可以不予考慮。到這里,又引入了兩個概念:layout engine、WebKit內(nèi)核。接下來簡單介紹一下layout engine和WebKit內(nèi)核。
我們都知道瀏覽器有兩個重要的引擎:渲染引擎(rendering engine,也稱layout engine,即上面提到的排版引擎,后續(xù)為了方便,統(tǒng)一描述為渲染引擎)和JS引擎。其中渲染引擎負(fù)責(zé)解析網(wǎng)頁內(nèi)容,計算顯示方式,輸出至顯示設(shè)備。JS引擎則負(fù)責(zé)解析JavaScript語言,實現(xiàn)網(wǎng)頁的動態(tài)交互效果。最開始時渲染引擎和JS引擎并沒有區(qū)分的很明確,后來JS引擎越來越獨立,內(nèi)核就傾向于只指渲染引擎,即瀏覽器內(nèi)核就是該瀏覽器采用的渲染引擎,主要參考X5內(nèi)核調(diào)研報告[5]。在后續(xù)的討論中,瀏覽器內(nèi)核就單指渲染引擎。
那WebKit內(nèi)核又是什么?這個不得不追溯WebKit的歷史了。1998,自由軟件社區(qū)KDE開發(fā)了HTML排版引擎KHTML和JavaScript解析引擎KJS,也就是現(xiàn)代瀏覽器兩個重要的引擎。Apple公司的開發(fā)者Don Melton于2001年在KDE的基礎(chǔ)之上開始了WebKit項目。剛開始時,WebKit僅為KDE的復(fù)刻,我們可以理解為WebKit是KDE基礎(chǔ)上fork出來的分支。后來,在WebKit項目中,KHTML被命名為WebCore,KJS被命名為JavaScriptCore,主要參考維基百科[6]。至此,我們可以回答,至少針對Apple的產(chǎn)品來說,瀏覽器內(nèi)核就是WebKit,即渲染引擎采用的是WebKit內(nèi)核。
webkit項目是Apple公司發(fā)展自家瀏覽器啟動的項目。Google公司在發(fā)展Chrome瀏覽器也成立了Chromium項目。在Chromium項目中,JavaScript解析引擎采用Google自己開發(fā)的大名鼎鼎的V8引擎,渲染引擎采用的是WebKit內(nèi)核。到2013年7月份,Chromium項目將渲染引擎替換為Blink引擎,并在Chrome28及后續(xù)的版本上采用[4][7]。Blink引擎是Google在WebKit項目中的WebCore基礎(chǔ)上fork出來的一個分支[8][9]。我們可以用一幅圖把KDE、WebKit和Chromium串聯(lián)起來:
現(xiàn)在,我們再回過頭來看一下Mobile Chrome 53/57,或者M(jìn)obile Chrome 53,其實它的內(nèi)核還是從WebKit上演化而來。繞了這么遠(yuǎn),只為一句話:小程序就是運行在WebView之上。那么我們的初衷,研究小程序白屏問題,其實就是在探究WebView白屏問題。如果要更詳細(xì)一點,那就是WKWebview、Android WebView白屏的原因。
關(guān)于WKWebview白屏,網(wǎng)上羅列的常見原因大致有以下幾種:
針對原因3,解決的方案是判斷IOS系統(tǒng)版本,小于8.2的使用UIWebView。如果站在小程序開發(fā)者的角度,這個跟我們好像沒有關(guān)系。小程序是個平臺,我們在這個平臺上開發(fā)我們的小程序應(yīng)用,如果小程序也有這個問題,那只能由小程序團(tuán)隊去解決這件事情。還有,比如原因4,我們該嵌套還是得嵌套,有問題也是小程序團(tuán)隊去解決。至于原因2,如果是小程序原生開發(fā)的話,頁面間的跳轉(zhuǎn)URL包含中文也是能正常跳轉(zhuǎn)的,這個應(yīng)該是小程序內(nèi)部兼容了。但是原因1,這個跟我們就有很大的關(guān)系了,比如我們定義了大量的變量,使用完了卻沒有釋放,那么這部分內(nèi)存在小程序銷毀之前會被一直占用。再比如我們在某一刻操作了某個比較大的變量,可能在短時間內(nèi),內(nèi)存使用量也會飆升。同樣的,對于導(dǎo)致Android WebView白屏的問題,絕大部分也只能由小程序團(tuán)隊去解決。
這樣一來,從開發(fā)小程序應(yīng)用的前端角度來說,我們能夠把握的是盡量避免由于內(nèi)存使用緊張導(dǎo)致的部分WebView被回收而出現(xiàn)的白屏問題。至此,我們研究的小程序白屏問題,可以轉(zhuǎn)向?qū)π〕绦騼?nèi)存優(yōu)化的研究。
下面總結(jié)一下平時開發(fā)過程中可能會導(dǎo)致內(nèi)存警告的操作:
使用大圖片和長列表圖片。根據(jù)小程序團(tuán)隊分析過的大部分案例,大圖片和長列表圖片的使用,都會引起WKWebview被回收[10]。其中長列表頁圖片是指頁面包含數(shù)目較大的列表,每個列表里面又引用了圖片。
隨意定義變量,由于小程序的機(jī)制而又沒有得到釋放。以下四種場景下定義的變量,即使離開當(dāng)前頁面,變量也不會被回收:
定義在Page構(gòu)造器外層的全局變量。
定義在data內(nèi)部的數(shù)據(jù)。
定義在Page內(nèi)部,類data數(shù)據(jù)。
掛載到getApp().globalData上的數(shù)據(jù)。
假如我們在testvar頁面定義了上述變量,由testvar通過navigateTo跳轉(zhuǎn)到下一個頁面otherpage,在頁面otherpage里面我們可以通過getCurrentPages()獲取頁面testvar的引用,進(jìn)而獲取里面的變量。通過navigateTo打開新頁面,上一個頁面進(jìn)入頁面棧,并且該頁面只是hide,并不是unload[11]。小程序框架的頁面棧最多可支持10層頁面。設(shè)想一下,那些具有復(fù)雜交互的頁面,每層頁面都附帶了眾多的數(shù)據(jù),甚至包含很多圖片,再考慮多層頁面并存的問題,那內(nèi)存使用量將是很可觀的。在頁面棧里面的頁面unload之前,都會造成持續(xù)的內(nèi)存占用。
短時間內(nèi)大數(shù)據(jù)操作。假設(shè)在某個時間點,我們需要對接口返回的大量數(shù)據(jù)進(jìn)行操作,可能會造成瞬時的內(nèi)存占用。
列表數(shù)據(jù)的持續(xù)累加,導(dǎo)致某個數(shù)據(jù)異常大。設(shè)想一下,假如我們的列表頁有很多條數(shù)據(jù),每經(jīng)過一次分頁請求,我們就把新的數(shù)據(jù)concat到已有的數(shù)據(jù)之上,久而久之,這條數(shù)據(jù)可能會變成巨無霸,逐漸侵蝕我們的內(nèi)存。
所幸的是,上述這些可能造成內(nèi)存大量占用的操作,我們是可以避免或者優(yōu)化的。
希望大家進(jìn)行批評和指正!