在开发小程序应用中,QA发现过几次页面白屏的情况,苦于难易复现和调试,故想对小程序白屏问题进行一番探究。
从小程序官方开发者文档得知,微信小程序运行在三端:iOS(iPhone/iPad)、Android和用于调试的开发者工具。三端的脚本执行环境以及用于渲染非原生组件的环境是各不相同的[1]:
下面说说WKWebView、Mobile Chrome 53/57、Mobile Chrome 53是什么。
在Apple公司的开发者文档网站上,有对WKWebView进行介绍,简单来说,WKWebView是一个为app内置浏览器渲染交互式网页内容的组件,用于替换老版本的UIWebView组件[2]。不管是UIWebView,还是WKWebView,它们都属于IOS WebView。我们可以把WebView理解为手机操作系统的一个系统级的组件。不管是手机内置的浏览器,还是其他app,比如微信等,只要你想呈现交互式的网页内容,都可以调用WebView去完成这件事情。Android WebView亦是如此[3]。
现在我们可以把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内核。
我们都知道浏览器有两个重要的引擎:渲染引擎(rendering engine,也称layout engine,即上面提到的排版引擎,后续为了方便,统一描述为渲染引擎)和JS引擎。其中渲染引擎负责解析网页内容,计算显示方式,输出至显示设备。JS引擎则负责解析JavaScript语言,实现网页的动态交互效果。最开始时渲染引擎和JS引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎,即浏览器内核就是该浏览器采用的渲染引擎,主要参考X5内核调研报告[5]。在后续的讨论中,浏览器内核就单指渲染引擎。
那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白屏,网上罗列的常见原因大致有以下几种:
针对原因3,解决的方案是判断IOS系统版本,小于8.2的使用UIWebView。如果站在小程序开发者的角度,这个跟我们好像没有关系。小程序是个平台,我们在这个平台上开发我们的小程序应用,如果小程序也有这个问题,那只能由小程序团队去解决这件事情。还有,比如原因4,我们该嵌套还是得嵌套,有问题也是小程序团队去解决。至于原因2,如果是小程序原生开发的话,页面间的跳转URL包含中文也是能正常跳转的,这个应该是小程序内部兼容了。但是原因1,这个跟我们就有很大的关系了,比如我们定义了大量的变量,使用完了却没有释放,那么这部分内存在小程序销毁之前会被一直占用。再比如我们在某一刻操作了某个比较大的变量,可能在短时间内,内存使用量也会飙升。同样的,对于导致Android WebView白屏的问题,绝大部分也只能由小程序团队去解决。
这样一来,从开发小程序应用的前端角度来说,我们能够把握的是尽量避免由于内存使用紧张导致的部分WebView被回收而出现的白屏问题。至此,我们研究的小程序白屏问题,可以转向对小程序内存优化的研究。
下面总结一下平时开发过程中可能会导致内存警告的操作:
使用大图片和长列表图片。根据小程序团队分析过的大部分案例,大图片和长列表图片的使用,都会引起WKWebview被回收[10]。其中长列表页图片是指页面包含数目较大的列表,每个列表里面又引用了图片。
随意定义变量,由于小程序的机制而又没有得到释放。以下四种场景下定义的变量,即使离开当前页面,变量也不会被回收:
定义在Page构造器外层的全局变量。
定义在data内部的数据。
定义在Page内部,类data数据。
挂载到getApp().globalData上的数据。
假如我们在testvar页面定义了上述变量,由testvar通过navigateTo跳转到下一个页面otherpage,在页面otherpage里面我们可以通过getCurrentPages()获取页面testvar的引用,进而获取里面的变量。通过navigateTo打开新页面,上一个页面进入页面栈,并且该页面只是hide,并不是unload[11]。小程序框架的页面栈最多可支持10层页面。设想一下,那些具有复杂交互的页面,每层页面都附带了众多的数据,甚至包含很多图片,再考虑多层页面并存的问题,那内存使用量将是很可观的。在页面栈里面的页面unload之前,都会造成持续的内存占用。
短时间内大数据操作。假设在某个时间点,我们需要对接口返回的大量数据进行操作,可能会造成瞬时的内存占用。
列表数据的持续累加,导致某个数据异常大。设想一下,假如我们的列表页有很多条数据,每经过一次分页请求,我们就把新的数据concat到已有的数据之上,久而久之,这条数据可能会变成巨无霸,逐渐侵蚀我们的内存。
所幸的是,上述这些可能造成内存大量占用的操作,我们是可以避免或者优化的。
希望大家进行批评和指正!