Hybrid开发中,web页面每每会跟native进行交互,而JSBridge就是web页面和native进行通讯的桥梁,经过JSBridge能够实现web调用native的方法,native能够经过
webview.loadUrl
之类的方法,将javascript:xxx
代码放在页面执行,这有点相似在浏览器地址栏直接输入:javascript:xxx
javascript
本文较长,先把目录列出来:html
web和native进行通讯,方法有不少,接下来一一列举一下JSBridge的多种形式,及其利弊。java
JSInterface是安卓4.2-官方推荐的解决方案,原理是经过WebView提供的addJavascriptInterface
方法给浏览器window
注入一个命名空间,而后给Web增长一些能够操做Java的反射。node
// Android java代码
mWebView.addJavascriptInterface(new Class(), 'android');
public class Class(){
@JavascriptInterface
public void method(){
}
}
// js 代码
window.android.method();复制代码
JSInterface在4.2以前的版本均可以,可是存在严重的安全隐患,容易被利用提权,从而调用各类Java的类和权限,甚至页面能够挂马。在咱们实际产品(手机百度)开始阶段,用过这个方法,不过如今已经不使用了。android
这个方法主要是经过修改原来浏览器的window
某些方法,而后拦截固定规则的参数,而后分发给Java对应的方法去处理。这里经常使用的是如下四个方法:ios
onJsAlert
监听onJsConfirm
监听onConsoleMessage
监听onJsPrompt
监听prompt简单举例说明,Web页面经过调用prompt()
方法,安卓客户端经过监听onJsPrompt
事件,拦截传入的参数,若是参数符合必定协议规范,那么就解析参数,扔给后续的Java去处理。这种协议规范,最好是跟iOS的协议规范同样,这样跨端调起协议是一致的,但具体实现不同而已。好比:hybrid://action?arg1=1
这样的协议,而其余格式的prompt
参数,是不会监听的,即除了hybrid://action?arg1=1
这样的规范协议,prompt
仍是原来的prompt
。web
这四个方法也是各有利弊,好比:json
alert
/console.log
是调试最经常使用的,若是你要看看协议是否是写错了,可是传入协议却被拦截了。。confirm
和prompt
都带返回值,prompt
是四个里面惟一能够自定义返回值,能够作同步的交互,要比写各类回调更「顺」,可是一旦串行调用了,就要骂爹了prompt
是咱们目前安卓用的比较多的JSBridge解决方案。api
这个叫法不是特别贴切,scheme是URI的一种格式,上文提到的hybrid://action?arg1=1
就是一个scheme协议,这里说的scheme(或者schema)泛指安卓和iOS的schema协议,由于它通用。浏览器
安卓和iOS均可以经过拦截跳页URL请求,而后解析这个scheme协议,符合约定规则的就扔个Native的方法处理。安卓和iOS分别用到拦截URL请求的方法是:
上文介绍到的JSBridge是在APP内的Web页面跟APP进行交换,还有一种特别多的需求,就是在APP外(浏览器、微信等)调起APP本身,给APP进行导流。这时候就要用到APP的唤起技术。这里有一下几种方法:
intent格式示例以下:
intent:
HOST/URI-path // Optional host
#Intent;
package=[string];
action=[string];
category=[string];
component=[string];
scheme=[string];
S.xxx=xxx
end;复制代码
由于Intent不只仅是调起APP,而是安卓客户端内部模块通讯也会用,因此权限很大,通常浏览器都给封掉了,💔
这是一个黑科技😈,早前安卓容许在本地启动一个本地server,这个server是在后台守候的,经过这个localserver均可以进行各类需求:app间通讯、app调起、收集数据、基础服务。百度的moplus就是这样的一个localserver。
举例说明:启动一个本地server,端口号是:8888
,那么在手机上,网页就能够经过:http://127.0.0.1:8888
访问这个server,server接收到请求就能够进行一些native的操做,对于须要回调数据的,就经过返回请求内容来执行,好比:
$.get('http://127.0.0.1:8888/getGeoLocation?callback=cbname')
window.cbname&&cbname({xxx})
给页面返回定位数据若是控制很差权限,由于localserver是一直后台守候的,容易被利用,好比提权获取通信录、甚至给通信录发短信、容易形成蠕虫攻击,感兴趣的能够搜下moplus的文章。另外安卓各类安全软件,都会清理内存和后台程序,很容易被干掉进程。浏览器也会封杀本地server调起,遇见127.0.0.1的请求就直接拦截。
这三个是官方推荐的调起方法,调起协议格式也是能够统一的,好比前文提到的hybrid://action?arg1=xxx
这类scheme协议就是。这样能够统一安卓和iOS调起和JSBridge通讯。
其实简单来讲,这三个出发点是想给应用作分发,可是若是用户手机没有安装这个APP,那么就调起失败,这时候直接无论不问,确定体验很差,并且浪费了点击资源,那么作成分发吧!将调起协议作成一个调起页面,放到一个域名下,点击这个URL就能够打开这个页面,页面执行代码调起APP,若是调起失败就展示APP的介绍,作分发。
Universal links / Deep link / Applink,就是这样的一个过程,经过这个域名受权,把URL分发给APP进行处理,惟一不一样的是:若是用户安装了APP,那么就不用打开这个分发页面了。
iOS 9新出的一个功能,须要在App内声明一个https域名(ul.test.com),而后在该网站根目录放置apple-app-site-association文件,文件指明了转发规则,例如:
{
"applinks": {
"apps": [],
"details": [
{
"appID": “xxx.com.baidu.SomeApp”,
"paths": ["*"]
}
]
}
}复制代码
当APP安装成功以后,会下载这个文件,明确知道碰见ul.test.com
的域名的URL时候,会把这个URL扔给你的APP,让你去解析,APP拿到这个URL就能够解析出来须要作什么事情。
Universal Link是iOS 9+的底层实现,因此在任何地方均可以直接调起APP,不受微信这类封闭APP的限制。
Deep link 是安卓一开始推出的,主要用于搜索调起APP,后来推出 Applink,实际是Deep link的升级版。
这里须要提到微信的APPlink,毕竟微信做为SuperApp,是很大的分发资源,微信有本身的分发方法,安卓内能够申请微信的APPlink,跟Universal link同样,也是一个域名下面的URL,符合必定规则就由微信(ios是底层系统)扔个对应的域名APP进行解析。
在页面的head中添加下面meta,在Safari浏览器中就会出现下面的banner
<meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL">复制代码
在APP内JSBridge能够实现Web和Native的通讯,可是若是APP打开一个恶意的页面,页面能够任意调用JSBridge方法,获取各类隐私的数据,就会引发安全问题。
JSBridge的安全有两个方法:经过Native进行白名单配置,经过Server云端受权。Server的云端受权这块,放到后续JSSDK的设计部分进行详细讲解。本文主要说下经过Native的方式来控制JSBridge的安全。
假设JSBridge的协议格式以下:
hybrid://action/method?arg1=xxx&arg2=xxx复制代码
能够经过下面方式进行安全设置:
*
,hao123.com可使用:hybrid://hao123/*
介绍了这么多,什么是最佳实践的JSBridge呢?结合文章内容,要求JSBridge作到如下几点:
综合上文介绍的内容,JSBridge的最佳实践是:
hybrid://action/method?arg1=xxx&arg2=xxx
先贴个URL scheme的图片,理解下URL的组成部分:
约定咱们的规范以下:
yourappscheme://module/action?arg1=x&arg2=x&ios_version=xxx&andr_version=xxx&upgrade=1/0&callback=xxx&sendlog=1/0复制代码
?
后面是querystring,这里预约了几个特殊的参数:
ios_version/andr_version
:非必须,iOS和安卓的最小版本,即本协议从哪一个版本开始支持的,低版本不支持则忽略,配合upgrade使用进行APP升级举例:
# 帐号相关
## 打开用户我的主页
fb://account/userprofile?id=xxx
## 打开登陆界面
fb://account/login?callback=xxx
# 工具类
## 获取定位
fb://utils/getgeolocation?callback=xx复制代码
当native操做成功以后,会将处理结束后的结果或者数据经过callback
回调传给Web,固然有成果就又失败,callback
的参数设计有两种方式:
即下面的回调方法格式:
function callback(error, data){
if(error){
throw error
}
console.log(data)
}复制代码
即回调方法只接收一个JSON对象,JSON格式以下:
{
error_code: 0,
data: {}
}复制代码
作APP开发常常会碰见下面的问题:
scheme的querystring部分由 ios_version/andr_version
和upgrade这三个成对的参数,能够解决升级问题,sendlog解决日志统计问题。
ios_version/andr_version
:是标示该协议的最低支持版本,若是低于这个版本可能由于功能并未实现而能识别。简单封装下JSBridge调用的方法,参数以下:
具体代码以下
function invoke (module, action, args, callback) {
let scheme = `yourappscheme://${module}/${action}?`
if (isFunction(args)) {
callback = args
args = null
}
// 处理下参数
if (isString(args)) {
scheme += args
} else if (isObject(args)) {
each(args, (k, v) => {
if (isObject(v) || isArray(v)) {
v = JSON.stringify(v)
}
scheme += `${k}=${v}`
})
}
// callback独立传,方便全局函数名命名
if (isFunction(callback)) {
var funcName = '_jsbridge_cb_' + getId()
window[funcName] = function () {
callback.apply(window, ([]).slice.call(arguments, 0))
}
scheme += (!~scheme.indexOf('?') ? '&' : '?') + `callback=${funcName}`
}
if (os.ios && versionCompare(os.version, '9.0') >= 0) {
window.location.href = scheme
} else {
var $node = document.createElement('iframe')
$node.style.display = 'none'
$node.src = scheme
var body = document.body || document.getElementsByTagName('body')[0]
body.appendChild($node)
setTimeout(function () {
body.removeChild($node)
$node = null
}, 10)
}
}复制代码
今天写的有点多,介绍了JSBridge经常使用的方法,而后介绍了APP外如何唤起APP,还介绍了scheme协议,最后比较了优缺点,作个最佳实践。但愿有用~
--eof--
@三水清
未经容许,请勿转载
本文使用MPEditor(js8.in/mpeditor)编辑…
感受有用,欢迎关注个人公众号