AppCache 离线存储 应用程序缓存 API 及注意事项

使用ApplicationCache接口实现离线缓存

原文:http://www.mb5u.com/HTML5/html5_96464.html

推荐: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
...
</html>

  

清单文件必须以 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

  

*.html /offline.html以“#”开头的行是注释行,但也可用于其他用途。应用缓存只在其清单文件发生更改时才会更新。例如,如果您修改了图片资源或更改了 JavaScript 函数,这些更改不会重新缓存。您必须修改清单文件本身才能让浏览器刷新缓存文件。使用生成的版本号、文件哈希值或时间戳创建注释行,可确保用户获得您的软件的最新版。您还可以在出现新版本后,以编程方式更新缓存,如更新缓存部分中所述。 
清单可包括以下三个不同部分: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的格式:

  1. 1
    2
    3
    4
    5
    6
    [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的文件写法:

  1. CACHE MANIFEST
  2. # v2012 1203v3
  3. http://m.baidu.com/static/index/i.gif
  4. http://m.baidu.com/static/hb/hot.gif
  5. http://m.baidu.com/static/index/logo_index2.png
  6. NETWORK:
  7. *
  8. http://m.baidu.com/su
  9. http://loc.map.baidu.com/wloc
  10. 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文件存在有效,如下:

  1. CACHE MANIFEST
  2. #test11123aaadfsaasdadffdsfaaaffd
  3. http://m.baidu.com/static/js/zepto-event-ajax.js
  4. testb.html
  5. NETWORK:
  6. *

Html文件如下:

  1. <!DOCTYPE html>
  2. <html manifest=”notes.manifest”>
  3. <head>
  4. <title>离线存储demo</title>
  5. <meta http-equiv=”Content-Type” content=”text/html;charset=UTF-8″>
  6. </head>
  7. <body>
  8. Hello world!
  9. <script type=”text/javascript” src=”http://m.baidu.com/static/js/zepto-event-ajax.js”></script>
  10. <script type=”text/javascript”>
  11. var appCache=window.applicationCache;
  12. console.log(appCache.status);
  13. appCache.addEventListener(“obsolete”,function(){
  14. console.log(“Obsolete,status:”+appCache.status);
  15. },false);
  16. appCache.addEventListener(“cached”,function(){
  17. console.log(“chache,status:”+appCache.status);
  18. },false);
  19. appCache.addEventListener(“checking”,function(){
  20. console.log(“checking,status:”+appCache.status);
  21. },false);
  22. appCache.addEventListener(“downloading”,function(){
  23. console.log(“downloading,status:”+appCache.status);
  24. },false);
  25. appCache.addEventListener(‘noupdate’, function(){
  26. console.log(‘Noupdate,Status:’ + appCache.status);
  27. }, false);
  28. appCache.addEventListener(‘updateready’, function(){
  29. console.log(‘updateready,Status:’ + appCache.status);
  30. }, false);
  31. appCache.addEventListener(“error”,function(){
  32. console.log(“error”);
  33. },false);
  34. </script>
  35. </body>
  36. </html>

如果页面没有发生变化:那么以上代码会输出:

AppCache 离线存储 应用程序缓存 API 及注意事项

如果对应的manifest有修改,需要更新文件,那么上面的代码会输出:

AppCache 离线存储 应用程序缓存 API 及注意事项

如果更新了manifest,会触发downloading和updateready事件。你可以根据这两个事件来处理一些逻辑了,比如在downloading给用户一些提示,正在加载,updateready就跳转到真正的页面,那么这个时候,页面就已经加载好了,就不用再刷新才生效页面了。比如:

  1. appCache.addEventListener(‘updateready’, function(){
  2. console.log(‘updateready,Status:’ + appCache.status);
  3. location.href=”testb.html”;
  4. }, false);

该页面在loading页面上,其实主要的内容页面在testb.html上。

D.Loading页面的问题

这时,你可能认为,这个不错,解决了所有问题。效果也ok,也没有第一次加载页面可能出错的状况。如果对于一个单页面来说,这样确实没问题了,但是如果你是一个完整的站,问题又来了,你不可能给网站每个页面都加loading,就算每个模版页都用一个loading,如果用户进入了你某个页面,如果把你某个页面存到了桌面快捷方式。那么下次启动,可能还是会挂,必须刷一次页面才能好,所以,你可能想到,整个页面都要通过js去build了……,然后又要保持build页面的js要在页面渲染前发生更改,那么,你可以在每个壳模版页面判断manifest的逻辑,然后动态载入的js,通过动态载入的jsbuild整个html页面,那么这样就木有bug了,不过这种处理方式确实恶心了,如果你的模版页的html壳更新不多,仅仅会更新里面的js逻辑的话,那你可以在改html壳中动态载入js即可,也可以直接刷一下页面来解决。

但是这毕竟不是一个app,如果你的站点突然接到需求要更新得很频繁,比如一天要更新一次,那么,这个manifest离线的意义就仅仅在于第二次载入的时候可以秒开。除此之外,我也想不到其他好处。然后用户如果一天刚好访问一次的话。这个状态好像还没有不用manifest离线的状态好。那么,我需要把manifest下线……

5.Manifest的回滚与下线

其实,回滚与下线表现上来说,差不多,当然,我这里所提到的回滚,是第一次上线manifest的回滚,一般如果你上线manifest出现了问题,比如造成的线上严重的bug,那么,这是时候,一般公司就会有回滚机制,用以前的代码来覆盖现在的代码。但是你一旦上线了manifest离线存储,回滚就没那么容易了,就是从有manifest的状态到无manifest的状态,其实这点和manifest下线一样。很多人都知道每次更新了静态文件,更新一下manifest多刷两次页面,页面就更新了。如果manifest文件没有了呢,我们还是拿上面的那个页面做测试。

第一次刷新会这样:

AppCache 离线存储 应用程序缓存 API 及注意事项

第二次再刷新是这样:

AppCache 离线存储 应用程序缓存 API 及注意事项

删除manifest以后,浏览器校验manifest文件就会返回一个404,那么这时,manifest就会失效,再刷页面,页面就会又开始自动更新了。也不会缓存了,是不是很兴奋。这不是很简单么?删掉manifest文件就能回滚了,就能下线了。不过现在好像404不流行了,如果你删掉了服务器上某个静态文件,服务器会302跳转到一个错误页,那么这时候,你在怎么刷新页面,页面都不会在更新了。悲剧了……用户手机浏览器中的页面再也更新不了了。此时你只能再次更新manifest让其强制更新。可能你还想到,我删除了manifest文件,把html中manifest引用也删除不就行了么。这个也是不行的。浏览器缓存了该地址的离线信息,除非你清除浏览器的缓存,否则这招无效。

AppCache 离线存储 应用程序缓存 API 及注意事项

所以,如果可以,就可以利用manifest文件404把manifest下掉。

如果这招行不通呢?就是没法返回404。那么你上线manifest的时候就要注意了。前文中提到了loading,那么你得再loading中处理好load manifest文件失效的情况。其实处理方式也很简单,就是跳到本来该跳的页面,然后带上一个参数(前面已经说过,带参数与带hash的区别)也就可以很轻松解决这个问题了,但是需要注意一定,找不到manifest文件,第一次刷新和第二次刷新所触发的事情不一样,第一次会触发obsolete事件,第二次再刷新,就触发error事件,不过这样也好办,我们在这两个事件中,将链接都跳到一个带参数的页面就好了(在error、obsolete中跳到页面带参数,在updateready、noupdate里面直接跳到页面)。所以,利用manifest的js api然后配合删除manifest文件(此时就可以不管页面是不是302或者404了),就可以下线掉manifest文件了,如果用户不清缓存,那段曾经被离线的代码就会起作用,会让用户永远页面带上参数。

  1. appCache.addEventListener(“downloading”,function(){
  2. console.log(“downloading,status:”+appCache.status);
  3. console.log(“downing”);
  4. },false);
  5. appCache.addEventListener(‘noupdate’, function(){
  6. console.log(‘Noupdate,Status:’ + appCache.status);
  7. location.href=”testb.html”;
  8. }, false);
  9. appCache.addEventListener(‘updateready’, function(){
  10. console.log(‘updateready,Status:’ + appCache.status);
  11. location.href=”testb.html”;
  12. }, false);
  13. appCache.addEventListener(“error”,function(){
  14. console.log(“error”);
  15. location.href=”testb.html?a=1″;
  16. },false);
  17. appCache.addEventListener(“obsolete”,function(){
  18. console.log(“Obsolete,status:”+appCache.status);
  19. location.href=”testb.html?a=2″;
  20. },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来优化你的站点吧。

上一篇:常用的4个eclipse插件安装过程及使用方法


下一篇:《DSP using MATLAB》Problem 4.10