四、什么是 JS Bridge,它的作用是什么
参考文章:《JSBridge的原理》
4.1 JS Bridge 介绍
JSBridge 简单来讲,主要是 给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用地址位置、摄像头甚至支付等 Native 功能。
JSBridge 就像其名称中的 “Bridge” 的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是 构建 Native 和非 Native 间消息通信的通道,而且是 双向通信的通道。
JSBridge 另一个叫法及大家熟知的 Hybrid app 技术。
所谓 双向通信的通道:
- JS 向 Native 发送消息 :
调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息 :
回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
4.2. JS Bridge 实现原理
参考文章:《Hybrid APP基础篇(四)->JSBridge的原理》
Android 和 iOS 的 JSBridge 实现方式:
4.2.1 基本流程
- H5 页面通过某种方式触发一个
url scheme
; - Native 捕获到
url scheme
,并进行分析和处理; - Native 调用 H5 的 JSBridge 对象传递回调;
原生的 WebView/UIWebView 控件已经能够和 JS 实现数据通信了,那为什么还要 JSBridge呢?
其实使用JSBridge有很多方面的考虑:
- Android4.2以下,
addJavascriptInterface
方式有安全漏掉。 - iOS7以下,JS 无法调用 Native。
url scheme
交互方式是一套现有的成熟方案,可以完美兼容各种版本,对以前老版本技术的兼容。
4.2.1 实现流程(Android 为例)
- 拟定协议,参考 http 制定的协议为:
jsbridge://className:port/methodName?jsonObj
;
className // Android端实现暴露给前端的类名 port // Android返回结果给前端的端口 methodName // 前端需要调用的函数 jsonObj // 前端给Android传递的参数
- 新建 HTML 文件命名为
index.html
, 编写一个button
绑定click
事件;
<button onclick="JSBridge.call( 'bridge', 'showToast', {'msg':'Hello JSBridge'}, function(res){ alert(JSON.stringify(res)) } )"> 测试showToast </button>
- 新建 JS 文件命名为
JSBridge.js
, 第2步中的JSBridge.call
即为调用JSBridge.js
中的call
方法,后面带了四个参数;
call: function (obj, method, params, callback) { console.log(obj+" "+method+" "+params+" "+callback); var port = Util.getPort(); console.log(port); this.callbacks[port] = callback; var uri=Util.getUri(obj,method,params,port); console.log(uri); window.prompt(uri, ""); },
JSBridge.js
中的 call
方法,最后调用了 window.prompt
方法,这个方法就是触发 Android 端 webChromeClient
的回调函数用的。
window.prompt
触发了WebChromeClient
(这个需要使用函数WebView.setWebChromeClient
(new WebChromeClietn()
)进行设定);
类中的如下回调 onJsPrompt
。这时就完成了前端与 Android端 的通信了,因为前端的信息都顺利通过这个函数传递给Android了。
@Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { result.confirm(JSBridge.callJava(view,message)); return true; }
- Android 中会定义一个类
JSBridge.java
来管理暴露给前端使用的函数;
这个类有两个功能:
- 暴露给前端的函数的动态注册功能。
- 解析前端信息,调用了 Android 端对应的函数,这个示例中是:
showToast
函数。
解析前端的信息,获取前端调用的函数名:
Uri uri = Uri.parse(uriString); className = uri.getHost(); param = uri.getQuery(); port = uri.getPort() + ""; String path = uri.getPath(); HashMap< String, Method> methodHashMap = exposedMethod.get(className); Method method = methodHashMap.get(methodName);
通过获取的函数名,这里是 showToast
,调用 Android 端的 showToast
函数。
method.invoke(null,webView,new JSONObject(param),new Callback(webView,port));
- 定义类
BridgeImpl.java
来具体的实现暴露给前端的所有函数。这里的showToast
函数如下:
public static void showToast(WebView webView, JSONObject param, final JSBridge.Callback callback){ String message = param.optString("msg"); Toast.makeText(webView.getContext(),message,Toast.LENGTH_LONG).show(); if(null != callback){ try { JSONObject object = new JSONObject(); object.put("key","value"); object.put("key1","vaule1"); callback.apply(getJSONObject(0,"ok",object)); }catch (Exception e){ e.printStackTrace(); } } }
五、请列举 Android 与 iOS 平台下 JS Bridge 的实现方式
这边代码比较多,我使用图片来展示,大家可以放大来查看。
5.1 Android 实现方式
5.1.1 Android 调用 JS 的 2 种方式
- 通过
WebView
的loadUrl()
:
JS 代码调用一定要在 onPageFinished()
回调之后才能调用,否则不会调用。
Web 端代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>前端代码</title> <script> // Android需要调用的方法 function callJS(){ alert("Android调用了JS的callJS方法"); } </script> </head> </html>
Android 端代码:
- 通过
WebView
的evaluateJavascript()
:
// 只需要将第一种方法的loadUrl()换成下面该方法即可 mWebView.evaluateJavascript( "javascript:callJS()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { //此处为 js 返回的结果 } }); }
5.1.2 JS 调用 Android 的 3 种方式
- 通过
WebView
的addJavascriptInterface()
进行对象映射:
Android 映射:
// 继承自Object类 public class AndroidtoJs extends Object { // 定义JS需要调用的方法 // 被JS调用的方法必须加入@JavascriptInterface注解 @JavascriptInterface public void hello(String msg) { System.out.println("JS调用了Android的hello方法"); } }
Web:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>前端代码</title> <script> function callAndroid(){ // 由于对象映射,所以调用test对象等于调用Android映射的对象 test.hello("js调用了android中的hello方法"); } </script> </head> <body> //点击按钮则调用callAndroid函数 <button type="button" id="button1" "callAndroid()"></button> </body> </html>
Android 端:
- 通过
WebViewClient
的shouldOverrideUrlLoading ()
方法回调拦截url
:
Web 端:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>前端代码</title> <script> function callAndroid(){ /*约定的url协议为:js://webview?arg1=111&arg2=222*/ document.location = "js://webview?arg1=111&arg2=222"; } </script> </head> <!-- 点击按钮则调用callAndroid()方法 --> <body> <button type="button" id="button1" onclick="callAndroid()" >点击调用Android代码</button> </body> </html>
Android 端:
- 通过 WebChromeClient 的方法回调拦截JS对话框方法:
通过 WebChromeClient 的 onJsAlert()
、onJsConfirm()
、onJsPrompt()
方法回调拦截JS对话框 alert()
、confirm()
、prompt()
消息。
Web 端:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>前端代码</title> <script> function clickprompt(){ // 调用prompt() var result=prompt("js://demo?arg1=111&arg2=222"); alert("demo " + result); } </script> </head> <!-- 点击按钮则调用clickprompt() --> <body> <button type="button" id="button1" onclick="clickprompt()" >点击调用Android代码</button> </body> </html>
Android 端:
5.2 iOS 实现方式
5.2.1 JS 调用 iOS 的 2 种方式
- 使用
XMLHttpRequest
发起请求的方式:
Web 端:
XMLHttpRequest bridge:
JS 端使用 XMLHttpRequest
发起了一个请求:execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true);
,请求的地址是 /!gap_exec
;并把请求的数据放在了请求的 header 里面,见这句代码:execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages());
。
而在 Objective-C 端使用一个 NSURLProtocol
的子类来检查每个请求,如果地址是 /!gap_exec
的话,则认为是 Cordova 通信的请求,直接拦截,拦截后就可以通过分析请求的数据,分发到不同的插件类(CDVPlugin 类的子类)的方法中:
Cordova 中优先使用这种方式, Cordova.js
中的注释有提及为什么优先使用 XMLHttpRequest
的方式,及为什么保留第二种 iframe bridge
的通信方式:
// XHR mode does not work on iOS 4.2, so default to IFRAME_NAV for such devices. // XHR mode’s main advantage is working around a bug in -webkit-scroll, which // doesn’t exist in 4.X devices anyways123
iframe bridge:
在 JS 端创建一个透明的 iframe
,设置这个 ifame
的 src
为自定义的协议,而 ifame
的 src
更改时,UIWebView
会先回调其 delegate
的 webView:shouldStartLoadWithRequest:navigationType:
方法,关键代码如下:
- 通过设置透明的
iframe
的src
属性:
5.2.2 iOS 调用 JS 的方式
UIWebView
有一个这样的方法 stringByEvaluatingJavaScriptFromString:
,这个方法可以让一个 UIWebView
对象执行一段 JS 代码,这样就可以达到 Objective-C 跟 JS 通信的效果,在 Cordova 的代码中多处用到了这个方法,其中最重要的两处如下:
- 获取 JS 的请求数据:
- 把 JS 请求的结果返回给 JS 端:
结语
对于初入混合应用开发的小伙伴,这些会有点难度,但是好好理解下那几张流程图,再理一理思路,相信会有帮助😁
给大家加加油~~