赞
踩
原生APP
虽然稳定,但是迭代比较慢,发版的时候需要商店审核,同个功能在不同端需要写两套代码(这里主要是ios
系统和安卓系统),但是随着H5的发展,原生APP
内核中支持用webview
打开H5页面,所以了混合APP
的诞生了。这时候H5
和原生APP
的通信成了需要解决的问题,JSBridge
应运而生。
JSBridge
是一种JS
实现的H5和客户端交流的桥梁的方法统称,解决两者的通信问题,可以让H5
调用原生APP
的API
,如查看相册、扫码等能力,原生APP
可以调用H5
中定义的方法。它更多的是一种称呼,实现方法并不是固定的。
p.s. 以下客户端代码仅供理解大概原理,写法上并不一定完全正确。
这里要区分版本号,在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使用:
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');
先在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()"); } }
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')'
}
上述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
,很容易丢失一些调用。
从上面的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
。
如果错误欢迎指出,感谢阅读~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。