总结自美团技术团队《WebView 性能、体验分析与优化》
性能优化
WebView 为什么会感觉很慢?
对于一个普通用户来讲,打开一个 WebView 通常会经历以下几个阶段:
- 交互无反馈
- 到达新的页面,页面白屏
- 页面基本框架出现,但是没有数据;页面处于 loading 状态
- 出现所需的数据
如果从程序上观察,WebView 启动过程大概分为以下几个阶段:
WebView启动时间.pngWebView 初始化
App 中打开 WebView 的第一步并不是建立连接,而是启动浏览器内核。
优化
- 使用全局 WebView
- 客户端代理数据请求
建立连接/服务器处理
DNS 域名解析,与服务器 connection,服务器逻辑处理都将耗费大量时间。
优化
- DNS 采用和客户端 API 相同的域名静态资源同理)
- 同步渲染采用 chunk 编码
页面框架渲染
页面在解析到足够多的节点,且所有 CSS 都加载完成后进行首屏渲染。在此之前,页面保持白屏;在页面完全下载并解析完成之前,页面处于不完整展示状态。
优化
- CSS 的加载会在 HTML 解析到 CSS 的标签时开始,所以 CSS 的标签要尽量靠前。
- 但是,CSS 链接下面不能有任何的 JS 标签(包括很简单的内联 JS),否则会阻塞 HTML 的解析。
- 如果必须要在头部增加内联脚本,一定要放在 CSS 标签之前。
JS 加载
随着网速越来越快,而 CPU 的速度反而没有提升(从 PC 到手机),JS 的时间开销就成为问题了。
优化
- 高性能要求页面还是需要后端渲染。
- React 还是太重了,面向用户写系统需要谨慎考虑。
- JS 代码的编译和执行会有缓存,同 App 中网页尽量统一框架。
WebView 性能优化总结
一个加载网页的过程中,native、网络、后端处理、CPU 都会参与,各自都有必要的工作和依赖关系;让他们相互并行处理而不是相互阻塞才可以让网页加载更快:
- WebView 初始化慢,可以在初始化同时先请求数据,让后端和网络不要闲着。
- 后端处理慢,可以让服务器分 trunk 输出,在后端计算的同时前端也加载网络静态资源。
- 脚本执行慢,就让脚本在最后运行,不阻塞页面解析。
- 同时,合理的预加载、预缓存可以让加载速度的瓶颈更小。
- WebView 初始化慢,就随时初始化好一个 WebView 待用。
- DNS 和链接慢,想办法复用客户端使用的域名和链接。
- 脚本执行慢,可以把框架代码拆分出来,在请求页面之前就执行好。
其他用户体验优化
禁掉长按选择
在 WebView 中,长按文字会使得 WebView 默认开始选择文字,长按链接会弹出提示是否在新页面打开。
解决方法:可以通过给 body 增加 CSS 来禁止这些默认规则。
解决点击延迟
在 WebView 中,click 通常会有大约 300ms 的延迟(同时包括链接的点击,表单的提交,控件的交互等任何用户点击行为)。
唯一的例外是设置的 meta:viewpoint 为禁止缩放的 Chrome(然而并不是 Android 默认的浏览器)。
解决方法:使用 fastclick 一般可以解决这个问题。
页面滑动期间不渲染/执行
WebView在滚动期间有各种限定:
- Scroll Event 不触发
- setTimeout 和 setInterval 不触发。
- GIF 动画不播放。
- 很多回调会延迟到页面停止滚动之后。
- background-position: fixed 不支持。
这些限制让 WebView 在滚动期间很难有较好的体验,并且大部分是不可突破的,但至少对于吸顶功能还是可以做一些支持,方法如下:
- 在iOS上,使用position: sticky可以做到元素吸顶。
- 在Android上,监听touchmove事件可以在滑动期间做元素的position切换(惯性运动期间就无效了)。
crash
通常 WebView 并不能直接接触到底层的 API,因此比较稳定,但仍然有使用不当造成整个 App崩溃的情况,目前发现的案例包括:
- 使用过大的图片(2M)
- 不正常使用 WebGL
安全优化
WebView 被运营商劫持、注入问题
- 使用 CSP(Content Security Policy)
- HTTPS
- App 使用 Socket 代理请求
客户端内打开第三方 WebView
在内嵌的 WebView 中应该限制允许打开的 WebView 的域名,并设置运行访问的白名单。或者当用户打开外部链接前给用户强烈而明显的提示。
网友评论