PWA以及Hybrid开发方案简介

PWA

PWA(Progressive Web App)渐进增强的Web App。最早提出在2015年,它最初的设计理念是,保留Web的精髓,让Web逐渐演进成App,而非现在Hybrid App(即现在最常用的UIWebView/WebView+前端)形式。

  • 可安装性
  • 离线能力
  • 推送能力

在PWA的概念下,网页可以被添加到主屏同时支持全屏运行,在Service Worker帮助下可以离线运行,最后它仍是Web而并不用添加到App Store中。

说到Service Worker很多人可能会想到Web Worker的概念。这两个看起来是包含关系的概念实际上有区别。

  • Web Worker是JS多线程的一种实现方式,借助它可以让脚本在后台运行,worker对象和主线程通过message的方式交流,caniuse上的支持度为93%
  • Service Worker是浏览器的一个新特性,配合PWA的概念一起使用,是PWA网络请求的代理,结合缓存管理等方案,提供很好的离线体验,caniuse支持度仅有73%

一个介绍ppt上展示了具体的区别:

  • 和tab的关系,Web Worker是一tab对多Web Worker,Service Worker则是多对一
  • 生命周期,Web Worker和选项卡同生共死,Service Worker则是完全独立的
  • 擅长场景,Web Worker用在多线程协同,Service Worker则可以提供很好的离线体验
  • 为保证安全Service Worker要求scheme为https

Service Worker通过navigator.serviceWorker.register('path').then的方式注册,之后便能通过监听事件拿到所有scope里发生的请求,当然,可以在path后的第二个参数中显式地声明作用域(如{scope: '/js'})。Service Worker可以监听它声明周期中的各事件

  • Install 发生在第一次注册和sw.js(这里的文件名只是举个例子)改变时,通常在这个阶段设定SW的初始状态和准备好缓存。缓存可以借助caches API完成。
  • Fetch 发生在网络请求产生时,任何匹配了Request的网络请求都会被拦截,并返回缓存数据。只有找不到存在的缓存,才会产生一个请求
  • Activate 发生在SW更新或网页关掉再重新打开时,触发在install之后
  • Sync 发生在用户有网络时,用在用户进行依赖网络的操作时,会推迟到有网络时再执行。简单来说,所有的依赖网络的操作,都需要使用sync事件

除了Service Worker,Manifest也是很重要的一部分。它用来描述应用程序的各种信息。它包括下面一些成员

  • background-color 在css加载前用作应用背景颜色
  • name 应用名,short_name也是类似意思
  • description 应用描述
  • display 显示模式,有fullscreen, standalone, minimal-uibrowser几种可以选择
  • icons 应用图标,数组类型,每项包含src, typesizes几个属性
  • orientation 默认的屏幕朝向

这里有一个收集PWA酷站的地方。

Hybrid方案相关

离线包管理方案:

  1. 本地开发测试,提交特性分支到远端,
  2. 通过提MR的方式合并在当前迭代分支上,触发basement自动CI为zip格式,根据当前发包的状态,传递给NebulaMng管理
  3. NebulaMng基于zip生成版本号和配置文件,构建整个离线包,并推送给应用中心
  4. 应用中心负责向客户端推送更新
  5. 客户端根据策略拉取离线包、解压、渲染

离线包本地渲染方案:

  1. 加载公共资源包
  2. 判断本地是否已安装该离线包,若有,则加载到内存,否则触发离线包下载
  3. WebView加载离线包url链接,加载前检查内存中是否存在页面数据,若有,从内存中取出并渲染,否则fallback到线上cdn地址

离线包更新方案:

  1. 应用中心广播或服务端发sync消息触发
  2. 向wapcenter获取当前客户端下所有包信息
  3. 在本地没有当前版本且WiFi条件或auto_install为1时更新本地包

双向通信和JSBridge原理:

  • WebView在载入页面时,注入JSBridge脚本。通过调用JSBridge.call,触发调用参数的序列化,并调用console.log(h5container.message:xxx)window.prompt事件。WebView监听页面的console或prompt事件,解析传递的参数信息,然后通过NebulaService分发事件
  • Service、Session、Page实例化时,内部都有一个H5PluginManager成员,通过类似EventEmitter的形式存储着一个action -> plugin的map。每个plugin都有interceptEvent和handleEvent两个函数,处理事件的拦截和处理两个阶段
  • WebView通过loadUrl(“javascript:JsBridge.callback”)的形式输入结果并运行回调

实现上类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
;//JsBridge
(function(window) {

var callbackList = {};

window.JsBridge = {
/* 参数说明
* @evt {string} 调用接口名称 (必须)
* @params {object} 配置参数 (可选)
* @callback {function} 回调函数 (可选)
*/
call: function(evt, params, callback) {
//第一个参数必须为string
if(typeof evt != 'string') return;

if(typeof params == 'function') {
callback = params;
params = null;
} else if(typeof params != 'object') {
params = null;
}

var callbackId = new Date().getTime() + '';
if (typeof callback == 'function') {
callbackList[callbackId] = callback;
}

var msg = {
callbackId: callbackId,
action: evt,
data: params || {}
};
prompt('JsBridgeCall', JSON.stringify(msg));
},
/* 参数说明
* @params {object} 返回的数据 (必须)
* 数据示例:{ callbackId: 'xxx', data: '' }
*/
callback: function(params) {
// params = JSON.parse(params);
var callbackId = params.callbackId,
data = params.data,
callbackHandler = callbackList[callbackId];
callbackHandler && callbackHandler.call(null, data);
delete callbackList[callbackId]; //删除回调
}
}
})(window)


;//JsBridgeReady
(function(document) {
var evt = document.createEvent('HTMLEvents');
evt.initEvent('JsBridgeReady', false, false);
document.dispatchEvent(evt)
})(document);

native和H5混合方案:

  • 在RootView中创建离线包View再异步添加进来
  • 通过JSBridge进行交互
  • 提前拦截touch事件,防止冲突