背景
原生APP
虽然稳定,但是迭代比较慢,发版的时候需要商店审核,同个功能在不同端需要写两套代码(这里主要是ios
系统和安卓系统),但是随着H5的发展,原生APP
内核中支持用webview
打开H5页面,所以了混合APP
的诞生了。这时候H5
和原生APP
的通信成了需要解决的问题,JSBridge
应运而生。
定义
JSBridge
是一种JS
实现的H5和客户端交流的桥梁的方法统称,解决两者的通信问题,可以让H5
调用原生APP
的API
,如查看相册、扫码等能力,原生APP
可以调用H5
中定义的方法。它更多的是一种称呼,实现方法并不是固定的。
APP与H5通信
p.s. 以下客户端代码仅供理解大概原理,写法上并不一定完全正确。
安卓
安卓调用H5中的方法
这里要区分版本号,在4.4版本之前:
// 获得当前的WebView对象
mWebView = new WebView(this);
// 调用H5中的方法
mWebView.loadUrl("javascript:方法名('参数1, 参数2...')");
如果是4.4版本之后:
mWebView = new WebView(this);
mWebView.evaluateJavascript("javascript:方法名('参数1, 参数2...')", new ValueCallback(){
@Override
// 当H5的方法触发完毕之后,就会调用这个方法,value即H5方法的返回值
public void onReceiveValue(String value) {
}
})
从上面不能看出4.4版本之后的调用多了个回调函数结果的返回~
H5调用安卓中的方法
首先需要先在安卓中定义一些方法供H5使用:
private Object getJSBridge(){
Object insertObj = new Object(){
// @JavascriptInterface
public String foo(){
return 'foo';
}
// @JavascriptInterface
public String foo2(final String param){
return 'foo2:' + param;
}
}
// 定义了一个实例化对象,内含两个方法
return insertObj;
}
// 获取WebView的设置对象
WebSettings webSettings = mWebView.getSettings();
// 设置Android运行JS脚本
webSettings.setJavaScriptEnabled(true);
// 暴露一个叫做JS的对象到WebView的全局环境
mWebView.addJavascriptInterface(getJSBridge(), 'JSBridge');
接下来在H5
中调用:
window.JSBridge.foo();
window.JSBridge.foo2('test');
IOS
IOS调用H5中的方法
先在H5
中定义函数:
function changeBgColor(){
document.body.style.backgroundColor = 'pink';
}
然后在IOS
中调用:
class ViewController: UIViewController, WKNavigationDelegate {
override func viewDidLoad(){
super.viewDidLoad();
// 让当前页面加载一个webview
let webView = WKWebView(frame: self.view.bounds);
webView.navigationDelegate = self;
self.view.addSubview(webView);
// 让webview加载我们指定的html
let url = URL(string: "http://xxxx.com/index.html");
let request = URLRequest(url: url!);
webView.load(request);
}
// 当webview加载完成后触发
func webView(_webView: WKWebView, didFinish navigation: WKNavigation!){
// 调用H5中的代码
webView.evaluateJavascript("changeBgColor()");
}
}
H5调用IOS中的方法
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
override func viewDidLoad(){
// ...
// 为webview添加方法名检测
wbeView.configuration.userContentController.add(self as WKScriptMessageHandler , name: "useNative");
}
// ...
// 监听H5
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("传来的数据源:", message.body);
}
}
在H5
中调用:
document.querySelector('input').onclick = function(){
// window.webkit.messageHandlers.【在IOS中定义的方法名】.postMessage
window.webkit.messageHandlers.useNative.postMessage('hello world')'
}
URL Scheme
上述APP
调用H5
方法的行为可以叫做API
注入,其实还有另一一种方法,借助的是URL scheme
。
URL Scheme
是一种类似于url
的链接,是为了方便app
直接互相调用设计的,形式和普通的 url
近似,但是可以自定义。比如微信的 scheme
以 'weixin://'
开头。
我们可以在H5
中跳转定义好的URL Scheme
,然后在原生APP
中拦截他们做出相应的反应。以IOS
和H5
为例子:
document.querySelector('input').onclick = function(){
// 创建iframe
const iframe = document.createElement('iframe');
// 设置URL scheme
iframe.src = "armouy://click";
// 设置尺寸
iframe.style.width = 0;
iframe.style.height = 0;
// 添加到页面上
document.body.appendChild(iframe);
// 移除
iframe.parentNode.removeChild(iframe);
}
在IOS
中进行拦截:
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
override func viewDidLoad(){
// ...
}
// ...
// 监听H5
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, descisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// 获取URL
let url = navigationAction.request.url?.absoluteString;
if(url === "armouy://click") {
// 做一些拦截处理
// do someting
descisionHandler(.cancel);
}else {
descisionHandler(.allow);
}
}
}
这种方式是早期使用的,优点是支持IOS6,缺点也很明显
frame
的链接长度有限制(有些方案为了规避url
长度隐患的缺陷,在iOS
上采用了使用Ajax
发送同域请求的方式,并将参数放到head
或body
里。这样,虽然规避了url
长度的隐患,但是WKWebView
并不支持这样的方式。);- 创建请求会有耗时
不使用
locaiton.href
代替iframe.src
的原因是:如果通过location.href
连续调用Native
,很容易丢失一些调用。
JSBridge简易封装
从上面的API
注入方式来看,还是不太灵活,所以在实际开发中,我们会封装一些代码:
// 是否是APP
const isApp(){
const agent = window.navigator.userAgent.toLowerCase();
// 这里要根据实际项目写正则去判断
return true;
}
// 是否是IOS
const isIos(){
return /ios|iphone|ipad|ipod/.test(window.navigator.userAgent.toLowerCase())
}
// 是否是安卓
const isAndroid(){
return /android/.test(window.navigator.userAgent.toLowerCase())
}
// 判断客户端是否注册了该方法
function canInvoke(method){
try {
// 这里的window.JSBridge不是固定的,取决于公司项目,我们前面定义的就是JSBridge
if (this.isAndroid) return !!window.JSBridge[method]
if (this.isIos) return !!window.webkit.messageHandlers[method]
return false
} catch (error) {
console.warn('canInvoke-客户端未初始化jsBridge', error)
return false
}
}
// H5给APP发送信息
function postMessageToApp(options){
try {
const { method, data = {} } = options
if (this.isIos) {
window.webkit.messageHandlers[method].postMessage(data)
} else if (this.isAndroid) {
// 这里的window.JSBridge不是固定的,取决于公司项目,我们前面定义的就是JSBridge
window.JSBridge(JSON.stringify(data))
}
} catch (error) {
console.warn('postMessageToApp', error, options.method)
}
}
// H5调用APP方法
function invokeNative(options) {
if (!this.isApp) return console.warn('invokeNative', '非app环境')
// 如果客户端已经注册了这些方法,那么就直接调用
if (this.canInvoke(options.method)) {
postMessageToApp(options);
}else {
// ...
}
}
// 这里的场景是H5调用APP的方法,并希望APP调用方法之后,通知H5,执行H5的回调方法
(function(){
let id = 0;
const callbacks = {};
window.JSBridge = {
// 调用原生方法
invoke: function(method, callback, data = {}) {
// 唯一ID
const thisId = id++;
callbacks[thisId] = callback;
// 通过原生提供的对象调用原生功能
invokeNative({
method,
data,
callbackId: thisId
})
},
// 接收原生消息
receiveMessage: function(msg){
const method= msg.method;
const data = msg.data || {};
const callbackId = msg.callbackId;
if(callbackId && callbacks[callbackId]) {
callbacks[callbackId](data);
}else {
// ...
}
}
}
})()
总结
理解JSBridge
的作用,并粗略地介绍了APP
和H5
之间通信机制,最后封装了一个简易的JSBridge
。
参考链接
如果错误欢迎指出,感谢阅读~