PWA介绍
PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式增强策略来创建跨平台 Web 应用程序。
PWA优势
PWA是可被发现、易安装、可链接、独立于网络、渐进式、可重用、响应性和安全性
核心指南
- service workers数据缓存
- 添加到主屏幕
- indexedDB数据储存
- 数据推送
service workers 离线缓存
1、出现的背景
丢失网络连接。即使是世界上最好的 web app,如果下载不了它,也是非常糟糕的体验。如今虽然已经有很多种技术去尝试着解决这一问题。而随着离线页面的出现,一些问题已经得到了解决。但是,最重要的问题是,仍然没有一个好的统筹机制对资源缓存和自定义的网络请求进行控制。
之前的尝试 — AppCache — 看起来是个不错的方法,因为它可以很容易地指定需要离线缓存的资源。但是,它假定你使用时会遵循诸多规则,如果你不严格遵循这些规则,它会把你的APP搞得一团糟。关于APPCache的更多详情,请看Jake Archibald的文章: Application Cache is a Douchebag.
后来service workers出现了,虽然 Service Worker 的语法比 AppCache 更加复杂,但是你可以使用 JavaScript 更加精细地控制 AppCache 的静默行为。有了它,你可以解决目前离线应用的问题,同时也可以做更多的事。 Service Worker 可以使你的应用先访问本地缓存资源,所以在离线状态时,在没有通过网络接收到更多的数据前,仍可以提供最基本的功能
2、使用前准备
在已经支持 serivce workers 的浏览器的版本中,很多特性没有默认开启。如果你发现示例代码在当前版本的浏览器中怎么样都无法正常运行,你可能需要开启一下浏览器的相关配置:
- Firefox Nightly: 访问
about:config
并设置dom.serviceWorkers.enabled
的值为 true; 重启浏览器; - Chrome Canary: 访问
chrome://flags
并开启experimental-web-platform-features
; 重启浏览器 (注意:有些特性在Chrome中没有默认开放支持); - Opera: 访问
opera://flags
并开启ServiceWorker 的支持
; 重启浏览器 Service Workers 网络要求要求必须在 HTTPS 下才能运行。本地开发可以用localhost
3、基本流程
1、service worker 首先通过 'serviceWorker' in navigator
来判断浏览器是否支持service worker
if('serviceWorker' in navigator) {
...
}
2、service worker URL通过serviceWorker.register()
来获取和注册
navigator.serviceWorker.register('/pwa/serviceWorker.js',{scope: '/pwa/'})
.then(function () {
console.log('registered')
}).catch(function (e) {
console.log(e);
})
3、如果注册成功,service worker 就在 ServiceWorkerGlobalScope
环境中运行; 这是一个特殊类型的 woker 上下文运行环境,与主运行线程(执行脚本)相独立,同时也没有访问 DOM 的能力,此时改js的this指的是ServiceWorkerGlobalScope
对象而不是window
4、当打开受service worker控制的页面后,该页面就会尝试通过install
方法安装service worker (开始填充indexDB和缓存站点资源)。
self.addEventListener('install', function (e) {
...
});
5、当 install
事件的处理程序执行完毕后,可以认为 service worker 安装完成了。
6、下一步是激活。当 service worker 安装完成后,会接收到一个激活事件(activate event)。 onactivate
主要用途是清理先前版本的service worker 脚本中使用的资源。
self.addEventListener('activate', function (event) {
...
})
安装和激活:填充你的缓存
install
事件会在注册完成之后触发。install
事件一般是被用来填充你的浏览器的离线缓存能力。为了达成这个目的,我们使用了 Service Worker 的 新的标志性的存储 API — cache
— 一个 service worker 上的全局对象,它使我们可以存储网络响应发来的资源,并且根据它们的请求来生成key。这个 API 和浏览器的标准的缓存工作原理很相似,但是是特定你的域的。它会一直持久存在,直到你告诉它不再存储,你拥有全部的控制权。
注意: Cache API 并不被每个浏览器支持。(查看 Browser support 部分了解更多信息。) 如果你现在就想使用它,可以考虑采用一个 polyfill,比如 Google topeka demo,或者把你的资源存储在 IndexedDB 中。
//install方法,安装serviceWorker
self.addEventListener('install', function (e) {
self.skipWaiting(); //告知浏览器直接跳过等待阶段,淘汰过期的sw.js,直接开始尝试激活新的service worker
e.waitUntil(
// 打开一个新的储存区域,同时尝试缓存文件
caches.open('myStore').then(function(cache){
return cache.addAll([
'/pwa/index.html',
'/pwa/index.js',
'/pwa/gallery/bountyHunters.jpg',
'/pwa/gallery/myLittleVader.jpg',
'/pwa/gallery/snowTroopers.jpg',
]
)
})
)
});
- 这里我们 新增了一个
install
事件监听器,接着在事件上接了一个ExtendableEvent.waitUntil()
方法——这会确保Service Worker 不会在waitUntil()
里面的代码执行完毕之前安装完成。 - 在
waitUntil()
内,我们使用了caches.open()
方法来创建了一个叫做myStore
的新的缓存,将会是我们的站点资源缓存的第一个版本。它返回了一个创建缓存的 promise,当它 resolved的时候,我们接着会调用在创建的缓存示例上的一个方法addAll()
,这个方法的参数是一个由一组相对于 origin 的 URL 组成的数组,这些 URL 就是你想缓存的资源的列表。 - 如果 promise 被 rejected,安装就会失败,这个 worker 不会做任何事情。这也是可以的,因为你可以修复你的代码,在下次注册发生的时候,又可以进行尝试。
- 当安装成功完成之后, service worker 就会激活。在第一次你的 service worker 注册/激活时,这并不会有什么不同。但是当 service worker 更新 (稍后查看 Updating your service worker 部分) 的时候 ,就不太一样了。
注意: localStorage 跟 service worker 的 cache 工作原理很类似,但是它是同步的,所以不允许在 service workers 内使用。
注意: IndexedDB 可以在 service worker 内做数据存储。
当遇到被sw控制的资源被请求都会触发fatch
方法
//每次任何被 service worker 控制的资源被请求到时,都会触发 fetch 事件,这些资源包括了指定的 scope 内的文档,和这些文档内引用的其他任何资源
self.addEventListener('fetch', function (event) {
console.log(event.request.url);
// 劫持http响应
//caches.match(event.request) 允许我们对网络请求的资源和 cache 里可获取的资源进行匹配,查看是否缓存中有相应的资源。这个匹配通过 url 和 vary header进行,就像正常的 http 请求一样。
event.respondWith(caches.match(event.request).then(function (response) {
if(response != undefined) {
//缓存中有新的资源;
return response
}else {
// 缓存中没有新的资源,尝试网络请求去获取新的资源,
return fetch(event.request).then(function (response) {
//复制一份;
// 原因是response是一个Stream,为了让浏览器跟缓存都使用这个response
// 必须克隆这个response,一份到浏览器,一份到缓存中缓存。
// 只能被消费一次,想要再次消费,必须clone一次
let resClone = response.clone();
// 抓取缓存区域
caches.open('myStore').then(function (cache) {
// 把数据放到缓存区域中
cache.put(event.request, resClone);
})
return response;
}).catch(function () {
//尝试请求失败,用 match() 把一些回退的页面作为响应来充当默认资源
return caches.match('/pwa/gallery/snowTroopers.jpg')
})
}
}))
})
删除旧的缓存
activate
:每个浏览器都对 service worker 可以用的缓存空间有个硬性的限制。浏览器尽力管理磁盘空间,但它可能会删除整个域的缓存。浏览器通常会删除域下面的所有的数据。
传给 waitUntil()
的 promise 会阻塞其他的事件,直到它完成。所以你可以确保你的清理操作会在你的的第一次 fetch 事件之前会完成
//清除缓存,在第一次fatch之前完成,
self.addEventListener('activate', function (event) {
let cacheWhiteList = ['myStore'];
console.log(caches)
event.waitUntil(
caches.keys().then(function (keyList) {
return Promise.all(keyList.map(function (key) {
if(cacheWhiteList.indexOf(key) === -1) {
return caches.delete(key)
}
}))
})
)
})
添加到主屏幕
添加到主屏幕(简称A2HS)是现代智能手机浏览器中的一项功能,使开发人员可以轻松便捷地将自己喜欢的Web应用程序(或网站)的快捷方式添加到主屏幕中,以便他们随后可以通过单点访问它。
浏览器支持
Mobile Chrome / Android Webview 从31版开始支持A2HS,Opera for Android从32版开始支持,Firefox for Android从58版开始支持。
注意: 电脑上可以实现,手机上没能实现 0..0>
如何使用
第一次打开如果浏览器支持,会在地址栏的右侧显示+的符号,见下图
当点击安装之后就会在你的主屏幕上出现你想要的程序了,同时程序也被打开了。
Manifest
{
"background_color": "red",
"description": "this is demo.",
"display": "fullscreen",
"icons": [
{
"src": "gallery/icon.png",
"sizes": "192x192",
"type": "image/png"
}
],
"name": "one demo",
"short_name": "Foxes",
"start_url": "/pwa/index.html"
}
background_color
:在加载程序之前,启动app时的背景颜色,仅用于在从网络或存储介质加载主样式表时改善用户体验discription
:string
程序的描述。categories
:用作目录或商店列表Web应用程序的提示,类似meta关键词 .eg"categories": ["books", "education", "medical"]
display
:指定应如何显示应用。 为了使它看起来像一个独特的应用程序(而不仅仅是网页),您应该选择一个值,例如fullscreen
(根本不显示任何UI)或独立standalone
(非常相似,但是系统级UI元素(例如状态栏)可能是可见的)。icons
:指定在不同位置(例如,在任务切换器上或更重要的是在主屏幕上)表示应用程序时浏览器使用的图标。name
/short_name
:这些字段提供了在不同位置表示应用程序时要显示的应用程序名称。name
提供完整的应用名称。short_name
当没有足够的空间显示全名时,提供一个缩写名称。如果您的应用程序名称特别长,建议您同时提供两者。start_url
:提供启动添加到主屏幕应用程序时应加载的资源的路径。请注意,这必须是一个真相网站主页的相对路径,相对于 manifest的url。 另外,请注意,Chrome在显示安装标语之前需要这样做,而Firefox在显示“含+号的home”图标时并不需要它。- 更多详细字段
### 帮助链接