一、传统web 应用

当前web应用在移动时代并没有达到其在桌面设备上流行的程度,下面有张图来对比与原生应用之间的差别。



究其原因,无外乎下面不可避免的几点:

  • 移动设备网络限制-不可忽略的加载时间
  • web应用依赖于浏览器作为入口
  • 体验与原生的差距

假如能解决以上的几点,对web app 来说会有多大的提升可以想象。

二、PWA是什么

PWA 全称Progressive Web Apps(渐进式Web应用程序),旨在使用现有的web技术提供用户更优的使用体验。

基本要求

  • 可靠(Reliable)

    即使在不稳定的网络环境下,也能瞬间加载并展现
  • 快速响应(Fast)

    快速响应,并且有平滑的动画响应用户的操作
  • 粘性(Engaging)

    像设备上的原生应用,具有沉浸式的用户体验,用户可以添加到桌面

PWA 本身强调渐进式,并不要求一次性达到安全、性能和体验上的所有要求,开发者可以通过 PWA Checklist 查看现有的特征。

除以上的基准要求外,还应该包括以下特性:

  • 渐进式 - 适用于所有浏览器,因为它是以渐进式增强作为宗旨开发的
  • 连接无关性 - 能够借助 Service Worker 在离线或者网络较差的情况下正常访问
  • 类似应用 - 由于是在 App Shell 模型基础上开发,因为应具有 Native App 的交互和导航,给用户 Native App 的体验
  • 持续更新 - 始终是最新的,无版本和更新问题
  • 安全 - 通过 HTTPS 协议提供服务,防止窥探和确保内容不被篡改
  • 可索引 - 应用清单文件和 Service Worker 可以让搜索引擎索引到,从而将其识别为『应用』
  • 粘性 - 通过推送离线通知等,可以让用户回流
  • 可安装 - 用户可以添加常用的 webapp 到桌面,免去去应用商店下载的麻烦
  • 可链接 - 通过链接即可分享内容,无需下载安装

看起来有点眼花缭乱,这又是一个新的飞起的轮子吗?这里重申一下,PWA背后不是一种新的技术,而是集合当前多种web技术的一种集合。分别利用各自的功能来完成渐进式的整体需求。下面就沿着前面提出的问题分别了解一下相关技术

三、技术组成

由以下几种技术构成:

  • App Manifest
  • Service Worker
  • Notifications API
  • Push API

其中Service Worker是PWA技术的关键,它们可以让app满足上面的三基准。其他技术则是锦上添花,让app更加的强大。

3.1 service worker背景

离线缓存背景

针对网页的体验,从前到后都做了很多努力,极力去降低响应时间,这里就不表述多样的技术手段。

另一个方向的就是缓存,减少与服务器非必要的交互,不过对于离线的情况下浏览器缓存就无力了,

这样离线缓存的需求就出现了。

离线缓存的历程

web应用在离线缓存发展的过程中也不是一簇而就的,经历了逐渐完善的过程。

初期的解决方案是AppCache

然而,事实证明这是一个失败的尝试,缺陷太多,已经被废弃了。具体可以查看Application Cache is a douchebag

但是方向还是正确的,那就继续孜孜不倦的探索。

workers

持久化先放一边,来谈谈另一个问题

基于浏览器中的 javaScript 单线程的现实逐渐不能满足现代web需求的现状,例如耗时的计算,用户的交互显然会受影响。

为了将这些耗时操作从主线程中解放出来,早期W3C新增了一个Web Worker 的 API,可以脱离主线程单独执行,并且可以与主线程交互。

不过Web Worker是临时性的依赖于创建页面 ,不能满足我们持久化的需求。

冲着这个目标,下面就比较容易解决了,搞个能持久存在的就行了。

在Web Worker的基础上,W3C新增了service worker来满足我们持久化的需求。

其生命周期与页面无关,关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动

功能

Service Worker虽然满足了离线缓存来,其功能可不仅仅局限于此。 可以提供

  • 丰富的离线体验,
  • 周期的后台同步,
  • 消息推送通知,
  • 拦截和处理网络请求,
  • 管理资源缓存

    这些正好也是PWA的目的,所以说Service Worker是PWA的关键技术。

前提条件

Service Worker 出于安全性和其实现原理,在使用的时候有一定的前提条件。

  • 由于 Service Worker 要求 HTTPS 的环境

    当然一般浏览器允许调试 Service Worker 的时候 host 为 localhost 或者 127.0.0.1
  • Service Worker 的缓存机制是依赖 Cache API (略过)
  • 依赖 HTML5 fetch API(略过)
  • 依赖 Promise 实现

    由上可知,不是所有的浏览器都支持的,支持情况大概如下:


iOS 内的所有的浏览器都基于 safari,所以iOS要在11.3以上
IE是放弃支持了,不过Edge好歹支持了。
### 3.2 Cache

Cache是Service Worker衍生出来的API,配合Service Worker实现对资源请求的缓存。

不过cache并不直接缓存字符串,而是直接缓存资源请求(css、js、html等)。

cache也是key-value形式,一般来说key就是request,value就是response

  • caches.open(cacheName) 打开一个cache
  • caches是global对象,返回一个带有cache返回值的Promise
  • cache.keys() 遍历cache中所有键,得到value的集合
  • cache.match(Request|url) 在cache中匹配传入的request,返回Promise;
  • cache.matchAll只有第一个参数与match不同,需要一个request的数组,当然返回的结果也是response的数组
  • cache.add(Request|url) 并不是单纯的add,因为传入的是request或者url,在cache.add内部会自动去调用fetch取回request的请求结果,然后才是把response存入cache;
  • cache.addAll类似,通常在sw install的时候用cache.addAll把所有需要缓存的文件都请求一遍
  • cache.put(Request, Response) 这个相当于cache.add的第二步,即fetch到response后存入cache
  • cache.delete(Request|url) 删除缓存

3.3 注册Service Worker

注册即声明sw文件的位置,显然应该在主js中引入。大概如下:

//基于promise
function registerServiceWorker(){
// 注册service worker
return navigator.serviceWorker.register('./sw1.js').then(registration => {
console.log('注册成功');
// 返回
return registration;
})
.catch(err => {
console.error('注册失败', err);
});
}
window.onload = function () {
//是否支持
if (!('serviceWorker' in navigator)) {
return;
}
registerServiceWorker()
}

3.4 生命周期

Service worker 有一个独立于web 页面的生命周期。

如果在网站上安装 serice worker ,你需要注册,注册后浏览器会在后台安装 service worker。然后进入下面的不同阶段。

激活之后,service worker 将控制所有的页面,纳入它的范围,不过第一次在页面注册 service worker 时不会控制页面,直到它再次加载。

service worker 生效之后,它会处于下面两种状态之一:

  • service worker 终止来节省内存,
  • 页面发起网络请求后,它将处理请求获取和消息事件。

由上图看知,分为这么几个阶段:

  • Installing

    发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存
  • Installed

    Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭。
  • Activating

    在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装
  • Activated

    在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会)。并可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)
  • Redundant

    被替换,即被销毁

了解声明周期其实是为了我们在不同时间段去监听事件来完成相应操作。对PWA来说主要两个事件。

  • install 事件回调:

event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。

self.skipWaiting():self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。

  • activate 回调:

event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。

self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。

const CURCACHE = 'CURCACHE_test_1'
const RUNTIME = 'runtime';
const CURCACHE_URLS = [
'./',
'/asset/sw.jpg',
'index.js'
]
self.addEventListener('install',e=>{
e.waitUntil(
//存储缓存路径对应的资源
caches.open(CURCACHE).then(cache=>{
cache.addAll(CURCACHE_URLS)
}).then(
self.skipWaiting()
)
)
}) //代理请求,使用缓存,请求发送之前
self.addEventListener('fetch', e => {
e.respondWith(
//缓存是否匹配
caches.match(e.request).then(function(response) {
if (response != null) {
//命中缓存返回缓存,结束请求
return response
}
//未命中缓存,正常请求
return fetch(e.request.url)
})
)
});

更新service worker

service worker 更新步骤如下:

  • 更新 service worker 的文件

    网页打开时服务器会进行对比,保持最新
  • 新的 service worker 启动install
  • 当前页面生效的依然是老的service worker,新的 service worker 会进入 “waiting” 状态。
  • 页面关闭之后,老的 service worker 会被干掉,新的 servicer worker 接管页面
  • 新的 service worker 生效后会触发 activate 事件。
const CURCACHE = 'precache_test_1'
//假设上个版本的key为precache_test_2 反正不等于CURCACHE
self.addEventListener('activate', e => {
e.waitUntil(
//遍历当前缓存keys
caches.keys().then(cacheNames=>{
return Promise.all(
cacheNames.map(function(cacheName) {
//是否等于当前key,保留自己
if (cacheName !== CURCACHE) {
return caches.delete(cacheName);
}
})
)}).then(() => self.clients.claim())
)
})

这样一个简单的service worker离线缓存完成了。控制台可以看到,来源是service worker


关闭网络之后再次访问,可以同样得到上面的结果,并且sw.js请求未能拿到,但是不影响,旧的文件依然在,这里证明了每次都回去对比sw文件以确保更新

到这里,离线缓存就实现了。

四、添加到主屏幕

允许将站点添加至主屏幕,是 PWA 提供的一项重要功能。这样就不用再依赖于浏览器作为平台,符合移动端的用户习惯。

manifest.json

需要 manifest.json 文件去配置应用的图标、名称等基本信息如下:

{
//被提示安装应用时出现的文本
"name": "PQJ-PWA",
//添加至主屏幕后的文本
"short_name":"PQJ",
"description": "测试demo",
//添加之后,启动地址
"start_url": "/index.html",
//图标信息
"icons": {
"128": "/asset/sw.jpg"
},
"developer": {
"name": "pqj",
"url": ""
},
"display": "standalone",
"background_color": "#287fc5",
"theme_color": "#fff",
"permissions": {
"desktop-notification": {
"description": "Needed for creating system notifications."
}
}
}

然后以如下方式在html中引入

<link rel="manifest" href="/mainfest.json" />

这样完成之后,移动端安卓使用chrome(亲测),首次访问时会提示是否允许安装到主屏幕,以应用icon的形式出现。

图片和文字即由配置决定。

五、消息通知

消息通知也是使用service worker的通知功能进行的,允许服务器想用户发生通知,而非用户主动请求才去响应某些行为。

正常的通知逻辑需要服务器来参与实现,这次展示只实现功能。

  • 首先申请通知权限
  • 注册service worker
  • 处理逻辑,发送通知
function getPermission(){
return new Promise((resolve, reject) => {
//权限获取
const permissionPromise = Notification.requestPermission(result => {
resolve(result);
});
}).then(result => {
//判断条件
if (result === 'granted') {
execute();
}
else {
console.log('no permission');
}
});
}

发送通知

function execute() {
// 允许之后执行
registerServiceWorker().then(registration => {
// 通知
registration.showNotification('Hello World!');
});
}

结束语

参考文档

https://lavas.baidu.com/doc

https://developer.mozilla.org/zh-CN/Apps/Progressive

至此,本文介绍就结束了,更多请参考实例虽然PWA目前来看,面对的限制还很多,但是也可以看出web组织在更好的提升web应用方向上做的努力。正如一直提到的那句话,未来可期。

目前国内百度这方面做的比较成熟,新浪微博已经有了pwa 测试版。

前端应该了解的PWA的更多相关文章

  1. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  2. 前端性能和加载体验优化实践(附:PWA、离线包、内存优化、预渲染)

    一.背景:页面为何会卡? 1.1 等待时间长(性能) 项目本身包/第三方脚本比较大. JavaScript 执行阻塞页面加载. 图片体积大且多. 特别是对于首屏资源加载中的白屏时间,用户等待的时间就越 ...

  3. 前端资讯周报 3.6 - 3.12: 对学习Javascript最有帮助的三本书,以及HTML标题的迷思

    每周一我都会分享上一周我订阅的技术站点中,和解决问题的过程中阅读到的值得分享的文章. 毕竟个人的阅读量有限,也欢迎大家留言或者私信给我你们阅读到的,对你们前端技术有帮助的任何内容,题材不限,语言不限. ...

  4. 前端资讯周报 3.13 - 3.19: WebVR来了!以及如何优化scroll事件性能

    每周一我都会分享上一周我订阅的技术站点中,和解决问题的过程中阅读到的值得分享的文章.这是迫使我学习的一个动力 本周推荐 Minecraft in WebVR with HTML Using A-Fra ...

  5. 大咖云集!IMWebConf 2017 前端大会即将在深圳盛大开幕

    2017年火热的夏季,一年一度的IMWebConf强势来袭!9月16日,深圳科兴国际会议中心,我们将全心全意打造一场前端盛宴,恭候各位的光临. 作为一名前端老鸟,笔者有幸也参与了本次的大会的主题分享& ...

  6. 记录一次参加D2前端技术论坛的杭州之行

    前言 在这里,闰土首先要感谢以下两位大佬提供的门票,分别是来自新浪微博部门的H同学,以及来自小米科技的D同学. 当我周六晚上在青旅写完这篇文章过后,第二天上网发现,已经有大佬提前一步在掘金上发布了高质 ...

  7. 新事物学习---Chrome上使用PWA

    PWA是什么 PWA(Progressive Web Apps)是 Google 最近在提的一种 Web App 形态 (或者如 Wikipedia 所称的"软件开发方法").PW ...

  8. PWA初体验

    一.前言 现在市面上的Native  APP成千上万个,各种应用商店里面的APP琳琅满目.原生的APP下载到手机上之后,用户就可以获取一个方便的入口,体验上也十分顺畅.但是再好的事物难免有点缺点: 1 ...

  9. 渐进式Web应用(PWA)入门教程(上)

    最近关于渐进式Web应用有好多讨论,有一些人还在质疑渐进式Web应用是否就是移动端未来. 但在这篇文章中我并不会将渐进式APP和原生的APP进行比较,但有一点是可以肯定的,这两种APP的目标都是使用户 ...

随机推荐

  1. Android自制浏览器WebView-android学习之旅(64)

    简单讲解如何使用WebView加载百度的网页 acticity代码 public class MainActivity extends Activity { private WebView webVi ...

  2. 03_Android项目中读写文本文件的代码

    编写一下Android界面的项目 使用默认的Android清单文件 <?xml version="1.0" encoding="utf-8"?> & ...

  3. 【一天一道LeetCode】#71. Simplify Path

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...

  4. Git版本控制 — 日常使用(二)

    本地使用 以下是我的一些日常操作. (1) 创建版本库 # cd /proj # git init Initialized empty Git repository in /proj/.git/ (2 ...

  5. leetcode 169 Majority Element 冰山查询

    Given an array of size n, find the majority element. The majority element is the element that appear ...

  6. 【Unity Shaders】概述及Diffuse Shading介绍

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  7. RDS和ROS使用小结

    微软的RDS和linux下的ROS,都已经使用了一段时间,RDS已经很久不更新了,前景必然不如ROS,但无奈用得顺手,还是偶尔怀旧一下. 使用RDS除了内置的文档需要仔细阅读,有些corobot.pr ...

  8. [译] NSScanner:一个陌生的条件判断利器!

    NSScanner官方文档 NSScanner类是一个类簇的抽象父类,该类簇为一个从NSString对象扫描值的对象提供了程序接口. NSScanner对象把NSString 对象的的字符解释和转化成 ...

  9. "C#":MySql批量数量导入

    现在对数据库(以MySql为例)的操作大多会封装成一个类,如下例所示: namespace TESTDATABASE { public enum DBStatusCode { ALL_OK, MySq ...

  10. 【LaTeX排版】LaTeX使用--入门基础<二>

    1.在命令之后留一个空格有下列方式: 源文件如下: \documentclass[a4paper,12pt]{book}%采用book类型(中篇论文一般采用这种类型),A4纸,字体为12磅,默认为10 ...