Hybrid App:狭义上是App内嵌WebView组件,再在WebView上使用页面的方案。广义上包括所有App混合方案,包括WebView方案、其他语言2Native方案、等。
随着技术的发展,有新的技术替代
桥协议
,如:JSI、等。以下内容中描述桥协议的部分,都可以用这些新技术替代。
桥协议
:JS和C++互相无感知,只能通过桥协议作为中间层,异步进行序列化/反序列化传输通讯。- JSI:将C++中的常用类型、定义的对象和函数 映射到JS中,支持JS随时调用C++中方法。并且支持其他JS引擎。
WebView种类
1. iOS: 官方:[WKWebView](https://developer.apple.com/documentation/webkit/wkwebview)、[UIWebView](https://developer.apple.com/documentation/uikit/uiwebview)(不推荐) 2. Android: 1. 官方:[WebView](https://developer.android.com/reference/android/webkit/WebView) 2. 第三方:[X5](https://x5.tencent.com/)、[AgentWeb](https://github.com/Justson/AgentWeb)、等
JS引擎:一个专门处理JS脚本的虚拟机
手机中的JS引擎: 1. Android运用的JS引擎:Google的[V8](https://github.com/v8/v8)。 2. iOS运行的JS引擎:Apple的[JavaScriptCore](https://developer.apple.com/documentation/javascriptcore)。 >还有其他JS引擎:SpiderMonkey、Rhino、等。
Hybrid底层依赖Native提供的容器(WebView),上层使用HTML、CSS、JS进行业务开发。
小程序是WebView+Native的双线程模型。
互相调用:
window.前端定义方法
)桥协议
(window.客户端定义方法
)、或触发自定义URL Scheme
(myscheme://客户端定义路径
)资源访问机制
以本地协议file
方式访问Native内部资源。
file:///android_asset/fonts/myFont.ttf
引用。URL
方式访问线上资源(http/https)。增量替换机制(不依赖发包更新)
URL限定,限制访问、跨域问题的解决方案
页面在客户端内打开方式
针对产品功能性页面:
用本地协议file
方式打开客户端包内.html(.js、.css、图片等都在客户端包内)。
file
打开的页面直接发起请求可能会有跨域问题,可以用客户端接口代理的方式请求服务端数据。针对运营活动页面:
用远程URL
方式请求。
身份验证机制
Native创建WebView时,根据客户端登录情况注入跟登录有关的信息(session_id或token)至WebView(可注入到全局对象或cookie)。
cookie有同源策略,所以客户端可以对指定白名单域名添加cookie。
开发测试
与Native通信方式:
都是以字符串(数据用JSON字符串)的形式交互,向客户端传递:
- 全局的方法名 -> 客户端调用
window.方法名(JSON数据)
- 匿名函数 -> 客户端调用
(匿名函数(JSON数据))
- WebView无法判断是否安装了其他App。
- 可以通过
查看注入的全局方法
、或客户端调用回调函数
(、或navigator.userAgent
)来判定页面是否在具体App内打开。桥协议
仅在App内部起作用;自定义URL Scheme
是系统层面,所以可以额外针对跨App起作用(如:分享去其他App);iOS的通用链接可以认为是高级的自定义URL Scheme
。- Native和WebView交互需要时间,对时效性很高的操作会有问题。
桥协议
:Native注入全局方法至WebView的window
,WebView调用则客户端拦截后触发Native行为。
- 客户端注入方式:javascript伪协议方式
javascript: 代码
。- 注入JS代码可以在创建WebView之前(
[native code]
)或之后(全局变量JS注入)。
- 若注入的方法为
undefined
,则认为不在此App内部。
自定义URL Scheme
:拦截跳转(<iframe>
或<img>
设置src
、点击<a>
、window.location.href
),触发Native行为。
是iOS和Android提供给开发者的一种WAP唤醒Native App方式(客户端用DeepLink实现)。Android应用在mainfest中注册自己的Scheme;iOS应用在App属性中配置。典型的URL Scheme:`myscheme://my.hostxxxxxxx`。
URL Scheme
- 客户端可以捕获、拦截任何行为(如:
console
、alert
)。相对于注入全局变量,拦截方式可以隐藏具体JS业务代码,且不会被重载,方便针对不可控的环境。- 有些App会设置允许跳转的其他App的白名单或黑名单(记录其他APP的Scheme),如:微信白名单。
- 除了增加回调函数且被客户端调用,否则无法准确判定是否在此App内部。
- 跨App使用
自定义URL Scheme
,其后面的字符串要产生的行为仅目的App能理解。- 快速触发多次
自定义URL Scheme
,有时仅有最后一个产生效果。e.g. 用window.location.href
快速触发多次,仅有最后一次跳转信息能够传递给客户端。- 想要实现这样的功能:”关闭当前WebView,然后执行某功能”,若用
window.location.href
等触发自定义URL Scheme
方式给客户端拦截,则可能WebView实例关闭后,事件无法被客户端捕获(类似:异步的XMLHttpRequest
会随WebView实例关闭而被忽略)。可以尝试换用桥协议
。
iOS
iOS8-
var iframe = document.createElement('iframe');
iframe.src = '自定义URL Scheme';
iframe.style.display = 'none';
document.body.appendChild(iframe);
setTimeout(function () {
document.body.removeChild(iframe);
}, 3000);
location.href = '下载地址';
iOS9+
<iframe>
无效。
location.href = '自定义URL Scheme';
setTimeout(function () {
location.href = '下载地址';
}, 250);
setTimeout(function () {
location.reload();
}, 1000);
iOS9+的Universal links(通用链接),可以从底层打开其他App客户端,跳过白名单(微信已禁用)
需要HTTPS域名配置、iOS设置等其他端配合。
参考:通用链接(Universal Links)的使用详解、Universal Link 前端部署采坑记、Support Universal Links。
Android
location.href = '自定义URL Scheme'; // 也可以用`<iframe>`
var start = Date.now();
setTimeout(function () { // 尝试通过上面的唤起方式唤起本地客户端,若唤起超时(还在这个页面),则直接跳转到下载页(或做其他未安装App的事情)(浏览器非激活时,定时器执行时间会变慢/主线程被占用,所以会大于定时器时间之后才执行定时器内回调)
if (Date.now() - start < 3100) { // 还在这个页面,认为没有安装App
location.href = '下载地址';
}
}, 3000);
在WebView中通过应用宝页面下载/打开其他APP
Android:应用宝页面支持下载/打开目标APP
android_schema
可以传递信息至目标APP:腾讯系APP打开目标APP后会带着信息;其他APP会先触发一次信息。直接打开在聊天界面的URL,会有诸多限制,如:无法拉起APP、分享的URL不是卡片而是纯文本 等。打开 分享的卡片、二维码扫描 的网址,才有更多页面权限。
拼接应用宝下载/打开目标APP的链接:
https://a.app.qq.com/o/simple.jsp?
跳转参数(search值,在?
后面,用&
分割):
pkgname=
+ com.xx.xxx
ckey=
+ CK1234567890123
目标APP内打开路径(可选):android_schema=
+ 自定义URL Scheme://具体跳转路径
若有一些特殊字符,则可以用
encodeURIComponent
转义属性名和属性值。
e.g. https://a.app.qq.com/o/simple.jsp?pkgname=com.xx.xxx&ckey=xxxx&android_schema=xxxx://xx
- 微信分享在部分系统(低于微信客户端Android6.2)使用
pushState导致签名失败,可查询官方文档;又因为一般是异步加载、配置微信的设置,所以要等待微信第三方文件和接口完成后才能够配置成功(才能够设置成功)。Android的微信、QQ等X5内核可以用http://debugx5.qq.com/打开调试,支持「清除缓存」等操作。
iOS可以在 设置->通用->存储空间 清理缓存。
长按没有
的src
<img>
:
- 在iOS微信WebView,截屏这个
<img>
所在位置;- 其他情况,可能导致保存图片错误、或不能进行保存。
WebView提供给Native调用的全局回调函数(或匿名函数)。
对于动态创建的全局回调函数,要注意同名覆盖问题
e.g. ```javascript let _localCounter = 1 // 同一个方法名快速请求时,可能 Date.now() 还没有变化 function invokeJSBridge (method, arg, { hasCallback = true }) { return new Promise((resolve, reject) => { if (typeof window.客户端定义方法 === 'function') { let callbackName = '' // 创建客户端回调 if (hasCallback) { callbackName = `${method}CallbackName_${_localCounter}` _localCounter += 1 window[callbackName] = (res) => { // todo: 增加定时器处理长时间未被客户端回调的方法 try { resolve({ result: 'ok', data: JSON.parse(res) }) } catch (e) { resolve({ result: 'error', data: res }) } window[callbackName] = null } } // 调用客户端方法 window.客户端定义方法( // 桥协议 method, // 方法名 JSON.stringify(arg || {}), // 传参 callbackName // 回调 ) // 无客户端回调时直接完成 if (!hasCallback) { resolve({ result: 'ok', data: '' }) } } else { reject(arg) } }) } /* 使用测试 */ invokeJSBridge('方法名', '参数') .then((res) => { // 是客户端、且调用成功&&客户端执行回调 // 根据res处理客户端执行之后业务 }) .catch((res) => { // 不是客户端 // 非客户端业务 }) ```接口设计可以带有「透传数据」:前端调用客户端方法时多传一个透传参数,之后客户端异步调用前端方法时带着这个参数的值。
分享到其他App
客户端内的前端页面,添加半屏弹窗策略:
针对通用的逻辑,要给多个前端页面添加半屏弹窗逻辑。如:某个后台接口返回错误码,统一弹出半屏弹窗。
原生实现 VS. 页面实现
Native App
WebView的页面
WebView启动流程
相对于Native App的流畅体验,WebView的页面瓶颈一般都卡在WebView实例初始化,可能导致App卡顿、页面加载缓慢。
WebView的初始化、保持,占用较多内存。
前端编写的代码,通过中间的其他语言2Native
之类的方式转换为原生App代码运行。
如:Dart2Native、React Native2Native、Weex2Native。