一次 WebView 的内存优化

前段时间团队的一个 H5 项目 经历了机器发烫交互卡顿甚至页面 crash 等故障,排查后发现是内存占用异常导致的。经过连续一周的熬夜优化,目前总算趋于稳定,在这里记录一下问题排查以及性能优化的过程。

项目介绍

先介绍一下项目,技术框架是采用的 Vue 单页面应用,入口是在我们的 app 中,承载方式就是 app 中的 webview,这里主要介绍项目的两个页面:

  • 首页。首页是一个图片列表,点击列表项就会跳转到详情页;
  • 商品详情页。详情页中会包含商品详情图以及描述大图。

看起来其实很简单,怎么也不会想到这么简单的页面会引起内存溢出的问题。那么问题是怎么暴露出来的呢?

问题定位

直接原因是项目引入了 threejs 来渲染 3D 模型,我们发现在进入 3D 商品的详情页后,肉眼可见的会有页面卡顿甚至渲染阻塞,随后发现手机开始发烫,再多进几个 3D 商品的详情页后,必现 webview crash。

通过 adb 工具调试后发现,从首页进入 3D 详情页会使得内存占用增加 200M~800M 不等,3D 模型越大,占用的内存也越高,令人惊讶的是从详情页返回到首页之后,内存占用依然不会下降,导致每进入一次详情页都会使得内存进行累加,直到超出 webview 的内存限制后被 kill。

进一步的排查发现,不仅 3D 模型如此,普通的图片也会导致内存上升且无法被回收,只不过内存增加只有几十兆,也就意味着当图片详情累加的足够多时,依然会造成页面 crash。

发现这个问题后项目组马上开了紧急会议并制定了一系列方案:

  1. 把 3D 模型的展示方式更改为图片展示,以序列帧动画的方式替代原来的 3D 渲染,以降低内存增加率。
  2. 将线上首页和详情页的图片进行 CDN 加参压缩,包括尺寸压缩和 webp 压缩,保证所有图片大小不超过300k。
  3. 首页列表增加图片懒加载,只加载一屏中的图片。
  4. 与 app 端的同事确认内存回收机制,是否可提供手动释放内存的 sdk。
  5. 排查代码逻辑,是否引用不当导致页面销毁时未释放对象造成内存泄漏。

做完以上 1、2 、3点之后,已经不会再有 crash 的情况出现了,但是部分疑问还是没有解开。

一些疑问

  • 为什么页面销毁之后内存未释放?

这个问题我们也去咨询了 app 端的同事,他们给出的回复是客户端自主行为,也就是说内存的增加是 webview 自己的行为。经过反复调试,发现这部分内存其实是加载图片后增加的,可以理解为 webview 将这些加载完成的图片缓存进了内存(注意不是磁盘缓存,local storage 就属于磁盘缓存,可以被手动清空),下次再进入时就不用再去进行网络请求加载图片了。

  • 为什么图片大小只有 1M,内存却会增加 50~80M?

查阅文档后发现图片所占用的手机内存和图片体积呈正相关,但不是对等的。图片占用的内存大小和图片尺寸、图片像素、手机 dpi、渲染引擎等都有关系,所以几兆的图片却能占用几百兆的内存。

总结与思考

  • 对于新框架或者新技术的引入一定要有足够的预研时间,需要充分评估风险,对于 WebGL 相关的技术应当保持谨慎,这次问题的暴露就是因为引入 threejs 时没有充分验证。
  • webview 中加载的图片大小不应超过 300k。应当对图片占用的内存持有敬畏之心,尤其注意 webview 中有通过内存缓存图片的机制。
  • 图片长列表要做懒加载。懒加载能有效提升加载效率以及首屏内存占用率。

一次 WebView 的内存优化

http://cherrow.top/posts/3e5d451e

作者

cherrow

发布于

2022-05-21

更新于

2022-05-21

许可协议

评论