本位主要总结下 JSBridge 前端实现原理,来自工做中的总结,安卓/ios代码仅为示意
JavaScript是运行在一个单独的 JS Context中(例如: webview的webkit引擎,JSCore)
// 安卓4.4版本以前,没法获取返回值 // mWebView = new WebView(this); // 即当前webview对象 mWebView.loadUrl("javascript: 方法名('参数,须要转为字符串')") // 安卓4.4及之后 mWebView.evaluateJavascript("javascript: 方法名,参数须要转换为字符串", new ValueCallback() { @Override public void onReceiveValue(String value) { // 这里的value即为对应JS方法的返回值 } }) // 总结: 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; } // html 中 js调用原生的代码 // JSBridge 经过addJavascriptInterface已被注入到 window 对象上了 window.JSBridge.foo(); // 返回 'foo' window.JSBridge.foo2(); // 返回 'foo2:test' 注意:在安卓4.2以前 addJavascriptInterface有风险,hacker能够经过反编译获取Native注册的Js对象,而后在页面经过反射Java的内置 静态类,获取一些敏感的信息和破坏
// 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]; }; } // js 调用原生代码 window.foo('test'); // 返回 'foo:test' // 注意: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.JSBridge.postMessage(msgObj); ios开发自带两种webview控件 UIWebview(ios8 之前的版本,建议弃用) 版本较老 可以使用JavaScriptCore来注入全局自定义对象 占用内存大,加载速度慢 WKWebview 版本较新 加载速度快,占用内存小 js使用全局对象window.webkit.messageHandlers.{NAME}.postMessage 来调用native的方法
JSBridge 是广为流行的Hybrid 开发中JS和Native一种通讯方式,简单的说,JSBridge就是定义Native和JS的通讯,Native只经过一个固定的桥对象调用JS,JS也只经过固定的桥对象调用native,基本原理是:javascript
h5 --> 经过某种方式触发一个url --> native捕获到url,进行分析 -->原生作处理 --> native 调用h5的JSBridge对象传递回调html
上面咱们看到native已经和js实现通讯,为何还要经过url scheme 的这种jsBridge方法呢前端
weixin://
具体位置app不会注册对应的scheme,而是由前端页面经过某种方式触发scheme(如用 iframe.src
),而后native用某种方法捕获对应的url触发事件,而后拿到当前触发url
,根据定好的协议(scheme://method...
),分析当前触发了哪一种方法,而后根据定义来实现java
1. 设计出一个native与js交互的`全局桥对象` 2. js如何调用native 3. native如何得知api被调用 4. 分析 url 参数和回调的格式 5. native如何调用js 6. h5中api方法的注册以及格式
// 名称: JSBridge 挂在 window上的一个属性 var JSBridge = window.JSBridge || (window.JSBridge = {}); /** 该对象有以下方法: registerHandler(String, Function) 注册本地 js 方法,注册后 native可经过 JSBridge调用,注册后会将方法注册到本地变量 messageHandles中 sendHandler(String, JSON, Function) h5 调用原生开放的api,调用后实际上仍是本地经过 url scheme触发,调用时会将回调 id 存放到本地变量responseCallbacks 中 _handleMessageFromNative h5 调用native以后的回调通知 参数为 {reposeId: 回调id, responseData: 回调数据} */ var JSBridge = { // 注册本地方法供原生调用 registerHandler: function(method, cb) { // 会将cb 放入 messageHandlers里面,待原生调用 }, messageHandles: {}, // h5注册方法集合,供native通知后回调调用 // h5 主动调用native,需生成惟一的callbackId sendHandler: function(mathod, data, succCb, errCb) { // 内部经过iframe src url scheme 向native发送请求 // 并将对应的回调注册进 responseCallbacks // native 处理结束后将结果信息通知到h5 经过 _handleMessageFromNative // h5 拿到返回信息处理 responseCallbacks 里对应的回调 }, responseCallbacks: {}, // 回调集合 // native 通知 h5 _handleMessageFromNative: function(message) { // 解析 message,而后根据通知类型执行 messageHandles 或 responseCallbacks里的回调 } } /** 注意: 1. native 调用_handleMessageFromNative通知h5,参数为 json 字符串 2. native 主动调用h5方法时 {methodName: api名, data, callbackId} methodName: 开放api的名称 data: 原生处理后传递给 h5 参数 须要把回调函数的值 return 出去,供native拿到, 或者再发一个 bridge 回去,方法名是 methodNameSuccess,或者严禁掉,方法名为native生产的callbackId */ 如: bridge.register("hupu.ui.datatabupdate", (name) => { if(name) { // 再发一个bridge通知原生tab更新成功,,,method 能够为native生成的 callbackId bridge.send('hupu.ui.datatabsuccess', {}) } });
// sendHandler 执行步骤 1. 判断是否有回调函数,若是有,生成一个回调函数id,并将id,和对应的回调添加放入回调函数集合 responseCallbacks 中 2. 经过特定的参数转换方法,将传入的数据,方法名一块儿拼接成一个 url scheme,以下: var param = { method: 'methodName', data: {xx: 'xx'}, success: 'successId', error: 'errorId' } // 变成字符串并编码 var url = scheme://ecape(JSON.stringify(param)) 3. 使用内部建立好的iframe来触发scheme(location.href = 可能会形成跳转问题) ...建立iframe var iframe = document.createElment('iframe'); iframe.src = url; document.head.appendChild(iframe); setTimeout(() => document.head.removeChild('iframe'), 200)
native 如何得知 api 被调用
ios
shouldoverrideurlloading
捕获到url进行分析window.prompt(url, '')
来触发scheme,而后native经过重写webviewClient
的 onJsPrompt
来获取url,而后解析UIWebView WKWebview
内发起的全部网络请求,均可以经过 delegate函数在native层获得通知,经过 shouldStartLoadWithRequest
捕获webview中触发的url scheme
分析url参数和回调的格式git
native如何调用 js (参照上面的native执行js的方法)github
JSBridge._handleMessageFromNative(messageJSON)
,json格式:{responseId, reponseData}
JSBridge._handleMessageFromNative(param)
,param 格式为 {methodName, data}
,因为是异步不支持批量调用