使用ApplicationCache接口实现离线缓存
推荐:html5 application cache遇到的严重问题
在我们的3G版网站的项目中使用了html5 application cache,将大部分图片资源、js、css等静态资源放在manifest文件中,需要了解的朋友可以参考下
简介
离线访问对基于网络的应用而言越来越重要。虽然所有浏览器都有缓存机制,但它们并不可靠,也不一定总能起到预期的作用。HTML5 使用 ApplicationCache 接口解决了由离线带来的部分难题。
使用缓存接口可为您的应用带来以下三个优势:
速度 – 缓存资源为本地资源,因此加载速度较快。
服务器负载更少 – 浏览器只会从发生了更改的服务器下载资源。
应用缓存(又称 AppCache)可让开发人员指定浏览器应缓存哪些文件以供离线用户访问。即使用户在离线状态下按了刷新按钮,您的应用也会正常加载和运行。
缓存清单文件
缓存清单文件是个简单的文本文件,其中列出了浏览器应缓存以供离线访问的资源。
引用清单文件
要启用某个应用的应用缓存,请在文档的html 标记中添加manifest 属性:
1
2
3
4
5
|
www.mb5u.com <html manifest= "example.appcache" >
... </html> |
您应在要缓存的网络应用的每个页面上都添加 manifest 属性。如果网页不包含 manifest 属性,浏览器就不会缓存该网页(除非清单文件中明确列出了该属性)。这就意味着用户浏览的每个包含manifest 的网页都会隐式添加到应用缓存。因此,您无需在清单中列出每个网页。
manifest 属性可指向绝对网址或相对路径,但绝对网址必须与相应的网络应用同源。清单文件可使用任何文件扩展名,但必须以正确的 MIME 类型提供(参见下文)。
1
2
3
|
清单文件必须以 text/cache-manifest MIME 类型提供。您可能需要向网络服务器或 .htaccess 配置添加自定义文件类型。
例如,要在 Apache 中提供此 MIME 类型,请在您的配置文件中添加下面一行内容:
AddType text/cache-manifest .appcache要在 Google App Engine 的 app.yaml 文件中提供此 MIME 类型,则添加以下内容:
- url: /mystaticdir/(.*\.appcache)
static_files: mystaticdir/\1
mime_type: text/cache-manifest
upload: mystaticdir/(.*\.appcache)清单文件结构
简单的清单格式如下:
CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js该示例将在指定此清单文件的网页上缓存四个文件。
您需要注意以下几点:
CACHE MANIFEST 字符串应在第一行,且必不可少。
网站的缓存数据量不得超过 5 MB。不过,如果您要编写的是针对 Chrome 网上应用店的应用,可使用 unlimitedStorage 取消该限制。
如果清单文件或其中指定的资源无法下载,就无法进行整个缓存更新进程。在这种情况下,浏览器将继续使用原应用缓存。
我们再来看看更复杂的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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: login.php /myapi http: //api.twitter.com
# 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 |
清单可包括以下三个不同部分:CACHE、NETWORK 和 FALLBACK。
CACHE:
这是条目的默认部分。系统会在首次下载此标头下列出的文件(或紧跟在 CACHE MANIFEST 后的文件)后显式缓存这些文件。
NETWORK:
此部分下列出的文件是需要连接到服务器的白名单资源。无论用户是否处于离线状态,对这些资源的所有请求都会绕过缓存。可使用通配符。
FALLBACK:
此部分是可选的,用于指定无法访问资源时的后备网页。其中第一个 URI 代表资源,第二个代表后备网页。两个 URI 必须相关,并且必须与清单文件同源。可使用通配符。
请注意:这些部分可按任意顺序排列,且每个部分均可在同一清单中重复出现。
以下清单定义了用户尝试离线访问网站的根时显示的“综合性”网页 (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请注意:系统会自动缓存引用清单文件的 HTML 文件。因此您无需将其添加到清单中,但我们建议您这样做。
请注意:HTTP 缓存标头以及对通过 SSL 提供的网页设置的缓存限制将被替换为缓存清单。因此,通过 https 提供的网页可实现离线运行。
更新缓存
应用在离线后将保持缓存状态,除非发生以下某种情况:
用户清除了浏览器对您网站的数据存储。
清单文件经过修改。请注意:更新清单中列出的某个文件并不意味着浏览器会重新缓存该资源。清单文件本身必须进行更改。
应用缓存通过编程方式进行更新。
缓存状态
window.applicationCache 对象是对浏览器的应用缓存的编程访问方式。其 status 属性可用于查看缓存的当前状态:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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 ;
}; |
要以编程方式更新缓存,请先调用 applicationCache.update()。此操作将尝试更新用户的缓存(前提是已更改清单文件)。最后,当 applicationCache.status 处于 UPDATEREADY 状态时,调用applicationCache.swapCache() 即可将原缓存换成新缓存。
1
2
3
4
5
6
|
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.
} |
请注意:以这种方式使用 update() 和 swapCache() 不会向用户提供更新的资源。此流程只是让浏览器检查是否有新的清单、下载指定的更新内容以及重新填充应用缓存。因此,还需要对网页进行两次重新加载才能向用户提供新的内容,其中第一次是获得新的应用缓存,第二次是刷新网页内容。
好消息是,您可以避免重新加载两次的麻烦。要使用户更新到最新版网站,可设置监听器,以监听网页加载时的 updateready 事件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// Check if a new cache is available on page load. window.addEventListener( 'load' , function(e) {
window.applicationCache.addEventListener( 'updateready' , function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// Browser downloaded a new app cache. // Swap it in and reload the page to get the new hotness. window.applicationCache.swapCache(); 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 );
|
APPCACHE 事件
正如您所预期的那样,附加事件会用于监听缓存的状态。浏览器会对下载进度、应用缓存更新和错误状态等情况触发相应事件。以下代码段为每种缓存事件类型设置了事件监听器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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 );
|
如果清单文件或其中指定的资源无法下载,整个更新都将失败。在这种情况下,浏览器将继续使用原应用缓存
应用程序缓存 API(简称“AppCache”)
使用 AppCache 在本地保存资源,你可以通过减少主机服务器的请求数量来改善网页性能;此外,你还可以脱机访问缓存的资源。
若要在本地缓存资源,请执行下列操作:
- 1. 创建一个清单文件,以指定你要保存的资源。
- 2. 在要使用缓存资源的每个网页中引用该清单文件。
H5的一个重要特性就是离线存储,所谓的离线存储就是将一些资源文件保存在本地,这样后续的页面重新加载将使用本地资源文件,在离线情况下可以继续访问web应用,同时通过一定的手法(更新相关文件或者使用相关API),可以更新、删除离线存储等操作;
H5的离线存储使用一个manifest文件来标明哪些文件是需要被存储的,使用如 <html manifest='offline.manifest'> 来引入一个manifest文件,这个文件的路径可以是相对的,也可以是绝对的,如果你的web应用很多,而且希望能集中管理manifest文件,那么静态文件服务器是个不错的选择。
对于manifest文件,要求:文件的mime-type必须是 text/cache-manifest类型。
如果你是JAVA工程,在你的web.xml中配置请求后缀为manifest的格式:
-
123456
[html] view plaincopy
<mime-mapping>
<extension>manifest</extension>
<mime-type>text/cache-manifest</mime-type>
</mime-mapping>
这样可以控制请求到的manifest文件格式为text/cache-manifest的。
创建清单文件
清单文件是一个针对网页所使用的资源指定缓存行为的文本文件,参见下面的示例。
CACHE MANIFEST CACHE:
# Defines resources to be cached.
script/library.js
css/stylesheet.css
images/figure1.png FALLBACK:
# Defines resources to be used if non-cached
# resources cannot be downloaded, for example
# when the browser is offline..
photos/ figure2.png NETWORK:
# Defines resources that will not be cached.
figure3.png
清单文件分为以下各节:
- 其中 CACHE 不是必须存在的,可以直接在 CACHE MANIFEST 行之下直接写需要缓存的文件,在这里指明的文件将被缓存到浏览器本地。
- 在NETWORK之下指明的文件,是强制必须通过网络资源获取的
- 在FALLBACK下指明的是一种失败的回调方案,比如上述index.php无法访问,那么就发出404.htm请求
清单文件可包含任意数量的这些节,并且可以重复;但新的节必须以节标题开头,且后面要加冒号,如上面的示例所示。如果未提供节标题,则假定使用 CACHE: 标题。下面的示例演示了一个简单的清单。
CACHE MANIFEST
script/library.js
css/stylesheet.css
images/figure1.png
此外,清单文件:
- 必须使用 8 位 Unicode 转换格式 (UTF-8) 字符编码。
- 必须接受文本/缓存清单 MIME 类型。
- 必须以“CACHE MANIFEST”行开始。
- 可以包含注释,但前面必须加英镑标记 (#)。
有关更多信息,请参阅缓存清单语法。
声明清单
要将清单与网页关联,需将清单文件的名称分配给 html 元素的 manifest 属性,如下面得示例所示。
<!doctype html>
<html manifest="appcache.manifest">
<head>
<title>A Web Page</title>
<script src="library.js"></script>
<link rel="stylesheet" href="stylesheet.css">
</head>
<body onload="doSomething();">
<p>Results go here: <span id="results">Unknown</span></p>
</body>
</html>
在此示例中,网页将“appcache.manifest”作为清单文件进行声明。清单声明可像任何其他文件引用一样被解释。由于此示例使用相对文件名称,所以该清单被假定位于与网页自身相同的目录中。
注意 清单中的文件引用被解释为清单文件的相对位置,而不是声明它的网页。
对清单来说,没有必要包含声明该清单的网页名称;声明清单的网页会被自动缓存。
这样几步就可以完成对离线存储的支持。接下来要思考的,是如何更新离线存储?
当用户本地再次联网的时候,本地的离线存储资源需要检查是否需要更新,这个更新过程,也是通过manifest的更新来控制的,更新了manifest文件,浏览器会自动的重新下载新的manifest文件并在下一次刷新页面的时候进行资源文件的重新请求(第三次刷新替换本地缓存为最新缓存),而且这个请求是全局性的,也就是所有在manifest缓存列表中的文件都会被请求一次,而不是单独请求某个特定修改过的资源文件,因为manifest是不知道哪个文件被修改过了的。
对于全局更新的担心是不必要的,因为对于没有更新过的资源文件,请求依旧是304响应,只有真正更新过的资源文件才是200.
所以控制离线存储的更新,需要2个步骤,一是更新资源文件,二是更新manifest文件,特别的,更新manifest文件是不需要修改什么特定内容的,只要是这个文件随意一处被修改,那么浏览器就会感知,对于我们的资源文件通常名称是固定的,比如xxx.css,更新内容不会带有文件名更新的情况下,需要更新manifest文件怎么操作呢?一个比较好的方式是更新任意一处# 开头的注释即可,其目的只是告诉浏览器这个manifest文件被更新过。
以上的这些内容,其更新操作都是浏览器自动完成的。同样的,W3C定义了离线存储的API规范:http://www.whatwg.org/specs/web-apps/current-work/#applicationcache
ApplicationCache 对象
Internet Explorer 10 还支持 ApplicationCache 对象,该对象提供可用来管理应用程序缓存的属性。此外,你还可以定义显示缓存进程进度的事件处理程序。
使用 window 对象的 applicationCache 属性(或 worker 对象)以访问 ApplicationCache 对象,如下面的示例所示。
var sCacheStatus = "Not supported";
if (window.applicationCache)
{
var oAppCache = window.applicationCache;
switch ( oAppCache.status )
{
case oAppCache.UNCACHED :
sCacheStatus = "Not cached";
break;
case oAppCache.IDLE :
sCacheStatus = "Idle";
break;
case oAppCache.CHECKING :
sCacheStatus = "Checking";
break;
case oAppCache.DOWNLOADING :
sCacheStatus = "Downloading";
break;
case oAppCache.UPDATEREADY :
sCacheStatus = "Update ready";
break;
case oAppCache.OBSOLETE :
sCacheStatus = "Obsolete";
break;
default :
sCacheStatus = "Unexpected Status ( " +
oAppCache.status.toString() + ")";
break;
}
}
return sCacheStatus;
此示例使用 status 属性为当前通过网页所加载的文档确定应用程序缓存的状态。
ApplicationCache 对象支持两种控制缓存的方法。update 方法启动对更新的异步检查,类似于在首次加载网页时执行的操作。在重新加载该网页或调用 swapCache 方法之前,将会一直使用任何现有的缓存。要开始使用已更新的缓存,需要重新加载网页(手动或以编程方式)或调用 swapCache 方法。当重新加载网页时,因为缓存已更新,所以在重新加载或刷新网页之前不需要调用 swapCache 方法。
提供了如下API:
1
2
3
4
5
6
|
// 更新,一般来说更新下载是通过用户代理(如浏览器)自动完成的,但是这个方法适用于一些长期打开的页面,比如邮件系统,可能这个页面是长期打开的,而不会有刷新动作,所以这个就比较适合做自动更新下载 void update();
// 取消 void abort();
// 替换缓存内容 ,对于manifest文件的改变,通常是下一次的刷新才会触发下载更新,第三次刷新才会切换使用新的缓存文件,通过这个方法,可以强制将缓存替换 void swapCache();
|
注意 在重新加载网页(由用户手动或以编程方式使用 window.location 对象的 reload 方法)之前,网页不会使用已更新的缓存。
ApplicationCache 对象支持以下事件:
- 当清单已缓存时,将触发 cached 事件。
- 当检查是否有更新时,将触发 checking 事件。
- 当下载清单资源时,将触发 downloading 事件。
- 在下载清单资源期间,将触发 progress 事件。
- 当出现问题(如 HTML 404 或 410 响应代码)时,将触发 error 事件。当无法下载清单文件时,也会触发该事件。
- 当缓存有更新的版本可用时,将触发 updateready 事件。
- 请求更新时将会触发 noupdate 事件,但清单不会发生更改。
- 当现有的缓存被标记为已过时时,将会触发 obsolete 事件。
下面的示例演示如何为这些事件注册事件处理程序。
if (window.applicationCache) {
var appCache = window.applicationCache;
appCache.addEventListener('error', appCacheError, false);
appCache.addEventListener('checking', checkingEvent, false);
appCache.addEventListener('noupdate', noUpdateEvent, false);
appCache.addEventListener('downloading', downloadingEvent, false);
appCache.addEventListener('progress', progressEvent, false);
appCache.addEventListener('updateready', updateReadyEvent, false);
appCache.addEventListener('cached', cachedEvent, false);
}
最后说一个对于manifest比较特别的地方:对于某个文件a.htm,其中有 <html manifest='a.manifest'> ,那么离线存储中,会自动将a.htm加入到列表中,这意味着a.htm的再次刷新将从本地缓存中获取,这样的机制从官方得到的答复是“特别的设计”,而对我们来说,这种强加的特性在后续的开发过程中会有不少问题,比如:
1、如何计算PV UV,由于当前页面被强制加入manifest,那么PV 和UV的统计,成了一个难题,因为请求不再是发送到服务器;
2、对于某个使用manifest的文件,其带有的参数可能是随机性的统计参数,如sid=123sss, sid=234fff ,尤其是比如商品详情的id字段等,这样每个页面都自动加入到manifest中,将会带来很大的存储开销,而且是毫无意义的;
所以伴随而来的,是如何在现有的体系架构下进行数据统计的难题,一个常规的方案是进入离线存储页面后自动发出ajax请求,以告知服务器统计PV UV;
对于第二个问题,可能就比较棘手,但是将GET请求的方式改成POST的方式确实是个解决问题的方案。
慎用manifest
Html5已经被提过很久了,各种新属性出来,人们也都欢呼了一把,今天要讨论的是一个html5的新属性——manifest,可能有很多人已经用过该方法来优化自己的网站了。今天来分享下我们在使用manifest的时候遇到的问题。
1.概念
该特性为HTML5的新特性,能够让web程序指定可以缓存在本地的资源,以及缓存策略,使得程序能够在离线时仍然能够运行,也可以使程序在线时也可以从本地加载资源而不用重新从网络加载。
2.基本写法
要用manifest,就要了解其基本写法
通过在页面的html标签中中引入:
<html manifest=”/static/index/innovation/cache.manifest”>
Cache.manifest的文件写法:
- CACHE MANIFEST
- # v2012 1203v3
- http://m.baidu.com/static/index/i.gif
- http://m.baidu.com/static/hb/hot.gif
- http://m.baidu.com/static/index/logo_index2.png
- NETWORK:
- *
- http://m.baidu.com/su
- http://loc.map.baidu.com/wloc
- http://m.baidu.com/static/tj.gif
第一行一定要写上CACHE MANIFEST,然后注释写后面,以后通过修改注释,来实现manifest的更新。
A.路径写法不支持带端口,所以最好用相对路径吧,要不然线下测试机上开发,有端口不好调试。
B.*号通配符有时候好像并不好使。如果在某些设备上出现问题,可以排查一下是不是这个导致的。
C.#以后的内容虽然是注释,但是不能放到第一行,第一行必须是如上的大写字母。
1.离线后的页面
页面离线后,好处显而易见,页面可以秒开了(启动速度快)。你会感觉页面加载的速度快了很多。但是也有一些问题。
A.页面都离线了,如何更新?首先,你可以修改下manifest文件来更新这个页面,这个我就不介绍了,但是作为文章内容页面离线以后,就会存储在本地了,如果你是一篇文章的话,那么这个文章的内容页就被存下来了,你如果以相同的url去访问,不管你文章里面的数据更新没有,这个离线下来的页面都不会更新了(除非你更新manifest文件)。所以,所有的动态数据,你都得用ajax方式去获取,就像客户端一样,离线的页面应该是一个没有数据的空壳,然后通过ajax去拉去数据填补这个空壳。然后要注意的是,ajax的请求地址,要写到manifest的network中,要不然,你可以试试。
B.页面的参数如何携带?,还是刚才那个问题,文章页的壳如果缓存了,怎么样传数据过来标识这个特点页面呢?比如m.baidu.com/app?a=1&b=2,通常我们用一些参数来标记这个页面,通过参数来渲染页面内容,但是manifest对于上面的方式,会认为不同的参数表示不同的页面。如果你吧内容页做成一个无数据的空壳,这种传参的方式显然不行,好在不一样的hash页面,manifest会认为是同一个页面,比如m.baidu.com/app#detail-111111与m.baidu.com/app#detail-222222会认为和m.baidu.com/app是同一个缓存页面。这样我们就可以通过hash传值了,当然,你也可以通过其它方式传值,比如写入cookie,写入localstorage方式等等。
4.离线页面的更新
网站更新了,如果更新用户本地的离线页面呢?与很多文章中说的一样,先上线你的文件,然后修改一下页面中引入的cache.manifest文件即可,比如修改下注释,修改后,如果再访问页面,就会先去校验manifest时候有更新,如有更新,再次刷新页面的时候,页面就会更新了。
A.长尾问题,就像前面说到的一样,如果你的manifest文件更新了,你访问页面,需要刷新一次,更新的页面才能load进来,那么这样就有一个问题,如果你的后端数据,就是给js ajax接口的数据变化了,你对应的js也修改了。那么你修改manifest上线的时候,第一次开页面,页面就会bug了。再刷一次页面,就好了。那么,这个第一次访问的bug,是我们不想看到的。而且你不能知道用户什么时候第二次再来访问你的页面,所以你的页面一旦使用manifest离线,就像客户端一样,这样就出现了长尾问题。还好,manifest有一些js接口,可以来判断,load更新文件。
B.刷新页面
有了js的api,一切都好办了,我们可以干很多事情了。现在你可以用js来判断页面的状态了,可能你已经想到,判断状态后,可以刷新一下页面,那么,就算数据发生了修改,这种处理方式也是ok的,没错,这样确实解决了问题。你也可以在设置了manifest的页面中加入这些方法,然后修改下文件,看看会触发哪些事件,刷新页面的唯一问题就是页面会闪动一下,这点体验当然是不好的。因此,我们想到了loading页面。
C.Loading页面
制作一个loading页面,用来检测manifest文件有没有更新如果发现有更新,则更新资源,然后再跳到真实的首页。这个实现起来就比较容易了,在loading页面你可以加一些效果,比如加载效果等等,给用户一个预知。让我们看看manifest修改过和没有修改过各种状态的差别。保证你的manifest文件存在有效,如下:
- CACHE MANIFEST
- #test11123aaadfsaasdadffdsfaaaffd
- http://m.baidu.com/static/js/zepto-event-ajax.js
- testb.html
- NETWORK:
- *
Html文件如下:
- <!DOCTYPE html>
- <html manifest=”notes.manifest”>
- <head>
- <title>离线存储demo</title>
- <meta http-equiv=”Content-Type” content=”text/html;charset=UTF-8″>
- </head>
- <body>
- Hello world!
- <script type=”text/javascript” src=”http://m.baidu.com/static/js/zepto-event-ajax.js”></script>
- <script type=”text/javascript”>
- var appCache=window.applicationCache;
- console.log(appCache.status);
- appCache.addEventListener(“obsolete”,function(){
- console.log(“Obsolete,status:”+appCache.status);
- },false);
- appCache.addEventListener(“cached”,function(){
- console.log(“chache,status:”+appCache.status);
- },false);
- appCache.addEventListener(“checking”,function(){
- console.log(“checking,status:”+appCache.status);
- },false);
- appCache.addEventListener(“downloading”,function(){
- console.log(“downloading,status:”+appCache.status);
- },false);
- appCache.addEventListener(‘noupdate’, function(){
- console.log(‘Noupdate,Status:’ + appCache.status);
- }, false);
- appCache.addEventListener(‘updateready’, function(){
- console.log(‘updateready,Status:’ + appCache.status);
- }, false);
- appCache.addEventListener(“error”,function(){
- console.log(“error”);
- },false);
- </script>
- </body>
- </html>
如果页面没有发生变化:那么以上代码会输出:
如果对应的manifest有修改,需要更新文件,那么上面的代码会输出:
如果更新了manifest,会触发downloading和updateready事件。你可以根据这两个事件来处理一些逻辑了,比如在downloading给用户一些提示,正在加载,updateready就跳转到真正的页面,那么这个时候,页面就已经加载好了,就不用再刷新才生效页面了。比如:
- appCache.addEventListener(‘updateready’, function(){
- console.log(‘updateready,Status:’ + appCache.status);
- location.href=”testb.html”;
- }, false);
该页面在loading页面上,其实主要的内容页面在testb.html上。
D.Loading页面的问题
这时,你可能认为,这个不错,解决了所有问题。效果也ok,也没有第一次加载页面可能出错的状况。如果对于一个单页面来说,这样确实没问题了,但是如果你是一个完整的站,问题又来了,你不可能给网站每个页面都加loading,就算每个模版页都用一个loading,如果用户进入了你某个页面,如果把你某个页面存到了桌面快捷方式。那么下次启动,可能还是会挂,必须刷一次页面才能好,所以,你可能想到,整个页面都要通过js去build了……,然后又要保持build页面的js要在页面渲染前发生更改,那么,你可以在每个壳模版页面判断manifest的逻辑,然后动态载入的js,通过动态载入的js来build整个html页面,那么这样就木有bug了,不过这种处理方式确实恶心了,如果你的模版页的html壳更新不多,仅仅会更新里面的js逻辑的话,那你可以在改html壳中动态载入js即可,也可以直接刷一下页面来解决。
但是这毕竟不是一个app,如果你的站点突然接到需求要更新得很频繁,比如一天要更新一次,那么,这个manifest离线的意义就仅仅在于第二次载入的时候可以秒开。除此之外,我也想不到其他好处。然后用户如果一天刚好访问一次的话。这个状态好像还没有不用manifest离线的状态好。那么,我需要把manifest下线……
5.Manifest的回滚与下线
其实,回滚与下线表现上来说,差不多,当然,我这里所提到的回滚,是第一次上线manifest的回滚,一般如果你上线manifest出现了问题,比如造成的线上严重的bug,那么,这是时候,一般公司就会有回滚机制,用以前的代码来覆盖现在的代码。但是你一旦上线了manifest离线存储,回滚就没那么容易了,就是从有manifest的状态到无manifest的状态,其实这点和manifest下线一样。很多人都知道每次更新了静态文件,更新一下manifest多刷两次页面,页面就更新了。如果manifest文件没有了呢,我们还是拿上面的那个页面做测试。
第一次刷新会这样:
第二次再刷新是这样:
删除manifest以后,浏览器校验manifest文件就会返回一个404,那么这时,manifest就会失效,再刷页面,页面就会又开始自动更新了。也不会缓存了,是不是很兴奋。这不是很简单么?删掉manifest文件就能回滚了,就能下线了。不过现在好像404不流行了,如果你删掉了服务器上某个静态文件,服务器会302跳转到一个错误页,那么这时候,你在怎么刷新页面,页面都不会在更新了。悲剧了……用户手机浏览器中的页面再也更新不了了。此时你只能再次更新manifest让其强制更新。可能你还想到,我删除了manifest文件,把html中manifest引用也删除不就行了么。这个也是不行的。浏览器缓存了该地址的离线信息,除非你清除浏览器的缓存,否则这招无效。
所以,如果可以,就可以利用manifest文件404把manifest下掉。
如果这招行不通呢?就是没法返回404。那么你上线manifest的时候就要注意了。前文中提到了loading,那么你得再loading中处理好load manifest文件失效的情况。其实处理方式也很简单,就是跳到本来该跳的页面,然后带上一个参数(前面已经说过,带参数与带hash的区别)也就可以很轻松解决这个问题了,但是需要注意一定,找不到manifest文件,第一次刷新和第二次刷新所触发的事情不一样,第一次会触发obsolete事件,第二次再刷新,就触发error事件,不过这样也好办,我们在这两个事件中,将链接都跳到一个带参数的页面就好了(在error、obsolete中跳到页面带参数,在updateready、noupdate里面直接跳到页面)。所以,利用manifest的js api然后配合删除manifest文件(此时就可以不管页面是不是302或者404了),就可以下线掉manifest文件了,如果用户不清缓存,那段曾经被离线的代码就会起作用,会让用户永远页面带上参数。
- appCache.addEventListener(“downloading”,function(){
- console.log(“downloading,status:”+appCache.status);
- console.log(“downing”);
- },false);
- appCache.addEventListener(‘noupdate’, function(){
- console.log(‘Noupdate,Status:’ + appCache.status);
- location.href=”testb.html”;
- }, false);
- appCache.addEventListener(‘updateready’, function(){
- console.log(‘updateready,Status:’ + appCache.status);
- location.href=”testb.html”;
- }, false);
- appCache.addEventListener(“error”,function(){
- console.log(“error”);
- location.href=”testb.html?a=1″;
- },false);
- appCache.addEventListener(“obsolete”,function(){
- console.log(“Obsolete,status:”+appCache.status);
- location.href=”testb.html?a=2″;
- },false);
5.适用场景小结
上线,下线,回滚,长尾等等问题都考虑过了,现在我们应该可以正确使用manifest了,现在来讨论一个问题,什么样的页面适合使用manifest呢。关于这点,目前我们还没有具体的数据证明,但是通过manifest离线的页面,第二次访问时启动速度快了很多,而且,如果处理得当,还是有一些收益的,每次更新manifest文件以后,manifest中未修改的文件是不会被重新下载的,会返回304,而且页面中的其他静态文件,如css和img等,也能享受到传统缓存的方式。
如果你的应用是一个html5的游戏、或者是一个html的app应用,明显,采用manifest会收到很好的效果,第二次及以后加载速度都非常快,而且你的应用如果不依赖网络的话,就算用户断网了,也可以正常使用,如html5游戏,你完全可以离线来带来更好的用户体验。然而对于一些传统的站点,也想运用一下manifest新特性,如果你的站点每天都有上线更新,每天都有上线,从manifest的机制上来说,这个是不怎么适合用manifest离线的,静态资源本来就可以自己缓存,引入manifest还会带来一些维护成本已经上面提到的一些问题。但是你的站点开发完成以后,更新频率比较低,做完一版以后很长时间内不做升级(这点类似客户端),那么,你的站点就很适合用manifest离线来优化用户体验,manifest虽然带来了一些好处,但是也有很多问题。
另外,在使用过程中,某些情况下,会出现浏览器无法更新manifest的情况:目前chrome 23.0.1271.97已经找到稳定复现方法。其中:
1.问题:chrome会无法更新静态文件(这些文件包括离线主页,静态文件。)
2.原因:bug情况下,修改了manifest文件以后,通过抓包,发现某些静态文件不会发生校验。Manifest的机制是如果manifest文件发生过修改,是会校验资源列表中的静态文件的,如果文件发生过修改则会重新download这个文件,如果资源没有发生修改,服务器会返回304 。但是实际情况是,浏览器不会校验一些文件了。导致文件无法更新。此时,通过chrome://appcache-internals/方式找到浏览器的manifest缓存,然后删除,然后直接访问无法校验的静态文件。发现仍然可以访问,并且是未修改过的。后来发现是浏览器传统缓存缓存了这个文件。通过chrome://cache/可以查看到缓存的文件并没有发生修改,造成无法更新。
3.解决方法:
1.把manifest离线资源与主文件跨域存放,但这个也会导致一个问题,就是会导致dns解析变多。
2.在将manifest的列表中的静态文件的文件头中加上no-store(加no-cache不行)。这个也会导致一个问题,就是如果一个库文件,如zepto.js在m.baidu.com/static 下,manifest用到了,然后其他产品线也用到了,那么其他没用manifest的产品线就享受不了传统缓存了。
3.将manifest静态资源列表中的文件加上时间戳参数,每次修改静态文件,都主动修改下manifest中静态文件的时间戳。可能有人会认为修改一次时间戳,manifest就会本地多存一次文件,其实不是的,发现列表中如果没有该文件了,浏览器会很高级,会自动删除这个文件。不好的地方可能就是上线要多改一处地方。略微增加维护成本。
另外,有同学还碰到过浏览器将manifest文件缓存的情况,后来将这个文件的过期时间设置成了1s。
一路坑踩过来,还不知道有多少,而且一些bug出现后很难复现,总之慎用吧。
正确应用manifest来优化你的站点吧。