离线web-ApplicationCache
https://www.html5rocks.com/en/tutorials/appcache/beginner/
http://diveintohtml5.info/offline.html#fallback
介绍
离线web程序变得非常重要。所有的浏览器都支持长期缓存页面资源,但浏览器会为了腾出缓存空间而清除掉一些之前的缓存。h5中通过ApplicationCache(后面简称appCache)来解决这个问题。appCache可以允许开发者指定某些文件应该保存到本地,用于离线访问。它有三个优点:
- 让用户可以离线访问页面
- 直接从本地硬盘而不是网络获取资源,速度更快
- 当我们的服务器因为某些原因宕机时,用户依然可以访问我们的页面,这使我们的服务更加健壮
清单文件
这是一个文本文件,用于列出浏览器应该缓存的资源,来离线访问。通过在页面中引用这个清单文件,来启用离线缓存:
- <html manifest="example.appcache">
- ...
- </html>
每个需要被离线访问的页面都需要像上面那样引用清单文件,如果html标签没有manifest属性,则浏览器不会缓存这个页面。页面地址后有不同参数,会被AppCache认为是不同的页面,会被分开缓存,所以AppCache最好用于无参数(或固定参数,唯一的)地址的页面。
通过在chrome中访问chrome://appcache-internals/来管理被appCache缓存的页面。
manifest所指向的清单文件地址必须与当前页面同源。清单文件可以使任意后缀,但被服务器输出时必须指定其媒体类型(content-type)为text/cache-manifest。这个规定在较新版本的chrome、ff、Safari被废弃了,但是对于IE11以及其他老浏览器,仍然有这个规定。
一个简单的清单文件结构如下:
- CACHE MANIFEST
- index.html
- stylesheet.css
- images/logo.png
- scripts/main.js
- http://cdn.example.com/scripts/main.js
以上清单文件会缓存4个文件,需要注意的是:
- CACHE MANIFEST必须在第一行
- 被缓存的文件可以来自其他域(CDN)
- 一些浏览器对于缓存的数量有限制,在chrome中,appCache使用共享缓存(与其他离线API共享这片区域)
- 如果manifest指向的地址返回了404或410,则缓存会被删除
- 如果清单文件或者指定被缓存的资源下载失败,则缓存会更新失败,浏览器会使用旧的缓存。
浏览器会下载所有清单文件中指定的文件,而不管这些文件有没有在当前页面中有用到。如果有多个页面,则每个页面都需要指向相同的、代表了整个应用程序的清单文件。如果app只有一个html,则保证这个html引用了清单文件即可,而不需要把这个html添加到清单文件中,因为浏览器会认为这个页面属于app的一部分,而自动对这个html进行缓存(但还是推荐显式地加上),利用html文件会被自动缓存的这特点可以实现一个“懒缓存”,H5说明书提供了一个例子:
- CACHE MANIFEST
- FALLBACK:
- / /offline.html
- NETWORK:
- *
对于一个有成千上万个页面的站点,我们不可能也不想去下载整个站点,但可以将其中一部分做成离线的,但怎么决定哪些页面应该被缓存呢?假设这个巨大的站点支持离线,我们访问的每个页面都会被下载和缓存。这就是以上清单文件所能做的事情。假设这个巨大的站点中每个html都引用了上面的清单文件,当访问里面的html时,浏览器会认为这个html是一个离线app的一部分,如果浏览器没有下载过这个清单文件,则会创建一个新的离线缓存,然后下载清单文件中所有的资源并且缓存到刚刚创建的离线缓存中,然后把当前页面也添加到缓存中。这样一来,任何被访问过的页面都会添加进同一个离线缓存中。换个说法就是所有html都引用同一个清单文件,但CACHE那一块不需要列举html文件,因为会自动缓存,但里面的资源需要列举,因为里面的资源不会被自动缓存。
以上的fallback仅仅只有一行,第一个斜杠是一个url匹配模式,会匹配所有页面。当我们离线访问一个页面时,这个页面在缓存中,则显示缓存的页面;如果不在缓存中,则显示错误信息,并且显示/offline.html这个页面。
下面来看一个更复杂的例子( # 用于注释一行):
- CACHE MANIFEST
- # 2010-06-18:v2
- # Explicitly cached 'master entries'.
- CACHE:
- /favicon.ico
- index.html
- stylesheet.css
- images/logo.png
- scripts/main.js
- # Resources that require the user to be online.
- NETWORK:
- *
- # static.html will be served if main.py is inaccessible
- # offline.jpg will be served in place of all images in images/large/
- # offline.html will be served in place of all other .html files
- FALLBACK:
- /main.py /static.html
- images/large/ images/offline.jpg
事件流
当我们访问了一个页面引用了清单文件,会有一系列的事件在window.applicationCache对象上触发(事件监听器要设置在这个对象上)。
当浏览器发现html标签上有一个manifest属性时,首先会触发一个checking事件,这个事件总会触发,不管浏览器之前有没有遇到过这个清单文件(再次访问当前页面或者访问过其他页面指向了相同的清单文件)
假如浏览器第一次遇到这个清单文件:
- 触发一个downloading事件,然后下载里面的资源
- 正在下载的时候,会持续触发progress事件,可以知道文件的下载情况
- 当所有文件都下载完,会触发一个cached事件,这就意味着资源已经缓存完成,可以离线了。
如果浏览器之前就遇到过这个清单文件,则可能有文件已经缓存好了。那问题就是自上次检查完后,这个清单文件本身有没有发生变化?
- 如果没有发生变化,则触发一个noupdate事件
- 如果有,则触发一个downloading事件,然后重新下载里面所有的资源。正在下载时,持续触发progress事件。当全部资源已经重新下载完毕了,触发updateready事件,这意味着一个全新版本的资源已经缓存完成,可以离线了。但新的资源并没有被应用上去,需要调用swapCache函数,然后手动刷新页面才会生效。
如果以上更新流程出现问题,则触发一个error事件并且停止更新。可能出错的地方:
- 可以联网时,以上的协商检查返回了404或410(清单文件找不到或者无权访问清单文件)
- 重新绑定关联时,新的清单下载失败
- 当更新过程中,服务器的清单文件发生变化
- 重新下载所有资源时,资源下载失败。
appCache暴露了一些事件让我们去观察appCache的状态:
- function handleCacheEvent(e) {
- //...
- }
- function handleCacheError(e) {
- alert('Error: Cache failed to update!');
- };
- // Fired after the first cache of the manifest.
- appCache.addEventListener('cached', handleCacheEvent, false);
- // Checking for an update. Always the first event fired in the sequence.
- appCache.addEventListener('checking', handleCacheEvent, false);
- // An update was found. The browser is fetching resources.
- appCache.addEventListener('downloading', handleCacheEvent, false);
- // The manifest returns 404 or 410, the download failed,
- // or the manifest changed while the download was in progress.
- appCache.addEventListener('error', handleCacheError, false);
- // Fired after the first download of the manifest.
- appCache.addEventListener('noupdate', handleCacheEvent, false);
- // Fired if the manifest file returns a 404 or 410.
- // This results in the application cache being deleted.
- appCache.addEventListener('obsolete', handleCacheEvent, false);
- // Fired for each resource listed in the manifest as it is being fetched.
- appCache.addEventListener('progress', handleCacheEvent, false);
- // Fired when the manifest resources have been newly redownloaded.
- appCache.addEventListener('updateready', handleCacheEvent, false);
调试的艺术
以上提到清单文件中的资源下载失败时,会触发一个error事件,但我们无法得知具体的错误是什么,这使离线应用调试起来让人沮丧。
浏览器到底是怎么检查被缓存的清单文件是否发生了修改呢?分成三步:
- 通过http协议,类似于其他http资源,浏览器会检查缓存的清单文件是否过期,在http响应头中包含了文件的元信息,但不是强缓存(Expires、Cache-Control)。
- 假如缓存的清单文件过期了(通过http头检查),浏览器会问服务器是否有一个新的版本可以下载,是则下载。为了实现这一点浏览器会发起一个http请求,里面包含了缓存的清单文件的修改时间,这个时间也就是上次清单文件下载的时间。如果web服务器认为这个文件没有被修改,则返回304.
- 如果web服务器认为这个文件发生修改了,则返回200,浏览器从这个响应中读取新的清单文件内容.
总结以上三个步骤就是缓存的清单文件是协商缓存。
假如发布了一个清单文件,10分钟后,往上面加了一行,再次发布。会发生这样的事情:刷新页面,结果什么也没发生。这是因为浏览器始终认为这个缓存没有发生变化,这可能是因为服务器设置了强缓存(Cache-Control)。所以有一件事情绝对要去做:取消(不设置)清单文件的强缓存。
只要清单文件没有发生变化,则即使里面的资源发生了变化,浏览器也不会发现。如一个css文件已经重新发布了,但运行起来没有任何变化,因为清单文件没有发生变化。解决办法就是:只要离线资源发生了变化,就去修改清单文件,简单随便改里面的一个字符即可。最方便就是通过 # 注释往里面标记一个版本号,修改版本号即可
- CACHE MANIFEST
- # rev 43
- clock.js
- clock.css
html标签可以以相对路径引用清单文件,清单文件内以相对于清单文件的路径来引用其他资源。
缓存更新
可以理解为清单文件也会被缓存,是协商缓存。表现为
- 第一次访问页面时,下载并且把清单内容缓存到本地,浏览器再将这个清单文件的地址与页面的地址进行关联(绑定关联)
- 再次访问时,浏览器根据页面地址找到对应关联的清单地址(获取关联),然后检查这个地址是否与当前页面的清单地址一致(校验关联),不一致则重新下载新的清单(重新绑定关联)。最后根据这个地址检查本地的清单内容是否发生变化(协商检查),是则进入更新流程(重新下载所有资源),无法联网则使用缓存的清单文件。
仅当清单文件内容发生变化时,才会触发浏览器去更新缓存。假如清单文件无变化,而仅仅修改了图片或者修改了一个JS函数,这些变化不会被缓存(不会被浏览器发现)。
在一次更新中,清单文件会被检查(协商检查清单文件有没有发生变化)两次,开始的时候一次和所有缓存文件都更新完成后再检查一次。如果清单文件在更新的时候被修改,即两次检查的结果不一致,则更新失败。
就算缓存被更新了,浏览器不会使用这些缓存,直到页面被刷新,因为缓存的更新发生在页面重新加载之后(加载的是当前版本的缓存,而不是新版版的缓存)。
当清单文件或者清单文件内指定的资源下载失败时,整个更新过程就会失败。浏览器会继续使用旧的缓存
缓存会一直有效,直到:
- 用户手动清空浏览器缓存
- 清单文件被修改。更新清单文件中的缓存列表,并不意味着浏览器会对缓存进行更新,清单文件本身必须要修改
缓存的状态
通过window.applicationCache可以以编程方式来访问浏览器中的appCache,对象的status属性用于检查当前缓存的状态
- var appCache = window.applicationCache;
- switch (appCache.status) {
- case appCache.UNCACHED: // UNCACHED == 0
- return 'UNCACHED';
- break;
- case appCache.IDLE: // IDLE == 1
- return 'IDLE';
- break;
- case appCache.CHECKING: // CHECKING == 2
- return 'CHECKING';
- break;
- case appCache.DOWNLOADING: // DOWNLOADING == 3
- return 'DOWNLOADING';
- break;
- case appCache.UPDATEREADY: // UPDATEREADY == 4
- return 'UPDATEREADY';
- break;
- case appCache.OBSOLETE: // OBSOLETE == 5
- return 'OBSOLETE';
- break;
- default:
- return 'UKNOWN CACHE STATUS';
- break;
- };
编程方式来更新缓存首先要调用update方法,这会尝试去更新缓存(但需要清单文件已经发生变化),当status进行UPDATEREADY时,可以使用swapCache函数来将新的缓存替换旧的缓存:
- var appCache = window.applicationCache;
- appCache.update(); // Attempt to update the user's cache.
- ...
- if (appCache.status == window.applicationCache.UPDATEREADY) {
- appCache.swapCache(); // The fetch was successful, swap in the new cache.
- }
以上缓存替换完成后,需要刷新页面,新替换进去的缓存才会生效,可以这么做:
- window.addEventListener('load', function(e) {
- window.applicationCache.addEventListener('updateready', function(e) {
- if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
- // Browser downloaded a new app cache.
- if (confirm('A new version of this site is available. Load it?')) {
- window.location.reload();
- }
- } else {
- // Manifest didn't changed. Nothing new to server.
- }
- }, false);
- }, false);
清单文件的结构
清单文件可以分成三个部分(书写顺序无影响,一个文件中可以同一个部分出现多次):CACHE、NETWORK和FALLBACK。
CACHE:
这是默认的部分,里面列出的文件(或者直接在CACHE MANIFEST底下的文件)会被下载后第一时间进行缓存
NETWORK:
里面列出的文件如果不在缓存中,则从网络上下载;否则如果某些资源地址不在这里,就算网络通畅,也不会去下载文件。所以一般指定一个 * 即可。
FALLBACK:
这是可选的,里面第一个url指定为资源,第二个url为当资源无法访问时就访问这个url。两个url必须与当前页面同源。这里指定资源url时,可以指定一个前缀来批量地指定多个url的访问失败时的反馈页面。
以下例子定义了一个全资源的错误页面(offline.html),当无网络时,用户访问站点根目录或者访问其他所有资源时,则这个页面就会显示出来
- CACHE MANIFEST
- # 2010-06-18:v3
- # Explicitly cached entries
- index.html
- css/style.css
- # offline.html will be displayed if the user is offline
- FALLBACK:
- / /offline.html
- # All other resources (e.g. sites) require the user to be online.
- NETWORK:
- *
- # Additional resources to cache
- CACHE:
- images/logo1.png
- images/logo2.png
- images/logo3.png
appCache是一个逗比(douchebag)
http://alistapart.com/article/application-cache-is-a-douchebag
里面形容appCache就是一颗洋葱,当你一层一层剥开,你会流泪的。
1.文件总是来自于appCache,即使网络可用
当缓存更新完成,updateready事件会被触发,但这时候我们不能刷新页面,因为用户可能正在进行交互。这不是什么大问题,因为旧的缓存也可能足够好用了,如果实在需要新的缓存,则弹框提醒用户是否要刷新界面来启用新的缓存,是则刷新页面。
2.appCache缓存仅当清单文件本身发生变化时才更新
http协议是有缓存的,我们可以为每个文件定义缓存的方式(总是不缓存、协商缓存和强缓存)。假如清单文件中有50个html,每次我们访问里面的页面,浏览器就要创建50个请求来检查他们是否需要更新。
浏览器仅当清单文件本身发生变化了,才去检查内部的资源是否需要更新,只要清单文件的一个字节发生变化,就会触发检查更新。
这对一直不会变化的静态资源来说非常好。当资源的路径发生变化,就意味着清单文件的内容发生变化。最简单的改变清单文件的方式是修改里面的注释(如# v1 改成 # v2),这可以通过构建工具来完成,类似于为每个文件都创建一个唯一标识符ETAG,然后通过注释写到清单文件中,这样一来,文件内容发生变化,则清单文件发生变化
然而,清单文件被更新,不意味着里面的资源会更新。
3.appCache属于额外的缓存,不能替换http缓存
大部分appCache被更新时,浏览器会发出http请求。这符合一般的缓存流程:假如资源已经被强缓存下来了,则appCache更新,浏览器发出请求,发现这个强缓存没有到期,则不会去访问服务器了,更新就这样结束了。这是可以的,因为当清单文件发生变化时,我们降低了向服务器请求的次数。
所以所有资源都要正确地设置好http缓存头,这比清单文件更加重要
4.永远不要去强缓存清单文件
强缓存一个清单文件的话,页面每次获取到的清单文件总是旧的,根据清单文件获取的缓存也是旧的。
5.一个缓存页上不会加载没被缓存的资源
当缓存了index.html,却没有缓存cat.jpg,则这个图片不会显示在index.html上,即使网络可用。解决这个问题可以将NETWORK版块设置为一个 * 。这意味着浏览器在展示一个缓存页时,没有被缓存的资源就从网络中获取。
配合iframe使用
案例1:
在线访问一个普通页面A(没有引用清单文件),内嵌了一个隐藏的iframe,指向另一个页面B(引用了清单文件),可以离线访问B
案例2:
在案例1的基础上,B中的清单文件配置了Fallback,然后离线访问A,B中的fallback生效了。这是因为B在上次已经被缓存,然后离线访问A,A中通过一个隐藏的iframe访问了B,就相当于离线访问了B,B的fallback就生效了。
fallback:
- CACHE MANIFEST
- FALLBACK:
- / fallback.html
- /assets/imgs/avatars/ assets/imgs/avatars/default-v1.png
以上当请求失败,就显示fallback.html。除非前缀为/assert/ims/avatars/,就返回一个默认的图片。
使用localstorage来动态离线管理
我们不可能缓存所有东西,因为内容太多了。我们希望用户自己去选择可离线访问的资源,保存到localstorage中。实现如下:
1.首先以地址映射页面内容,地址映射标题(标题都保存到index指向的json字符串中)
- // Get the page content
- var pageHtml = document.body.innerHTML;
- var urlPath = location.pathName;
- // Save it in local storage
- localStorage.setItem( urlPath, pageHtml );
- // Get our index
- var index = JSON.parse( localStorage.getItem( 'index' ) );
- // Set the title and save it
- index[ urlPath ] = document.title;
- localStorage.setItem( 'index', JSON.stringify( index ) );
2.通过清单指定一个fallback页面,页面中读取localstorage:
- var pageHtml = localStorage.getItem( urlPath );
- if ( !pageHtml ) {
- document.body.innerHTML = 'Page not available';
- }else {
- document.body.innerHTML = localStorage.getItem( urlPath );
- document.title = localStorage.getItem( 'index' )[ urlPath ];
- }
6.再见,条件下载
对于响应式下载(媒体查询加载不同的图片,如source标签),浏览器会根据清单文件的内容来下载,而不管其他地方是否有媒体查询,这会导致浏览器下载多套不同的资源。同样的道理也会作用于字体
7.我们不知道什么时候会显示fallback页面
根据说明(spec),当源请求被重定向到其他域、遇到4xx或5xx状态码、或网络错误时会显示fallback页面(显示的方式类似于服务器端跳转,即浏览器地址不变)。但是当用户没有网络的时候,fallback也没办法显示了。
8.重定向到其他域也会被认为是个错误
如果页面访问一个url,服务器对这个url的请求进行重定向,这会马上显示一个对应url的fallback页面,因为appCache不允许。
这个规则是好的,因为假设我们连了wifi上网,访问网站则重定向到了这个wifi的付费页面,相对于这点,显示fallback页面是好的。把这些需要重定向的url添加到NETWORK是无效的,但使用JS或者meta-redirect是可以的
其他阅读
- Gmail for mobile HTML5 series: using appcache to launch offline - part 1
- Gmail for mobile HTML5 series: using appcache to launch offline - part 2
- Gmail for mobile HTML5 series: using appcache to launch offline - part 3
- Debugging HTML5 offline application cache
- an HTML5 offline image editor and uploader application
- 20 Things I Learned About Browsers and the Web, an advanced demo that uses the application cache and other HTML5techniques
- https://github.com/jakearchibald/appcache-demo
离线web-ApplicationCache的更多相关文章
- HTML5离线Web应用实战:五步创建成功
[IT168 技术]HTML5近十年来发展得如火如荼,在HTML 5平台上,视频,音频,图象,动画,以及同电脑的交互都被标准化.HTML功能越来越丰富,支持图片上传拖拽.支持localstorage. ...
- HTML5应用程序缓存实现离线Web网页或应用
HTML5应用程序缓存和浏览器缓存的区别.(有些)浏览器会主动保存自己的缓存文件以加快网站加载速度.但是要实现浏览器缓存必须要满足一个前提,那就是网络必须要保持连接.如果网络没有连接,即使浏览器启用了 ...
- web离线应用--applicationCache
applicationCache是html5新增的一个离线应用功能 离线浏览: 用户可以在离线状态下浏览网站内容. 更快的速度: 因为数据被存储在本地,所以速度会更快. 减轻服务器的负载: 浏览器只会 ...
- 文档通信(跨域-不跨域)、时时通信(websocket)、离线存储(applicationCache)、开启多线程(web worker)
一.文档间的通信 postMessage对象 //不跨域 1.iframe:obj.contentWindow [iframe中的window对象] iframe拿到父级页面的window: pare ...
- 原生js--应用程序存储和离线web应用
1.应用程序缓存和其它存储方式的区别: a.不像localStorage和sessionStorage那样只存储web应用程序的数据,它将应用程序自身存储起来. b.不像浏览器缓存一样会过期或者被用户 ...
- [HTML5]构建离线web应用程序
1.检查浏览器是否支持缓存 if(window.applicationCache){ //TODO } 2.在html中加入manifest特性 <html manifest="app ...
- HTML5权威指南--Web Storage,本地数据库,本地缓存API,Web Sockets API,Geolocation API(简要学习笔记二)
1.Web Storage HTML5除了Canvas元素之外,还有一个非常重要的功能那就是客户端本地保存数据的Web Storage功能. 以前都是用cookies保存用户名等简单信息. 但是c ...
- Html5离线应用程序
最近,整理了一下关于 H5离线应用缓存的知识,今天在家休息,和大家分享一下,希望对大的学习和工作,能有所帮助. HTML5的离线web应用允许我们在脱机时与网站进行交互.这在提高网站的访问速度和制作一 ...
- HTML5实际和离线应用分析
当前离线Web申请书,即,该装置不能访问因特网时的应用的执行.HTML5离线应用重点,主要开发人员希望.步骤离线应用开发有:首先我们应该知道设备是否可以连接;然后,它也应该可以访问某些资源(像.CSS ...
- HTML5离线应用与客户端存储
序言 本篇文章会详细介绍使用HTML5开发离线应用的步骤,以及本地存储与cookie的一些异同,最后利用上面所学例子来实现一个购物车场景. 使用HTML5离线存储的基本过程如下: 离线检测:首先要对设 ...
随机推荐
- bzoj2825:[AHOI2012]收集资源
传送门 看到数据范围这么小,就没想过暴力的办法么 考虑肯定是从近走到远,所以走的点之间一定没有其他的点,所以我们就可以暴力的建图,然后暴力的去dfs就好了 代码: #include<cstdio ...
- TopCoder9915(期望dp)
1.还是逆向. 2.状态是还剩红i黑j张时的期望,这样从0,0往R,B推.注意因为是逆着的,所以到了某一步发现期望为负时直接f[i][j]归零,意义是这之后(在递推中算是这之前)的都不摸了,到这就停( ...
- SSE练习:单精度浮点数组求和
SSE(Streaming SIMD Extensions)指令是一种SIMD 指令, Intrinsics函数则是对SSE指令的函数封装,利用C语言形式来调用SIMD指令集,大大提高了易读性和可维护 ...
- js AES对称加密 16进制和base64格式
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- phpmyadmin解决“高级功能尚未完全设置,部分功能未激活”
首先在点击主页中的导入, 在“从计算机中上传:”选择/usr/share/doc/phpmyadmin/examples的“create_tables.sql.gz”文件 点击执行 但是我的电脑上还是 ...
- How to detect the presence of the Visual C++ 2010 redistributable package
Question: I have seen your previous blog posts that describe how to detect the presence of the Visua ...
- Java编程基础-面向对象(下)
一.抽象类 1.引入:当定义一个类时,常常需要定义一些方法来描述该类的行为特征,但有时这些方法的实现方式是无法确定的.Java允许在定义方法时不写方法体,不包含方法体的方法为抽象方法,抽象方法必须使用 ...
- CF1025C Plasticine zebra
思路: 不要被骗了,这个操作实际上tm是在循环移位. 实现: #include <bits/stdc++.h> using namespace std; int main() { stri ...
- 浅析 var that = this;
在阅读别人的代码时,发现别人写的代码中有这么一句:var that = this;,这代表什么意思呢?经过一番查阅,才明白是这么回事. 在JavaScript中,this代表的是当前对象. var t ...
- Android GreenDao 深查询 n:m 的关系
在我的应用程序这样设计的关系:和我想选择至少一个用户作为一个朋友的所有聊天. 基本上,我想要执行以下查询:\ SELECT c.* FROM CHAT c, USER u, UserChats uc ...