JavaScript是运行在一个单独的 JS Context中(例如: webview的webkit引擎,JSCore)
本位主要总结下 JSBridge 前端实现原理,来自工作中的总结,安卓/ios代码仅为示意JSBridge 是广为流行的Hybrid 开发中JS和Native一种通信方式,简单的说,JSBridge就是定义Native和JS的通信,Native只通过一个固定的桥对象调用JS,JS也只通过固定的桥对象调用native,
jsBridge
两种交互方式注入api
、url schema
、重写h5全局方法
注入api的形式
安卓操作方法
native调用js
// 安卓4.4版本之前,无法获取返回值 // mWebView = new WebView(this); // 即当前webview对象 mWebView.loadUrl("javascript: 方法名('参数,需要转为字符串')") // 安卓4.4及以后 // webView.evaluateJavascript("javascript:if(window.callJS){window.callJS('" + str + "');}", new ValueCallback<String>() { mWebView.evaluateJavascript("javascript: 方法名,参数需要转换为字符串", new ValueCallback() { @Override public void onReceiveValue(String value) { // 这里的value即为对应JS方法的返回值 } }) // js 在全局window上声明一个函数供安卓调用 window.callAndroid = function() { console.log('来自中h5的方法,供native调用') return "来自h5的返回值" } /** 总结: 1. 4.4 之前Native通过loadUrl来调用js方法,只能让某个js方法执行,但是无法获取该方法的返回值 2. 4.4 之后,通过evaluateJavaScript异步调用js方法,并且能在onReceive中拿到返回值 3. 不适合传输大量数据 4. mWebView.loadUrl("javascript: 方法名") 函数需在UI线程运行,因为mWebView为UI控件,会阻塞UI线程 */
JS调用Native
// 安卓环境配置 WebSettings webSettings = mWebView.getSettings(); // Android容器允许js脚本,必须要 webSettings.setJavaScriptEnabled(true); // Android 容器设置侨连对象 mWebView.addJavascriptInterface(getJSBridge(), "JSBridge"); // Android中JSBridge的业务代码 private Object getJSBridge() { Object insterObj = new Object() { @JavascriptInterface public String foo() { // 此处执行 foo bridge的业务代码 return "foo" // 返回值 } @JavascriptInterface public String foo2(final String param) { // 此处执行 foo2 方法 bridge的业务代码 return "foo2" + param; } } return inserObj; } // js调用原生的代码 // JSBridge 通过addJavascriptInterface已被注入到 window 对象上了 window.JSBridge.foo(); // 返回 'foo' window.JSBridge.foo2(); // 返回 'foo2:test' // 注意:在安卓4.2之前 addJavascriptInterface有风险,hacker可以通过反编译获取Native注册的Js对象,然后在页面通过反射Java的内置 静态类,获取一些敏感的信息和破坏
ios 操作方法
js 调用native
// 注意:ios7 以前 js无法调用native方法,ios7之后可以引入第三方提供的 JavaScriptCore 库 /* 总结: 1. ios7 才出现这种方式,在这之前js无法直接调用Native,只能通过JSBridge方式调用 2. JS 能调用到已经暴露的api,并且能得到相应返回值 3. ios原生本身是无法被js调用的,但是通过引入官方提供的第三方“JavaScriptCore”,即可开发api给JS调用 */ // WKWebview ios8之后才出现,js调用native方法 // ios 代码配置 https://zhuanlan.zhihu.com/p/32899522 // js调用 window.webkit.messageHandlers.{name}.postMessage(msgObj); /* * 优缺点 ios开发自带两种webview控件 UIWebview(ios8 以前的版本,建议弃用)版本较老, 可使用JavaScriptCore来注入全局自定义对象 占用内存大,加载速度慢 WKWebview 版本较新 加载速度快,占用内存小 */
native 调用 js
// UIWebview [webView stringByEvaluatingJavaScriptFromString:@"方法名(参数);"]; // WKWebview [_customWebView evaluateJavaScript:[@"方法名(参数)"] completionHandler:nil]; -------------------- // js 调用 native // 引用官方库文件 UIWebview(ios8 以前的版本,建议弃用) #import <JavaScriptCore/JavaScriptCore.h> // webview 加载完毕后设置一些js接口 -(void)webViewDidFinishLoad:(UIWebView *)webView{ [self hideProgress]; [self setJSInterface]; } -(void)setJSInterface{ JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // 注册名为foo的api方法 context[@"foo"] = ^() { //获取参数 NSArray *args = [JSContext currentArguments]; NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]]; //做一些自己的逻辑 返回一个值 'foo:'+title return [NSString stringWithFormat:@"foo:%@", title]; }; } window.foo('test'); // 返回 'foo:test'
url scheme 介绍
url scheme
是一种类似于url的链接,是为了方便app直接互相调用设计的:具体为:可以用系统的OpenURI
打开类似与url的链接(可拼入参数),然后系统会进行判断,如果是系统的url scheme
,则打开系统应用,否则找看是否有app注册中scheme,打开对应app,需要注意的是,这种scheme必须原生app注册后才会生效,如微信的scheme为weixin://
- 调用过程(如用
iframe.src
),然后native用某种方法捕获对应的url触发事件,然后拿到当前触发url
,根据定好的协议(scheme://method/?params=xxx
),然后native拦截该请求分析当前触发了哪种方法,然后根据定义来实现 客户端捕获url
- 安卓捕获 url scheme:
shouldoverrideurlloading
捕获到url进行分析 - ios: 在
UIWebView WKWebview
内发起的所有网络请求,都可以通过 delegate函数在native层得到通知,通过shouldStartLoadWithRequest
捕获webview中触发的url scheme
- 安卓捕获 url scheme:
- 大致流程:
h5 --> 通过某种方式触发一个url --> native捕获到url,进行分析 -->原生做处理 --> 如果需要回调 native再调用h5的JSBridge对象传递回调
- 缺点:速度可能稍慢一点,url长度会有限制,需要定义url结构解析较为复杂
相较于注入api形式有以下有优点:
- Android4.2 一下,addJavaScriptInterface方式有安全漏洞
- ios7以下,js无法调用native
- url scheme交互方式是一套现有的成熟方案,可以兼容各种版本
重写prompt/alert等原生方法
- native会劫持webview
onJsAlert、onJsConfirm、onConsoleMessage、onJsPrompt
并进行重写,好像ios高版本对此方式做了限制
设计实现一个JSBridge
- 设计出一个native与js交互的
全局桥对象
- js如何调用native
- native如何调用js
- h5中api方法
send
/register
// 名称: JSBridge 挂在 window上的一个属性
window.JSBridge = {
// ...其他属性,比如:版本、app基础信息
// 回调函数集合
_cbMap: {},
// js注册方法,native主动发起调用
register(method, callback) {
const callbackId = method
this._cbMap[callbackId] = callback
},
// h5 调用native方法,调用时会将回调 id 存放到本地变量cbList中
send(method, data, callback) {
let callbackId
if (callback) {
callbackId = `${method}_${Date.now()}`
this._cbMap[callbackId] = callback
}
const params = {
method, // 和客户端约定好定为method字段,即bridge名称
callback: callbackId,
data: {} // 业务参数
}
// 调用native将参数传递进去 ==> 通信方式以上三种可任意选择
callNative(JSON.Stringify(params))
},
// native回调js的方法 obj: { 回调id, 回调数据 }
// 相当于native只调用此方法,参数为json字符串
handler(obj) {
const { callbackId, data } = JSON.parse(obj)
// 执行对应的回调函数即send传进来的callback,如果要返回值,可再发一个send
this._cbMap[callbackId] && this._cbMap[callbackId].(data)
}
}
// 调用示例
// 主动发消息
JSBridge.send('ui.callNative', {}, (data) => data)
// 注册在本地,被动接受客户端调用
JSBridge.register("ui.datatabupdate", (data) => data);
JSBridge.send
内部的callNative
的具体实现
url schema方式
// url schema 实现 // 变成字符串并编码 var url = scheme://ecape(JSON.stringify(param)) // 使用内部创建好的iframe来触发scheme(location.href = 可能会造成跳转问题) var iframe = document.createElment('iframe'); iframe.src = url; document.head.appendChild(iframe); setTimeout(() => document.head.removeChild('iframe'), 200)
api注入方式
// ios window.webkit.messageHandlers.{name}.postMessage(JSON.stringify(params)) // 安卓 window.{name}.{name}(JSON.stringify(params))
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。