Hybrid
hybrid
指前端和客户端的混合开发。客户端承载前端的内容。客户端是前端内容的容器。
hybrid 存在价值
可以 快速 迭代更新 - 核心意义
- 因为
hybrid
是使用前端三大语言开发,权限低,无法获取手机的隐私等安全性信息,那么hybrid
页面更新是 不需要app
审核 的。
- 因为
与
Native
应用体验基本类似开发成本低,双端可以共用一套代码
webview
app
中非必要组件,一类组件的统称。用于加载
HTML5
页面,即一个小型浏览器精简内核。
file 协议
file
协议用于本地文件请求,相较于 http(s)
请求的 速度快 得多。
具体实现
选型
使用
Native
:追求极致体验,变化不频繁的场景。使用
hybrid
:体验要求高,且变化频繁的场景(如新闻详情页)。使用
HTML5
:体验无要求,不常用的页面(如反馈等页面)。
流程
前端做好静态页面(
HTML
、CSS
、JS
),将静态文件移交客户端开发。客户端开发以文件形式嵌入存储在
app
中。客户端在一个
webview
中使用file
协议读取本地静态文件。
更新
app
发布后,如何更新静态文件?因为前端语言都存在于客户端容器内,那么此时的前端语言是没有权限更新静态文件的,只有客户端有权限来完成更新静态文件的操作。
在服务端维护静态文件压缩包和版本号。
在一个时机被触发时(如启动 App 时),客户端主动去请求
server
比对版本号。仅在版本号不一致的情况下下载最新的静态文件压缩包,并在客户端解压,即可得到新的静态文件。
静态页面如何动态获取文字内容(如新闻,作者等内容)?
见本章 - 前端和客户端通信
hybrid 与 HTML5 对比
hybrid
优点可快速迭代更新
与
Native
应用体验基本相似
hybrid
缺点开发成本高。联调、测试、debug 等都有很高的成本。
运维成本高。
适用场景
hybrid
适用于产品型场景,它要求稳定,体验要求高,迭代频繁HTML5
侧重活动运营,如单次运营活动或不常用的功能(如反馈页面)。
前端和客户端通信
静态页面如何动态获取文字内容(如新闻,作者等内容)?
JS 和客户端通信的基本形式
(原理类似 JSONP
跨域解决方案的形式。)
JS 向客户端传递参数和回调函数。
客户端通过回调函数返回内容。
schema
协议简介和使用
在前端中,除了 http(s)
协议与 file
协议,还有 schema
协议是用于前端和客户通信的协议。
/* 格式形如: */
weixin://dl/scan
2
schema
协议的使用逻辑示例:
// 以下省略了权限验证
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
// iframe 通过 `schema` 协议访问客户端
// a. 无参数访问
iframe.src = 'weixin://dl/scan'
// b. 传参访问
window['_wx_scan_callback_'] = function (result) {
// do something
}
iframe.src = 'weixin://dl/scan?keyword=value&callback=_wx_scan_callback_'
// 返回 _wx_scan_callback({ key: value }),即调用 _wx_scan_callback 函数解析其中的数据
const body = document.body || document.getElementsByTagName('body')[0]
body.appendChild(iframe)
// 异步销毁 iframe,防止内存泄漏
setTimeout(() => {
body.removeChild(iframe)
iframe = null
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
schema
使用的封装
function _invoke (type, data, callback) {
// excluding verifying params data and callback validity
// package schema url
const schema = `schema://utils/${type}`
// add params to url
if (data) {
schema = schema + '?'
const keys = Object.keys(data)
const len = keys.length
keys.forEach(dataKey => {
if (dataKey !== keys[len - 1]) {
schema += `${dataKey}=${data[dataKey]}&`
} else {
schema += `${dataKey}=${data[dataKey]}`
}
})
}
const callbackName = ''
if (typeof callback === 'String') {
callbackName = callback
} else {
// declare a unique function in window when param callback is a function
// It will be called when schema respond
callbackName = `${type}${Date.now()}`
window[callbackName] = callback
}
schema += `&callback=${callbackName}`
// package schema url ending
// perform request
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = schema
const body = document.body || document.getElementsByTagNames('body')[0]
body.appendChild(iframe)
// Once schema request has been invoked, we can destroy iframe
// It's used to prevent too many iframe to memory overflow
setTimeout(() => {
body.removeChild(iframe)
iframe = null
}, 0)
}
// expose API
window.invoke = {
scan: function (data, callback) {
_invoke('scan', data, callback)
}
share: function (data, callback) {
_invoke('share', data, callback)
},
}
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
57
58
59
// invoke API
window.invoke.share({}, function (res) {
if (res.errno === 0) {
// do something right
} else {
console.error(`Request fail, errno is ${res.errno}`)
}
})
2
3
4
5
6
7
8
schema
协议调用文件上线
内置 将封装的
schema
协议调用代码打包为invoke.js
,内置到客户端。客户端每次启动
webview
,都默认执行invoke.js
。本地加载免去了网络加载时间
本地加载更安全。因为没有网络请求,防范了因抓包造成的黑客攻击。