小程序白屏问题和内存研究

在开发小程序应用中,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…

相关文章
相关标签/搜索