html5中新增了“应用程序缓存”,允许web应用程序自身本地保存到用户的浏览器中。不像localStorage和sessionStorage只是保存web应用程序相关的数据,它是将应用程序自身保存起来----应用程序所需运行的所有文件(HTML、CSS、Javascript、图片等)。“应用程序缓存”和一般的浏览器缓存不同:它不会随着用户清除浏览器缓存而被清除。同时,缓存起来的应用程序也不会像一般固定大小的缓存那样,老数据会被最近一次访问的新数据代替掉。它其实不是临时存储在缓存中:应用程序更像是被“安装”在那里,除非被用户“卸载”或“删除”它们,否则,它们就会一直“驻扎”在那里。所以说,“应用程序缓存”在真正意义上不是“缓存”更好的说法应该称之为“应用程序存储”。
让web应用能够实现“本地安装”的目的是要保证它们能够在离线状态(比如,当在飞机或者手机没信号的时候)下依然可访问。将自己“安装”到应用程序缓存中的Web应用,在离线状态下使用localStorage来保存应用相关的数据,同时还具备一套同步机制,在再次回到在线状态的时候,能够将存储的数据传输给服务器。
想要将应用程序“安装”到应用程序缓存中,首先要创建一个清单:包含所有应用程序依赖的所有url列表。然后,通过在应用程序主HTML页面的标签中设置manifest属性,指向到清单文件就可以了:
manifest=”myapp.appcache”>
…
…
清单文件:
文件的首先内容必须以”CACHE MANIFEST”字符串开始。其余就是要缓存的文件URL列表,一行一个URL。相对路径的URL都相对于清单文件的URL 。会忽略内容中的空行,会作为注释而忽略以”#”开始的行。注释前面可以有空格,但同在同一行注释后面是不允许有非空字符串的。
例如下清单:
CACHE MANIFEST
#上面一行标识此文件是一个清单文件。本行是注释
# 下面的内容都是应用程序依赖的资源文件的URL
myapp.html
myapp.js
myapp.css
images/background.png
应用程序缓存清单文件约定以.appcache作为文件扩展名。但是,这也仅仅只是约定而已,web服务器真正识别清单文件的方式是通过”text/cache-manifest”这个MIME类型的一个清单。如果服务器将清单文件的Content-Type的头信息设置成其他MIME类型,那么就不会缓存应用程序了。因此,可能需要对web服务器做一定的配置来使用这个MIME类型,比如,在Web应用目录下创建Apache服务器的一个.htaccess文件
清单文件包含要缓存的应用的标识。如果一个Web应用有很多web页面,那么每个html页面就需要设置属性来指向清单文件。事实上,将这些不同的页面都指向同一个清单文件,可能很清楚地表达出它们都是需要缓存起来的,同时它们又是来自同一个web应用的。如果一个应用只有少量的html页面,那么一般会把这些页面都显示地列在清单文件中。
像之前提到的,一个简单的清单必须列出web应用依赖的所有资源。一旦一个Web应用首次下载下来并缓存,之后的任何加载请求都来自缓存。从缓存中去载入一个应用资源的时候,就要求它请求的资源务必要在清单中。不会载入不在清单的资源。这种政策有点离线的味道。如果一个简单的缓存起来的应用能够从缓存中载入并运行,那么它也可以在浏览器的离线状态下运行。通常情况下,很多复杂的web应用无法将它们依赖的所有资源都缓存起来。但是,如果它们同时也有一个复杂的清单的话,它们仍然可能使用应用程序缓存。
“NETWORK:“区域标识该url中的资源从不缓存,总要是通过网络获取。该区域中的资源的url都只是url前缀,用来表示以此url前缀开头的资源都应该要通过网络加载。可以使用通配符 ”*“
“FALLBACK:“区域中的清单项每行都包含两个URL。第二个url是指需要加载和存储在缓存中的资源,第一个URL是一个前缀。任何能够匹配到该前缀的URL都不会缓存起来,如果在线,它们会从网络中载入。如果从网络中载入这样的URL失败的话,就会使用第二个URL指定的缓存资源来代替,从缓存中获取。
下面是一个更加复杂的缓存清单:
CHCHE MANIFEST
CACHE:
myapp.html
myapp.css
myapp.js
FALLBAC:
videos/ offline_help.html
NETWORK:
cgi/
当一个web应用从缓存中载入的时候,所有与之相关的文件也是直接从缓存中获取。在线状态下,浏览器会异步地检查清单文件是否有更新。如果有更新,新的清单文件以及清单中的列举的所有文件都会下载下来重新保存到程序缓存中。但是,要注意是,浏览器只是检查清单文件,而不会检查缓存的文件是否有更新:只检查清单文件。
如果修改一个缓存的js文件,并且要想让该文件生效,就必须去更新下清单文件。由于应用程序依赖的文件列表其实并没有变化,因此最简单的方式就是更新版本吃
CHCHE MANIFEST
CACHE:
#MyApp version 1 (更改这个数字以便让浏览器重新下载)
myapp.html
myapp.css
myapp.js
同样“卸载“,就要在服务器端删除清单文件,使得请求该文件的时候返回404,同时,修改html文件以便他们与该清单列表”断开链接“。
注意:浏览器检查清单文件以及更新缓存的操作是异步的,可能是在从缓存中载入应用之前,也有可能是同时进行。因此对于简单的web应用而言,在更新清单文件之后,用户必须载入应用两次才能保证最新的版本生效:第一次是从缓存中载入老版本随后更新缓存;第二次才是从缓存中载入最新的版本。
浏览器在更新缓存过程中会触发一系列事件,可以通过注册处理程序来跟踪这个过程同时提供反馈用户。
如:
applicationCache.onupdateready= function(){
var reload = confirm(“A new version ofthis application is available\n and will be used the next time you reload.\n”);
if(reload) location.reload();
}
该事件注册在ApplicationCache对象上的,该对象是window的applicationCache属性的值。支持应用程序缓存的浏览器会定义该属性。
//下面所有的事件处理程序都使用此函数来显示状态消息
//由于都是通过调用status函数来显示状态,因此所有处理程序都返回false来阻止浏览器
//显示其默认状态消息
function status(msg){
doucment.getElementById(“statusline”).innerHTML= msg;
console.log(msg); //同时在控制台输出此消息,便于调试
}
//每当应用程序载入的时候,都会检查该清单文件
//也总会首先触发“checking”事件
window.applicationCache.onchecking = function(){
status(“checking for a new version.”);
return false;
}
//如果没有改动,同时应用程序也已经缓存了
//”noupdate”事件被触发,整个过程结束
window.applicationCache.onnoupdate = function(){
}
//如果还未缓存应用程序,或者清单文件有改动
//那么浏览器会下载并缓存清单中的所有资源
//触发”downloading”事件,同时意味着下载过程开始
window.applicationCache.ondownloading = function(){
status(“Downloading new version”);
window.progresscount = 0;
return false;
}
//在下载过程中会间断性触发“progress“事件
//通常是在每个文件下载完毕的时候
window.applicationCache.onprogress = function(e){
varprogress = “”;
if(e && e.lengthComputable)
progress = “ ”+Math.round(100*e.loaded/e.total)+”%”
else
progress = “(“+(++progresscount)+”)”
return false;
}
//当下载完成并且首次将应用程序下载到缓存中时,浏览器会触发“cached“事件
window.applicationCache.oncached = function(e){
status(“Thisapplication is now cached locally”);
return false;
}
//当下载完成并将缓存中的应用程序更新后,浏览器会触发”updaterady”事件
//要注意的是:触发此事件的时候,用户任然可以看到老版本的应用程序
window.applicationCache.onupdateready = function(e){
status(“Anew version has been downloaded. Reload to run it”);
return false;
}
//如果浏览器处于离线状态,检查清单列表失败,则会触发“error“事件
//当一个未缓存的应用程序引用一个不存在的清单文件,也会触发此事件
window.applicationCache.onerror = function(e){
status(“Couldn’tload manifest or cache application”);
return false;
}
//如果一个缓存的应用程序引用一个不存在的清单文件,会触发“obsolete“
//同时将应用从缓存中移除之后不会从缓存而是通过网络加载资源
window.applicationCache.onobsolete = function(e){
status(“Thisapplication is no longer cached. Reload to get the latest version from thenetwork.”);
return false;
}
每次载入一个设置了manifest属性的html文件,浏览器都会触发”checking”事件。并通过网络载入该清单文件。不过之后,会随着不同的情况触发不同的事件。
l 没有可用更新
如果应用程序已经缓存并且清单文件没有动,则浏览器会触发noupdate事件
l 有可用更新
如果应用程序已经缓存并且清单元件有改动,则浏览器会触发downloading事件并开始下载和缓存清单文件中列举的所有资源。随着下载过程的进行,浏览器还会触发”progress”事件,在下载完成后,会触发”updateready”事件
l 首次载入新的应用程序
如果还未缓存应用程序,如上所述downloading,progress事件都会触发。但是,当下载完成后,浏览器会触发”cached”事件而不是updateready事件
l 浏览器处于离线状态
如果浏览器处于离线状态,它无法检查清单文件,同时它会触发“error”事件。
如果一个未缓存的应用程序引用了不存的清单文件,浏览器也会触发该事件
l 清单文件不存在
如果浏览器处理在线状态,应用程序也已经缓存起来,但是清单文件不存在,浏览器会触发obsolete事件,并将该应用程序从缓存中移除。
n ApplicationCache.UNCACHED(0)
应用程序没有设置manifest属性:未缓存
n ApplicationCache.IDLE(1)
清单文件已经检查完毕,并且已经缓存了最新的应用程序
n ApplicationCache.CHECKING(2)
浏览器正在检查清单文件
n ApplicationCache.DOWNLOADING(3)
浏览器正在下载并缓存清单中列举的所有文件
n ApplicationCache.UPDATEREADY(4)
已经下载和缓存了最新版的应用程序
n ApplicationCache.OBSOLETE(5)
清单文件不存在,缓存将被清除
n update
显式调用了更新缓存算法以检测是否有最新版本的的应用程序。这导致浏览器检测同一个清单文件(并触发相同的事件),这和第一次载入应用程序时的效果是一样的。
n swapCache
它告诉浏览器可以弃用老缓存,所有的请求都从新缓存中获取。注意,这并不会重新载入应用程序:所有已经载入的html文件、图片、脚本等资源都不会改变。但是,之后的请求将从最新的缓存中获取。这会导致“版本错乱”的问题,因此一般不推荐使用,除非应用程序设计得很好,确保这样的方式没有问题。只有当ApplicationCache.UPDATEREADY和ApplicationCache.ABSOLETE 时调用 swapCache()才有意义(当状态OBSOLETE时,调用它可以立即弃用废弃的缓存,让之后所有的请求都通过网络获取)。如果状态属性是其他数值的时候调用swapCache()方法,它就会抛出异常。
离线web应用指的是将自己“安装”在应用程序缓存中的程序,使得哪怕在浏览器处于离线状态时依然可访问它。为了在离线状态可用,Web应用需要可以告知别人自己是离线还是在线,同时当网络连接的状态发生改变时候也能“感知”到。通过navigator.onLine属性,
navigator.onLine是HTML5定义用来检测设备是在线还是离线。对应的值为false或true。
但是不同浏览器表现并不一致。
HTML5定义了online&offline事件用于监听网络状态变化。
window.addEventListener('online', callback); // 离线到上线
window.addEventListener('offline', callback); // 上线到离线
目前除了IE(IE只支持navigator.onLine属性)外,其他最新浏览器都支持这个事件。
下面将以一个简单的离线web应用结束,该应用使用了这些技术。应该名叫PermaNote----------一个简单的记事本程序。
缓存清单文件
premanote.appcache
CACHE MANIFEST
# PermaNote v8
permanote.html
permanote.js
NETWORK:
note
文件permanote.html
#editor {width:100%;height:250px}
#statusline{width:100%}
js文件
定义了一个status()函数在状态栏上展示消息,一个save函数来把当前版本的笔记保存到服务器上,以及一个sync()方法来确保本地副本和服务器的副本的同步。其中,save()和sync()两个函数使用了http脚本化技术。save函数用的是http “PUT”方法而不是常见的”POST”方法。
除了三个基本函数,还定义了一些事件处理程序。为了保持本地副本和服务器端的副本同步,应用程序需要一些事件处理程序:
n onload
尝试和服务器同步,一旦有新版本的笔记并且完成同步后,就启用编辑窗口
save()和sync()发http请求,并在XMLHttpRequest对象上注册一个onload事件处理程序来获取上传或下载完成的提醒。
n onbeforeunload
在未上传前,把当前版本的笔记数据保存到服务器上
n oninput
每个
n onoffline
当浏览器进入离线状态时,在状态栏上显示离线消息
n ononline
在浏览器回到在线状态时,同步服务器,检查是否有新版本数据,并且保存当前版本的数据
n onpudateready
如果新版本的应用(已缓存)准备就绪,就在状态条上展示消息告知用户。
n onnoupdate
如果应用程序缓存没有必生变化,则通知用户他仍在运行当前版本。
permanote.js
var editor,statusline,savebutton,idletimer;
//首次载入应用
window.onload = function(){
//第一次载入时,初始化本地存储
if(localStorage.note==null)localStorage.note= “”;
if(localStorage.lastModified==null)localStorage.lastModified = 0;
if(localStorage.lastSaved==null)localStorage.lastSaved = 0;
//查找编辑器ui元素,并初始化全局变量
editor = document.getElementById(“editor”);
statusline =document.getElementById(“statusline”);
savebutton =document.getElementById(“savebutton”);
editor.value = localStorage.note;
eidtor.disable = true; //同步前禁止编辑
//一旦文本区有内容输入
editor.addEventListener(“input”,function(e){
//将新的值保存到localStorage中
localStorage.note =editor.value;
localStorage.lastModified =Date.now();
if(idletimer) clearTimeout(idletimer);
idletimer = setTimeout(save,5000);
//启动保存按钮
savebutton.disabled = false;
});
//每次载入应用程序时,尝试同步服务器
sync();
}
//离开页面前保存数据到服务器
window.onbeforeunload = function(){
if(localStorage.lastModified>localStorage.lastSaved)save();
}
//离线时,通知用户
window.onoffline = function(){
status(“Offline”);
}
//再次返回在线状态时,进行同步
window.onoffline = function(){
sync();
}
//当有新版本应用时候,提醒用户
//这里我们也可以采用location.reload()方法来强制重新载入应用
window.applicationCache.onupdateready =function(){
status(“A new version of thisapplication is available. reload to run it ”);
}
//当没有新版本的时候也通知用户
window.applicationCache.onnoupdate =function(){
status(“You are running the latestversion of the application. ”);
}
function status(msg){statusline.innerHTML= msg;}
//每当笔记内容更新后,如果用户停止编辑超过5分钟,
//就会自动将笔记文本上传到服务器(在线状态)
function save(){
if(idletimer) clearTimeout(idletimer);
idletimer = null;
if(navigator.onLine){
var xhr = newXMLHttpRequest();
xhr.open(“PUT”,”/note”);
xhr.send(editor.value);
xhr.onload = function(){
localStorage.lastSaved =Date.now();
savebutton.disabled =true;
}
}
}
//检查服务器端是否有新版本的笔记
//如果没有,则将当前版本保存到服务器端
function sync(){
if(navigator.onLine) {
var xhr = new XMLHttpRequest();
xhr.open(“GET”,”/note”);
xhr.send();
xhr.onload = function(){
var remotemodTime = 0;
if(xhr.status==200){
var remoteModTime =xhr.getResponseHeader(“Last-Modified”);
remoteModTime = new Date(remoteModTime).getTime();
}
if(remoteModTime>localStorage.lastModified){
status(“Newernote found on server.”);
var useit =confirm(“是否更新”);
var now = Date.now();
if(useit){
editor.value = localStorage.note = xhr.responseText;
status(“Newest versiondownloaded.”);
}else status(“Ignoringnewer version of the note. ”);
localStorage.lastModified = now;
}else status(“You areediting the currentversion of the note.”);
if(localStorage.lastModified>localStorage.lastSaved){
save();
}
editor.disabled =false;
editor.focus();
}//function end
}else{
//离线状态下,不能同步
status(“can’t sync while offline”);
editor.disabled = false;
editor.focus();
}
}