[转]H5浏览器和webview后退刷新⽅案
⼀、背景
⽤户点击浏览器⼯具栏中的后退按钮,或者移动设备上的返回键时,或者JS执⾏(-1);时,浏览器会在当前窗⼝“打开”历史纪录中的前⼀个页⾯。不同的浏览器在“打开”前⼀个页⾯的表现上并不统⼀,这和浏览器的实现以及页⾯本⾝的设置都有关系。
在移动端HTML5浏览器和webview中,“后退到前⼀个页⾯”意味着:前⼀个页⾯的html/js/css等静态资源的请求(甚⾄是ajax动态接⼝请求)根本不会重新发送,直接使⽤缓存的响应,⽽不管这些静态资源响应的缓存策略是否被设置了禁⽤状态。除⾮请求是JS发送的,且每次发送时,都在url中加⼊了随机数。(证据可以通过抓包来看,发⽣后退返回时,没截获到主页⾯、静态资源和动态接⼝的请求,但抓到JS 发送的pv⽇志请求,因为pv⽇志请求的url中加了随机数。)
后退返回到上⼀个页⾯的表现,⼀句话总结就是:html/js/css/接⼝等资源直接使⽤前⼀次请求过的,⽽JS中的代码从头开始重新执⾏了⼀遍。这在⼀些场景下会导致严重的bug,所以才会提出“后退刷新”的需求。
“后退刷新”的⽬标是浏览器在后退返回到前⼀个页⾯时,能从server端请求到⼀个全新的的页⾯内容(即
status code 200 ok或status code 304 not modified的页⾯响应,⽽不是status 200 from cache根本不向server端请求)进⾏加载展⽰并重新执⾏JS代码。
⼆、思路和⽅案
2.1 浏览器历史纪录和HTTP 缓存
PC浏览器实现后退刷新的⽅法是给响应添加Cache-Control的header,如果server返回页⾯响应的headers中包含如下内容:Cache-Control: no-cache,no-store,must-revalidate
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
浏览器在前进后退到该页⾯时,就会重新发送请求。
代码⽰例如下:
在jsp模板的header部分加⼊如下的禁⽤缓存设置:
1. <head>
2. <meta http-equiv="Pragma" content="no-cache">
3. <meta http-equiv="Cache-control" content="no-cache,no-store,must-revalidate">
4. <meta http-equiv="Expires" content="0">
5. <%
6. response.setHeader("Cache-Control","no-cache,no-store,must-revalidate");
7. response.setHeader("Expires", "0");
8. response.setHeader("Pragma","no-cache");
9. %>
10.
11. </head>
说明⼀下,其实通过meta标签设置缓存策略,会被浏览器忽略,设置在response header中是⽐较可靠的。
这样看上去,浏览器历史纪录和HTTP缓存是有关系的。事实上不是这样的,参考,⾥⾯的结论是:
The browser does not respect HTTP caching rules when you click the back button.
2.2 bfcache和page cache
bfcache和page cache是webkit和firefox有⼀项优化技术。可参考:
1.
2. 和
这⾥简单介绍⼀下:
对于⽀持bfcache/page cache的浏览器,“后退”不光意味着html/js/css/接⼝等动静态资源不会重新请求,连JS也不会重新执⾏。因为前⼀个页⾯没有被unload,最后离开时的状态和数据被完整地保留在内存中,发⽣后退时浏览器直接把“离开时”的页⾯状态展⽰给⽤户。
就好像,你在页⾯A,点击链接要在当前窗⼝打开页⾯B,这时浏览器在不卸载页⾯A的情况下去加载页⾯B。这时你看到的是页⾯B,那页⾯A呢?页⾯A只是被隐藏了,JS暂停执⾏(我们称之为pagehid
e)。如果⽤户点击“返回”,浏览器快速把页⾯B隐藏,并把页⾯A再显⽰出来,JS恢复执⾏(我们称之为页⾯B pagehide, 页⾯A pageshow)。
pageshow事件在页⾯全新加载并展现时也会触发,与从bfcache/page cache中加载并展⽰的区分依据是pageshow event的persisted属性。
实际观察中发现,⼀些移动端浏览器的pageshow event的persisted属性值⼀直是false,尽管页⾯看上去确实是从bfcache/page cache中加载展⽰。(另外⼀个理论上的point,页⾯绑定了unload事件时,不再会进⼊bfcache/page cache,⼀些移动端浏览器上观察来看实际上也不是这样的)。
可⾏的⽅案是:JS监听pagehide/pageshow来阻⽌页⾯进⼊bfcache/page cache,或者监测到页⾯从bfcache/page cache中加载展现时进⾏刷新。参考
代码⽰例如下:
1.
2. if(Q.ua.IOS){
3. Q.$(window).on("pagehide",function(){
4. var $body = $(document.body);
5. $body.children().remove(); // wait for this callback to finish executing
6. setTimeout(function() {
7. $body.append("<script type='text/javascript'>load(true);<\/script>");
8. });
9. });
10. }
11. //for android qq browser
12. Q.$(window).on('pageshow', function(evt){
13. setTimeout(function(){
14. if(evt.persisted){
15. load(true);
16. }
17. });
18. });
19.
3. 安卓webview cache的问题
安卓webview,包括安卓⾥⾯内嵌的QQ X5内核浏览器,都存在后退不会重新请求页⾯的问题,⽆论页⾯是否禁⽤缓存。上⾯的pageshow/pagehide⽅案也都失效。可⾏的⽅法,如下:
1. 给每个需要后退刷新的页⾯上加⼀个hidden input,存储页⾯在服务端的⽣成时间,作为页⾯的服务端版本号。
2. 并附加⼀段JS读取读取页⾯的版本号,同时也记录在浏览器/webview本地(cookie/localStorage/sessionStorage)进⾏存储,作为本地版本号。
3. JS检查页⾯的服务端版本号和本地存储中的版本号,如果服务端版本号⼤于本地存储中版本号,说明页⾯是从服务端重新⽣成的;否则页⾯就是本地缓存的,即发⽣了后退⾏为。
4. JS在监测到后退时,强制页⾯重新从服务端获取。
该⽅案的前提是浏览器在向server请求页⾯时,每次都⽤jsp重新⽣成html。需要页⾯本⾝有禁⽤缓存的配置。
⽅案的代码⽰例如下:
1.
2. <!-- 安卓webview 后退强制刷新解决⽅案 START -->
3. <jsp:useBean id="now" class="java.util.Date" />
4. <input type="hidden" id="SERVER_TIME" value="${Time()}"/>
5. <script>
恢复历史浏览记录6. //每次webview重新打开H5⾸页,就把server time记录本地存储
7. var SERVER_TIME = ElementById("SERVER_TIME");
8. var REMOTE_VER = SERVER_TIME && SERVER_TIME.value;
9. if(REMOTE_VER){
10. var LOCAL_VER = sessionStorage && sessionStorage.PAGEVERSION;
11. if(LOCAL_VER && parseInt(LOCAL_VER) >= parseInt(REMOTE_VER)){
12. //说明html是从本地缓存中读取的
13. load(true);
14. }else{
15. //说明html是从server端重新⽣成的,更新LOCAL_VER
16. sessionStorage.PAGEVERSION = REMOTE_VER;
17. }
18. }
19. </script>
20. <!-- 安卓webview 后退强制刷新解决⽅案 END -->
以上代码应该放在body中,最好是作为body的第⼀个⼦元素,这样后退发⽣时可以第⼀时间进⾏后退⾏为检测,避免⽤户看到页⾯呈现,然后页⾯⼜重新刷新,中间闪现“空⽩”。
以上是安卓webview的后退刷新⽅案,对于安卓内嵌浏览器,也适⽤的。
三、总结
1. PC浏览器,设置禁⽤页⾯缓存header即可实现后退刷新
2. ⽀持bfcache/page cache的移动端浏览器,JS监听pageshow/pagehide,在检测到后退时强制刷新
3. 在前2个⽅案都不work的情况下,可以在HTML中写⼊服务端页⾯⽣成版本号,与本地存储中的版本号对⽐判断是否发⽣了后退并使⽤缓存中的页⾯
四、花絮
1. 后退时表单控件⽤户输⼊内容
后退时,有些浏览器还会把表单控件中⽤户输⼊内容给记录并恢复。可以通过给form或者input添加autocomplete="off"属性解决。如果不work,参考
1,form中没有input[type=password],autocomplete="off"将起作⽤
2,去掉form,设置input[type=text]的autocomplete也起作⽤
2. 从webview的⾓度看缓存的问题
关于安卓webview缓存的问题,⾥⾯有描述:
8、浏览器缓存静态页⾯,如果这个静态页⾯重新⽣成了之后,还是原来的地址,⽤户看到的是不是就还是⽼页⾯了
回答:浏览器对页⾯这种主资源没有做缓存。只对⼦资源,图⽚,JS,CSS等做了缓存,⽽且缓存都是有⼀个新旧对⽐的,这个主要是服务器来控制哪些可以缓存哪些不能缓存。会有304 这种判断。
从:
2、缓存模式(5种)
LOAD_CACHE_ONLY: 不使⽤⽹络,只读取本地缓存数据
LOAD_DEFAULT: 根据cache-control决定是否从⽹络上取数据。
LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作⽤同LOAD_DEFAULT模式
LOAD_NO_CACHE: 不使⽤缓存,只从⽹络获取数据.
LOAD_CACHE_ELSE_NETWORK,只要本地有,⽆论是否过期,或者no-cache,都使⽤缓存中的数据。
如:www.taobao的cache-control为no-cache,在模式LOAD_DEFAULT下,⽆论如何都会从⽹络上取数据,如果没有⽹
络,就会出现错误页⾯;在LOAD_CACHE_ELSE_NETWORK模式下,⽆论是否有⽹络,只要本地有缓存,都使⽤缓存。本地没有缓存时才从⽹络上获取。
www.360的cache-control为max-age=60,在两种模式下都使⽤本地缓存数据。
总结:根据以上两种模式,建议缓存策略为,判断是否有⽹络,有的话,使⽤LOAD_DEFAULT,⽆⽹络时,使⽤
LOAD_CACHE_ELSE_NETWORK。
看上去修改webview的配置就能轻松愉快地解决问题的。只是迫于app版本发布缓慢的现实,才从JS和⽹页的层⾯来解决这个问题。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论