小程序白屏問題和內存研究

在開發小程序應用中,QA發現過幾回頁面白屏的狀況,苦於難易復現和調試,故想對小程序白屏問題進行一番探究。javascript

從小程序官方開發者文檔得知,微信小程序運行在三端:iOS(iPhone/iPad)、Android和用於調試的開發者工具。三端的腳本執行環境以及用於渲染非原生組件的環境是各不相同的[1]:html

  1. 在 iOS 上,小程序邏輯層的 javascript 代碼運行在 JavaScriptCore 中,視圖層是由 WKWebView 來渲染的,環境有 iOS八、iOS九、iOS10;
  2. 在 Android 上,舊版本,小程序邏輯層的 javascript 代碼運行中 X5 JSCore 中,視圖層是由 X5 基於 Mobile Chrome 53/57 內核來渲染的;
  3. 新版本,小程序邏輯層的 javascript 代碼運行在 V8 中,視圖層是由自研 XWeb 引擎基於 Mobile Chrome 53 內核來渲染的;
  4. 在 開發工具上,小程序邏輯層的 javascript 代碼是運行在 NW.js 中,視圖層是由 Chromium 60 Webview 來渲染的。

下面說說WKWebView、Mobile Chrome 53/5七、Mobile Chrome 53是什麼。前端

在Apple公司的開發者文檔網站上,有對WKWebView進行介紹,簡單來講,WKWebView是一個爲app內置瀏覽器渲染交互式網頁內容的組件,用於替換老版本的UIWebView組件[2]。不論是UIWebView,仍是WKWebView,它們都屬於IOS WebView。咱們能夠把WebView理解爲手機操做系統的一個系統級的組件。不論是手機內置的瀏覽器,仍是其餘app,好比微信等,只要你想呈現交互式的網頁內容,均可以調用WebView去完成這件事情。Android WebView亦是如此[3]。java

如今咱們能夠把WKWebView稱爲IOS端的WebView,那麼Android端的Mobile Chrome 53/57,或者Mobile Chrome 53又是什麼,這兩個跟WebView又是什麼關係呢? 咱們能夠把Mobile Chrome 53/57理解爲Chrome for Android 537版本,這裏的537是指Chrome的排版引擎(layout engine)採用的WebKit內核版本,具體參考Google Chrome version history[4]。須要指出的是,53/57是否是就是537,這裏存疑,沒有查到有效的參考資料,可是這個對咱們的研究應該沒有什麼影響,能夠不予考慮。到這裏,又引入了兩個概念:layout engine、WebKit內核。接下來簡單介紹一下layout engine和WebKit內核。android

咱們都知道瀏覽器有兩個重要的引擎:渲染引擎(rendering engine,也稱layout engine,即上面提到的排版引擎,後續爲了方便,統一描述爲渲染引擎)和JS引擎。其中渲染引擎負責解析網頁內容,計算顯示方式,輸出至顯示設備。JS引擎則負責解析JavaScript語言,實現網頁的動態交互效果。最開始時渲染引擎和JS引擎並無區分的很明確,後來JS引擎愈來愈獨立,內核就傾向於只指渲染引擎,即瀏覽器內核就是該瀏覽器採用的渲染引擎,主要參考X5內核調研報告[5]。在後續的討論中,瀏覽器內核就單指渲染引擎。web

那WebKit內核又是什麼?這個不得不追溯WebKit的歷史了。1998,自由軟件社區KDE開發了HTML排版引擎KHTML和JavaScript解析引擎KJS,也就是現代瀏覽器兩個重要的引擎。Apple公司的開發者Don Melton於2001年在KDE的基礎之上開始了WebKit項目。剛開始時,WebKit僅爲KDE的復刻,咱們能夠理解爲WebKit是KDE基礎上fork出來的分支。後來,在WebKit項目中,KHTML被命名爲WebCore,KJS被命名爲JavaScriptCore,主要參考維基百科[6]。至此,咱們能夠回答,至少針對Apple的產品來講,瀏覽器內核就是WebKit,即渲染引擎採用的是WebKit內核。小程序

webkit項目是Apple公司發展自家瀏覽器啓動的項目。Google公司在發展Chrome瀏覽器也成立了Chromium項目。在Chromium項目中,JavaScript解析引擎採用Google本身開發的大名鼎鼎的V8引擎,渲染引擎採用的是WebKit內核。到2013年7月份,Chromium項目將渲染引擎替換爲Blink引擎,並在Chrome28及後續的版本上採用[4][7]。Blink引擎是Google在WebKit項目中的WebCore基礎上fork出來的一個分支[8][9]。咱們能夠用一幅圖把KDE、WebKit和Chromium串聯起來:微信小程序

如今,咱們再回過頭來看一下Mobile Chrome 53/57,或者Mobile Chrome 53,其實它的內核仍是從WebKit上演化而來。繞了這麼遠,只爲一句話:小程序就是運行在WebView之上。那麼咱們的初衷,研究小程序白屏問題,其實就是在探究WebView白屏問題。若是要更詳細一點,那就是WKWebview、Android WebView白屏的緣由。瀏覽器

關於WKWebview白屏,網上羅列的常見緣由大體有如下幾種:微信

  1. 內存佔用比較大時,WebContent Process 會 crash,從而出現白屏現象。
  2. URL網址無效或者含有中文字符。
  3. WKWebview剛推出時,在IOS8.0~8.2會偶爾出現白屏
  4. 因爲滾動組件嵌套的結構,不刷新的問題。

針對緣由3,解決的方案是判斷IOS系統版本,小於8.2的使用UIWebView。若是站在小程序開發者的角度,這個跟咱們好像沒有關係。小程序是個平臺,咱們在這個平臺上開發咱們的小程序應用,若是小程序也有這個問題,那隻能由小程序團隊去解決這件事情。還有,好比緣由4,咱們該嵌套仍是得嵌套,有問題也是小程序團隊去解決。至於緣由2,若是是小程序原生開發的話,頁面間的跳轉URL包含中文也是能正常跳轉的,這個應該是小程序內部兼容了。可是緣由1,這個跟咱們就有很大的關係了,好比咱們定義了大量的變量,使用完了卻沒有釋放,那麼這部份內存在小程序銷燬以前會被一直佔用。再好比咱們在某一刻操做了某個比較大的變量,可能在短期內,內存使用量也會飆升。一樣的,對於致使Android WebView白屏的問題,絕大部分也只能由小程序團隊去解決。

這樣一來,從開發小程序應用的前端角度來講,咱們可以把握的是儘可能避免因爲內存使用緊張致使的部分WebView被回收而出現的白屏問題。至此,咱們研究的小程序白屏問題,能夠轉向對小程序內存優化的研究。

下面總結一下平時開發過程當中可能會致使內存警告的操做:

  1. 使用大圖片和長列表圖片。根據小程序團隊分析過的大部分案例,大圖片和長列表圖片的使用,都會引發WKWebview被回收[10]。其中長列表頁圖片是指頁面包含數目較大的列表,每一個列表裏面又引用了圖片。

  2. 隨意定義變量,因爲小程序的機制而又沒有獲得釋放。如下四種場景下定義的變量,即便離開當前頁面,變量也不會被回收:

    • 定義在Page構造器外層的全局變量。

    • 定義在data內部的數據。

    • 定義在Page內部,類data數據。

    • 掛載到getApp().globalData上的數據。

    假如咱們在testvar頁面定義了上述變量,由testvar經過navigateTo跳轉到下一個頁面otherpage,在頁面otherpage裏面咱們能夠經過getCurrentPages()獲取頁面testvar的引用,進而獲取裏面的變量。經過navigateTo打開新頁面,上一個頁面進入頁面棧,而且該頁面只是hide,並非unload[11]。小程序框架的頁面棧最多可支持10層頁面。設想一下,那些具備複雜交互的頁面,每層頁面都附帶了衆多的數據,甚至包含不少圖片,再考慮多層頁面並存的問題,那內存使用量將是很可觀的。在頁面棧裏面的頁面unload以前,都會形成持續的內存佔用。

  3. 短期內大數據操做。假設在某個時間點,咱們須要對接口返回的大量數據進行操做,可能會形成瞬時的內存佔用。

  4. 列表數據的持續累加,致使某個數據異常大。設想一下,假如咱們的列表頁有不少條數據,每通過一次分頁請求,咱們就把新的數據concat到已有的數據之上,長此以往,這條數據可能會變成巨無霸,逐漸侵蝕咱們的內存。

所幸的是,上述這些可能形成內存大量佔用的操做,咱們是能夠避免或者優化的。

  1. 針對緣由1中的大圖片,咱們就能夠適當壓縮壓縮。若是不能再壓了,或者圖片必須這麼大,還有單個圖片原本都不大,可是列表太多形成引用的圖片太多怎麼辦呢?好,這個能夠暫時先放下,在後續的討論中再提對應的解決方案。
  2. 針對緣由2,咱們須要結合實際的業務場景,對那些用完就能夠丟棄的,不須要伴隨頁面整個生存週期存在的變量,就不要用那四種方式去定義數據。
  3. 針對緣由3,咱們能夠儘可能和接口開發方協商,經過分頁或其餘方式來避免接口一次返回大量的數據。
  4. 針對緣由4,本質的緣由是持續的分頁請求致使新的數據不斷追加到已有的數據之上,那麼這種場景,咱們就須要對已有的部分數據進行捨棄。捨棄哪些已有的數據,須要一個原則。設想一下有這樣一個場景,咱們進入列表頁list,咱們定義了listData用來存放每次分頁請求過來的數據。第一頁的數據過來了,listData僅僅包含第一頁數據。第二頁數據過來了,咱們把新數據concat到第一頁上,此時,listData就包含了第1、第二兩個頁面的數據。第三頁的數據過來了,listData就包含前三個頁面的數據。如今咱們不妨停下來想一想,目前咱們給用戶呈現的是第三頁的數據,第一頁的數據處於不可見的狀態,既然不可見,爲什麼不把它丟棄?若是用戶往上滑動,須要呈現第一頁的數據時,咱們能夠再請求第一頁的數據。listData丟棄部分數據,會及時反饋到view層,view層部分節點也會隨之銷燬,這樣App Service層和view層佔用的部份內存都會獲得釋放。固然,咱們提出的這種方案,就是爲了解決持續的分頁請求致使新的數據不斷追加到已有的數據之上的問題,至於要不要採用,採用了什麼場景下進行已有數據丟棄,丟棄哪些數據,這些都要結合實際的業務進行評估和權衡。

但願你們進行批評和指正!

參考文獻: [1]: developers.weixin.qq.com/miniprogram… [2]: developer.apple.com/documentati… [3]: developer.android.com/reference/a… [4]: en.wikipedia.org/wiki/Google… [5]: juejin.im/post/5a3522… [6]: zh.wikipedia.org/wiki/WebKit [7]: zh.wikipedia.org/wiki/Google… [8]: zh.wikipedia.org/wiki/Chromi… [9]: zh.wikipedia.org/wiki/Blink [10]:developers.weixin.qq.com/miniprogram… [11]: developers.weixin.qq.com/miniprogram…

相關文章
相關標籤/搜索