HTML5应用程序存储和离线Web应用
HTML5中新增了“应用程序缓存”,允许Web应用将应用程序自身本地保存到用户的浏览器中。不像localStorage和sessionStorage只是保存Web应用程序相关的数据,它是将应用程序自身保存起来–应用程序所需要的运行的所有文件(HTML、CSS、JavaScript、图片等)。
“应用程序缓存”和一般的浏览器缓存不同:它不会随用户清除浏览器缓存而被清除。同时缓存起来的应用程序也不会像一般固定大小的缓存那样,老数据会被最近一次访问的新数据代替掉。它其实不是临时存储在缓存中:应用程序更像是被“安装”在那里,除非被用户“卸载”或者“删除”他们,否则他们就会一直“驻扎”在那里。
让Web应用能够实现“本地安装”的目的是要保证他们能够子啊离线状态(比如,当在飞机上或者手机没有信号的时候)下依然可以访问。将自己“安装”到应用程序缓存中的Web应用,在离线状态下使用localStorage来保存应用相关的数据,同时还具备一套同步机制,在再次回到在线状态的时候,能够将存储的数据传输给服务器。
应用程序缓存清单
想要将应用程序“安装”到应用程序缓存中,首先要创建一个清单:包含了所有应用程序依赖的所有URL
列表,然后,通过再应用程序主HTML页面的<html>
标签中设置manifest
属性,指向到该清单文件就可以了:
<!DOCTYPE HTML>
<html manifest="myapp.appcache">
<head>...</head>
<body>...</body>
</html>
清单文件中的首行内容必须以“CACHE MANIFEST
”字符串开始。其余就是要缓存的文件URL列表,一行一个URL。相对路径的URL都相对于清单文件的URL。会忽略内容中的空行,会作为注释而忽略以“#”开始的行。注释前面可以有空格,但是在同一行注释后面是不允许有非空字符的。如下所示是一个简单的清单文件:
CACHE MANIFEST
# 上一行标识此文件是一个清单文件。本行是注释
# 下面的内容都是应用程序依赖的资源文件的URL
myapp.html
myapp.js
myapp.css
images/background.png
缓存清单的MIME类型
应用程序缓存清单文件约定以.appcache
作为文件扩展名。但是,Web服务器真正识别清单的方式是通过“text/cache-manifest
”这个MIME类型的一个清单。
如果服务器将清单文件的Content-Type
的头信息设置成其他MIME类型,那么就不会缓存应用程序了。因此,可能需要对Web服务器做一定的配置来使用这个MIME类型,比如,在Web应用目录下创建Apache服务器的一个.htaccess
文件。
如果使用nginx服务器配置这个MIME类型,可以找到mime.types文件,如下:
types {
text/cache-manifest appcache;
}
如果一个Web应用有很多页面(多个HTML页面),那么每个HTML页面就需要设置<html manifest>
属性来指向清单文件,将这些不同的页面都指向同一个清单文件,可以很清楚地表达出他们都是需要缓存起来的。
一个简单的清单必须列出Web应用依赖的所有资源。一旦一个Web应用首次下载下来并缓存,之后的任何加载请求都来自于缓存。
如果一个简单的缓存起来的应用能够从缓存中载入并运行,那么他也可以在浏览器的离线状态下运行。
复杂的清单
复杂的清单除了列举资源列表外,还有两种方式。
- NETWORK:从不缓存
- FALLBACK:指定加载和存储
NETWORK 区域标识了该URL中的资源从不缓存,总是要通过网络获取。该区域中资源的URL都只是URL前缀,用来表示以此URL前缀开头的资源都因该通过网络加载。同时该区域中的URL还支持通配符“*”,这实际上违背了一条规则:缓存应用程序必须要在清单中列举所有应用相关的资源!
FALLBACK区域中的清单项每行都包含两个URL,第一个URL是一个前缀,第二个URL是指需要加载和存储在缓存中的资源。任何能够匹配到该前缀的URL都是不会缓存起来,但是可能的话,他们会从网络中载入。如果从网络中载入这样一个URL失败的话,就会使用第二个URL指定的缓存资源来代替,从缓存中获取。
一个复杂的清单:
CACHE MANIFEST
# CACHE 是默认区域,用来列举所有项目依赖的资源列表
CACHE:
myapp.html
myapp.js
myapp.css
FALLBACK:
videos/ offline_help.html
NETWORK:
cgi/
缓存的更新
在线状态下,浏览器会异步检查清单文件是否有更新,如果有更新,新的清单文件以及清单中列举的所有文件都会下载下来重新保存到应用程序缓存中。但是,浏览器只检查清单文件,而不会去检查缓存的文件是否有更新,如修改了一个缓存的Javascript文件,并要想让他生效,就必须去更新清单文件。最简单的更该清单文件的方式就是更改版本号:
CACHE MANIFEST
# MyApp version 1 (更该这个数字以便让浏览器重新下载这个文件)
myapp.html
myapp.js
如果想要让Web应用从缓存中“卸载”,就要在服务器端删掉清单文件,同时修改HTML文件以便他们与该清单列表“断开连接”。
对于简单的Web应用而言,在更新清单文件之后,用户必须载入应用两次才能保证最新的版本生效:第一次是从缓存中载入老版本随后更新缓存;第二次才从缓存中载入最新的版本。
可以通过浏览器注册处理程序来跟踪更新缓存的过程同时反馈给用户:
applicationCache.onupdateready = function () {
var reload = confirm('这是应用程序的一个新版本,重载后才可以可以使用,是否需要立即重载');
if (reload) location.reload();
}
该事件处理程序是注册在Application Cache对象上的,该对象是Window的applicationCache属性的值。目前Chrome浏览器已经弃用该对象。
兼容性
实例演练
效果:
网络请求:
实例:
HTML 结构
<div id="toolbar">
<button id="save-button">Save</button>
<button id="sync-note">Sync Note</button>
<button>Update Application</button>
</div>
<textarea name="" id="editor" cols="30" rows="10"></textarea>
<div id="statusline"></div>
首次载入应用
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;
editor = document.getElementById('editor');
statusline = document.getElementById('statusline');
savebutton = document.getElementById('save-button');
editor.value = localStorage.note;
editor.disabled = true;
editor.addEventListener('input', function(e) {
localStorage.note = editor.value;
localStorage.lastModified = Date.now();
if (idletimer) clearTimeout(idletimer);
idletimer = setTimeout(save, 5000);
savebutton.disabled = false;
}, false);
sync();
}
显示默认状态消息
function status(msg) {
statusline.innerHTML = msg;
}
离开页面前保存数据
window.onbeforeunload = function() {
if (localStorage.lastModified > localStorage.lastSaved) save();
}
离线是通知用户
window.onoffline = function() {
status('Offline');
}
返回在线状态是同步数据
window.ononline = function() {
sync();
}
新版本通知用户
window.applicationCache.onupdateready = function() {
status('A new version of this application is availble. Reload to run it.');
}
没有新版本时通知用户
window.applicationCache.onnoupdate = function() {
status('You are running the lastest version of the application.');
}
保存数据
function save() {
if (idletimer) clearTimeout(idletimer);
idletimer = null;
if (navigator.onLine) {
var xhr = new XMLHttpRequest();
xhr.open('PUT', 'http://localhost:3000/api/note', true);
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', 'http://localhost:3000/api/note', true);
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('Newer note found on server.')
var useit = confirm('There is a newer version of the note\n'
+ 'on the server. Click ok to use that version\n'
+ 'or click Cancel to continue editing this\n'
+ 'version and overwrite the server.');
var now = Date.now();
if (useit) {
editor.value = localStorage.note = xhr.responseText;
localStorage.lastSaved = now;
status('Newest version download.');
} else {
status('Ignoring newer version of the note.');
}
localStorage.lastModified = now;
} else {
status('You are editing the current version of the note.');
}
if (localStorage.lastModified > localStorage.lastSaved) {
save();
}
editor.disabled = false;
editor.focus();
}
}
} else {
status('Can`t sync while offline');
editor.disabled = false;
editor.focus();
}
}