一次 WebView 的内存优化
前段时间团队的一个 H5 项目
经历了机器发烫、交互卡顿甚至页面 crash 等故障,排查后发现是内存占用异常
导致的。经过连续一周的熬夜优化,目前总算趋于稳定,在这里记录一下问题排查以及性能优化的过程。
项目介绍
先介绍一下项目,技术框架是采用的 Vue 单页面应用
,入口是在我们的 app 中,承载方式就是 app 中的 webview
,这里主要介绍项目的两个页面:
- 首页。首页是一个
图片列表
,点击列表项就会跳转到详情页; - 商品详情页。详情页中会包含商品详情图以及描述大图。
看起来其实很简单,怎么也不会想到这么简单的页面会引起内存溢出
的问题。那么问题是怎么暴露出来的呢?
问题定位
直接原因是项目引入了 threejs
来渲染 3D 模型,我们发现在进入 3D 商品的详情页后,肉眼可见的会有页面卡顿甚至渲染阻塞,随后发现手机开始发烫,再多进几个 3D 商品的详情页后,必现 webview crash。
通过 adb
工具调试后发现,从首页进入 3D 详情页会使得内存占用增加 200M~800M
不等,3D 模型越大,占用的内存也越高,令人惊讶的是从详情页返回到首页之后,内存占用依然不会下降
,导致每进入一次详情页都会使得内存进行累加,直到超出 webview 的内存限制后被 kill。
进一步的排查发现,不仅 3D 模型如此,普通的图片也会导致内存上升且无法被回收,只不过内存增加只有几十兆,也就意味着当图片详情累加的足够多时,依然会造成页面 crash。
发现这个问题后项目组马上开了紧急会议并制定了一系列方案:
- 把 3D 模型的展示方式更改为图片展示,以序列帧动画的方式替代原来的 3D 渲染,以降低内存增加率。
- 将线上首页和详情页的图片进行
CDN
加参压缩,包括尺寸压缩和webp
压缩,保证所有图片大小不超过300k。- 首页列表增加
图片懒加载
,只加载一屏中的图片。- 与 app 端的同事确认内存回收机制,是否可提供手动释放内存的 sdk。
- 排查代码逻辑,是否引用不当导致页面销毁时未释放对象造成内存泄漏。
做完以上 1、2 、3点之后,已经不会再有 crash 的情况出现了,但是部分疑问还是没有解开。
一些疑问
- 为什么页面销毁之后内存未释放?
这个问题我们也去咨询了 app 端的同事,他们给出的回复是客户端自主行为,也就是说内存的增加是 webview
自己的行为。经过反复调试,发现这部分内存其实是加载图片后增加的,可以理解为 webview
将这些加载完成的图片缓存进了内存
(注意不是磁盘缓存,local storage 就属于磁盘缓存,可以被手动清空),下次再进入时就不用再去进行网络请求加载图片了。
- 为什么图片大小只有 1M,内存却会增加 50~80M?
查阅文档后发现图片所占用的手机内存和图片体积呈正相关,但不是对等的。图片占用的内存大小和图片尺寸、图片像素、手机 dpi、渲染引擎
等都有关系,所以几兆的图片却能占用几百兆的内存。
总结与思考
- 对于新框架或者新技术的引入一定要有足够的预研时间,需要充分评估风险,对于 WebGL 相关的技术应当保持谨慎,这次问题的暴露就是因为引入 threejs 时没有充分验证。
- webview 中加载的图片大小不应超过 300k。应当对图片占用的内存持有敬畏之心,尤其注意 webview 中有通过内存缓存图片的机制。
- 图片长列表要做懒加载。懒加载能有效提升加载效率以及首屏内存占用率。
一次 WebView 的内存优化