Android 完美解决WebView缺陷 - Go语言中文社区

Android 完美解决WebView缺陷


作为一名Android工程师,我们在使用Webview时候遇到过很多很多的坑,比如加载白屏,超时加载,卡顿等等,一系列的问题,每次在使用Webview的时候脑海中是不是出现过无数的草泥马。今天我们将从性能、内存消耗、体验、安全几个维度,来系统的分析客户端默认WebView的问题,以及对应的优化方案。彻底解决困扰在Android工程师面前的这个难题。

1.性能问题

打开速度比native慢

对于一个普通用户来讲,打开一个WebView通常会经历以下几个阶段:

交互无反馈——》到达新的页面,页面白屏——》页面基本框架出现,但是没有数据——》页面处于loading状态——》出现所需的数据

如果从程序上观察,WebView启动过程大概分为以下几个阶段:


WebView初始化
1.当App首次打开时,默认是并不初始化浏览器内核的;只有当创建WebView实例的时候,才会创建WebView的基础框架。
所以与浏览器不同,App中打开WebView的第一步并不是建立连接,而是启动浏览器内核。

2.在浏览器中,我们输入地址时(甚至在之前),浏览器就可以开始加载页面。而在客户端中,客户端需要先花费时间初始化WebView完成后,才开始加载。

解决方案

方案1,全局WebView

在客户端刚启动时,就初始化一个全局的WebView待用,并隐藏;当用户访问了WebView时,直接使用这个WebView加载对应网页,并展示。这种方法可以比较有效的减少WebView在App中的首次打开时间。当用户访问页面时,不需要初始化WebView的时间。当然这也带来了一些问题,包括:额外的内存消耗。页面间跳转需要清空上一个页面的痕迹,更容易内存泄露。

方法2,客户端代理数据请求
在客户端初始化WebView的同时,直接由native开始网络请求数据;当页面初始化完成后,向native获取其代理请求的数据。此方法虽然不能减小WebView初始化时间,但数据请求和WebView初始化可以并行进行,总体的页面加载时间就缩短了;缩短总体的页面加载时间:
还有其他各种优化的方式,不再一一列举,总结起来都是围绕两点:
在使用前预先初始化好WebView,从而减小耗时。
在初始化的同时,通过Native来完成一些网络请求等过程,使得WebView初始化不是完全的阻塞后续过程。

建立连接/服务器处理

在页面请求的数据返回之前,主要有以下过程耗费时间。

DNS
connection

服务器处理解决方案

解决方案

方案一 DNS采用和客户端API相同的域名

(DNS:域名系统,万维网上作为域名和IP地址相互映射的一个分布式数据库,访问网址时会先解析域名,域名分为动态解析和静态解析,静态解析失败会继续动态解析)DNS会在系统级别进行缓存,对于WebView要请求的地址,如果使用的域名与native的API相同,则可以直接使用缓存的DNS而不用再发起请求去进行域名解析所浪费时间。以美团为例,美团的客户端请求域名主要位于api.meituan.com,然而内嵌的WebView主要位于 i.meituan.com。当我们初次打开App时:客户端首次打开都会请求api.meituan.com,其DNS将会被系统缓存。然而当打开WebView的时候,由于请求了不同的域名,需要重新获取i.meituan.com的IP。根据统计,至少10%的用户打开WebView时耗费了60ms在DNS上面,如果WebView的域名与App的API域名统一,则可以让WebView的DNS时间全部达到1.3ms的量级。静态资源同理,最好与客户端的资源域名保持一致。

方案二 同步渲染采用chunk编码

方案三 页面框架渲染

页面在解析到足够多的节点,且所有CSS都加载完成后进行首屏渲染。在此之前,页面保持白屏;在页面完全下载并解析完成之前,页面处于不完整展示状态。

解决方案:

  1. CSS的加载会在HTML解析到CSS的标签时开始,所以CSS的标签要尽量靠前。
  2. 但是,CSS链接下面不能有任何的JS标签(包括很简单的内联JS),否则会阻塞HTML的解析。
  3. 如果必须要在头部增加内联脚本,一定要放在CSS标签之前。

WebView内存消耗


Webview内存消耗较大,

WebView体验

除了打开的速度,WebView通常体验也没有native的实现更好,我们可以找到以下几个例子:

长按选择

在WebView中,长按文字会使得WebView默认开始选择文字;长按链接会弹出提示是否在新页面打开。

解决方法:可以通过给body增加CSS来禁止这些默认规则。

点击延迟

在WebView中,click通常会有大约300ms的延迟(同时包括链接的点击,表单的提交,控件的交互等任何用户点击行为)。

唯一的例外是设置的meta:viewpoint为禁止缩放的Chrome(然而并不是Android默认的浏览器)。

解决方法:使用fastclick一般可以解决这个问题。

页面滑动期间不渲染/执行

在很多需求中会有一些吸顶的元素,例如导航条,购买按钮等;当页面滚动超出元素高度后,元素吸附在屏幕顶部。

这个功能在PC和native中都能够实现,然而在WebView中却成了难题:

 在页面滚动期间,Scroll Event不触发

不仅如此,WebView在滚动期间还有各种限定:

  • setTimeout和setInterval不触发。
  • GIF动画不播放。
  • 很多回调会延迟到页面停止滚动之后。
  • background-position: fixed不支持。
  • 这些限制让WebView在滚动期间很难有较好的体验。

这些限制大部分是不可突破的,但至少对于吸顶功能还是可以做一些支持:

解决方法:

  • 在iOS上,使用position: sticky可以做到元素吸顶。
  • 在Android上,监听touchmove事件可以在滑动期间做元素的position切换(惯性运动期间就无效了)。

crash

通常WebView并不能直接接触到底层的API,因此比较稳定;但仍然有使用不当造成整个App崩溃的情况。

目前发现的案例包括:

  • 使用过大的图片(2M)
  • 不正常使用WebGL


WebView安全问题

WebView被运营商劫持、注入问题

由于WebView加载的页面代码是从服务器动态获取的,这些代码将会很容易被中间环节所窃取或者修改,其中最主要的问题出自地方运营商(浙江尤其明显)和一些WiFi。

我们监测到的问题包括:
无视通信规则强制缓存页面。
header被篡改。
页面被注入广告。
页面被重定向。
页面被重定向并重新iframe到新页面,框架嵌入广告。
HTTPS请求被拦截。

DNS劫持。

针对页面注入的行为,有一些解决方案:

1.使用CSP(Content Security Policy)

2.HTTPS
HTTPS可以防止页面被劫持或者注入,然而其副作用也是明显的,网络传输的性能和成功率都会下降,而且HTTPS的页面会要求页面内所有引用的资源也是HTTPS的,对于大型网站其迁移成本并不算低。
HTTPS的一个问题在于:一旦底层想要篡改或者劫持,会导致整个链接失效,页面无法展示。这会带来一个问题:本来页面只是会被注入广告,而且广告会被CSP拦截,而采用了HTTPS后,整个网页由于受到劫持完全无法展示。
对于安全要求不高的静态页面,就需要权衡HTTPS带来的利与弊了。

3.App使用Socket代理请求

如果HTTP请求容易被拦截,那么让App将其转换为一个Socket请求,并代理WebView的访问也是一个办法。
通常不法运营商或者WiFi都只能拦截HTTP(S)请求,对于自定义的包内容则无法拦截,因此可以基本解决注入和劫持的问题。
Socket代理请求也存在问题:
首先,使用客户端代理的页面HTML请求将丧失边下载边解析的能力;根据前面所述,浏览器在HTML收到部分内容后就立刻开始解析,并加载解析出来的外链、图片等,执行内联的脚本……而目前WebView对外并没有暴露这种流式的HTML接口,只能由客户端完全下载好HTML后,注入到WebView中。因此其性能将会受到影响。
其次,其技术问题也是较多的,例如对跳转的处理,对缓存的处理,对CDN的处理等等……稍不留神就会埋下若干大坑。
此外还有一些其他的办法,例如页面的MD5检测,页面静态页打包下载等等方式,具体如何选择还要根据具体的场景抉择。












版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/lcj235/article/details/80419568
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-28 03:57:51
  • 阅读 ( 905 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢