背景
最近在工作中,遇到了页面跳转时点击上报丢失的问题,突出表现在微信ios的webview上,上报后直接跳转的失败率达到了惊人的93%。喝口水压压惊,开始逐步分析问题的发生。
原理
现代浏览器越来越快,尤其是Chrome的v8内核,跑的那叫一个迅速。但是必须看到的是,在不同的平台上,浏览器之间的实现还是略有不同的。就拿页面跳转时点击上报丢失的问题来看,PC chrome浏览器模拟和微信 Android的Webview, 均不存在这个问题,但是在IOS端的表现却如此的严重。
现在我们跨域上报的方式主要有三种,image src, jsonp和fetch。不管是哪种上报方式,在浏览器点击跳转时,跳转前的点击上报请求都会进行一个三次握手,如果此时,网络较慢、服务器运行缓慢或者上报请求还在处理阶段,这时,如果页面被卸载了,浏览器都会自动对当前的请求进行abort。这样,这个http的请求就没有建立,导致上报没有真正发出。
解决方案
说到解决方案,那就海了去了,但是能打的其实一个也没有。下面我会把我认为还OK的方法加粗。
- 加一个delay
这应该是最简单的方案了吧,没有什么是setTimeout 1s不能解决的,如果有,那就2s……但是这种方案不管是对用户的体验,还是对代码的优雅,都损害特别大。除非真的是对性能不太在意,不然这招是用不出来的。
之前类似的方案还有:
1. 阻塞式的 Ajax 请求
2. 暴力的死循环
3. 发一个图片请求阻塞
当然这些方案在chrome浏览器上都应该失效了,但是他们与delay的原理和优缺点都是类似的。对这三种失效的方案有兴趣的可以看《页面跳转时,统计数据丢失问题探讨》
- window.name
把要跳转的url放到window.name中,然后在下一个页面上上报点击。window.name的含义是获得/设置窗口的名称,这个属性是跨域的,当窗口存在的时候,window.name就存在;每个窗口的名称都可以是不一样的。
在这个方案中,window.name被当做了一个存储上报点击地址的介质。
虽然这个方案解决了体验差的问题,但是要实现这个方案,就要在所有的页面上都部署处理window.name的代码。举一个简单的例子,你是一个网站主,点击一个广告跳转广告落地页,总不能要求在这个广告落地页上也部署你的代码,所以这种方案也有很大的局限性。
- cookie和localStorage
这种方案与window.name的方案类似,但是因为cookie和localStorage的生命周期比window.name的生命周期更长。我们就可以将延时上报的代码放到我们之前的页面上去:每当用户在跳转页面时出现了上报丢失的情况,就将这个上报地址写入cookie或者localStorage中,然后等用户再次回到我们的页面上时,判断下这两个存储介质中是否有值,有值则上报即可。
这种方案无需对所有的页面都加监控代码,但是若是某些极端情况,用户去了别的页面一去不返,或者间隔了一个很长的时间(诸如2天),那对我们整体统计数据上都会有个偏差。
- Beacon API
这是W3C委员会给的浏览器异步请求发送API。它可以保证即使页面在unload状态下,也会异步发送统计,不影响页面过渡/跳转到下跳页。但是,它的浏览器支持率一直是个问题。
截止本文发稿时(2018年02月04日),支持率如下:
可以看到至关重要的ios safari终于在11.3版本支持了这一特性,但是,我更新了IOS的最新版本:11.2.5!估计还需要两个月这个特性才能被使用。而IOS最近爆出的升级变慢门又会进一步拖慢新版本的覆盖时间,难受了呀,老哥!
- 后台上报
说过来覆过去,无非是前端浏览器abort了请求,如果后台同学给给力,问题不就解决了吗?一种解决方案是前端把所有需要上报的内容扔给后台,让后台去进行上报并进行302跳转。这是最完美的方案吗?
前台Yes, 后台No。
总结
尽管现在我们还是没有一个完美的解决方案,但是在不久的将来,我们就能使用到Beacon API。你问我现在怎么办?先使用cookie/localStorage的方案顶一顶吧。