JSBridge是什么 JSBridge本质就是:Web端和客户端Native之间的通信桥梁,让混合开发模式中的Web端和Native端能够互相通信,实现双向调用。
Web端调用Native端 在Webview中注入JS API 通过WebView提供的接口,App将Native的相关接口注入到JS的Context(window)的对象中 Web端就可以直接在全局 window 下使用这个暴露的全局JS对象,进而调用原生端的方法
Android注入方法:
addJavascriptInterface配合@JavascriptInterface:Android 4.2+ 配合 @JavascriptInterface 才安全可用
IOS注入方法:
WKWebView + WKScriptMessageHandler 是 iOS 官方方案,推荐新项目优先使用,链路更清晰。
WebViewJavascriptBridge 是常见历史封装方案,接入快、兼容旧项目,但初始化握手和调试成本更高。
示例代码:
IOS走WebViewJavascriptBridge方案、Android走addJavascriptInterface方案
1 2 3 4 5 6 7 8 9 10 11 12 webView.addJavascriptInterface(new NativeBridge (this ), "NativeBridge" ); class NativeBridge { private Context ctx; NativeBridge(Context ctx) { this .ctx = ctx; } @JavascriptInterface public void showNativeDialog (String text) { new AlertDialog .Builder(ctx).setMessage(text).create().show(); } }
1 2 3 4 5 6 // iOS 侧(示意):WebViewJavascriptBridge 注册 handler bridge.registerHandler("showNativeDialog") { data, responseCallback in let text = (data as? [String: Any])?["text"] as? String ?? "" // 展示原生弹窗... responseCallback?(["code": 0, "msg": "ok"]) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function isAndroidBridge ( ) { return !!window .NativeBridge && typeof window .NativeBridge .showNativeDialog === "function" ; } function isiOSBridgeReady ( ) { return !!window .WebViewJavascriptBridge && typeof window .WebViewJavascriptBridge .callHandler === "function" ; } function setupIOSBridge (callback ) { if (isiOSBridgeReady ()) return callback (window .WebViewJavascriptBridge ); if (window .WVJBCallbacks ) return window .WVJBCallbacks .push (callback); window .WVJBCallbacks = [callback]; const iframe = document .createElement ("iframe" ); iframe.style .display = "none" ; iframe.src = "https://__bridge_loaded__" ; document .documentElement .appendChild (iframe); setTimeout (() => document .documentElement .removeChild (iframe), 0 ); } function showDialog (text ) { if (isAndroidBridge ()) { window .NativeBridge .showNativeDialog (text); return ; } setupIOSBridge ((bridge ) => { bridge.callHandler ("showNativeDialog" , { text }, function (res ) { console .log ("ios callback:" , res); }); }); } showDialog ("hello from h5" );
URL Schema URL Schema是类URL的一种请求格式,格式如下:
1 2 3 4 <protocol>://<host>/<path>?<qeury> // 我们可以自定义JSBridge通信的URL Schema,比如: hellobike://showToast?text=hello
Native加载WebView之后,Web发送的所有请求都会经过WebView组件 ,所以Native可以重写WebView里的方法,从来拦截Web发起的请求,我们对请求的格式进行判断:
符合我们自定义的URL Schema,对URL进行解析,拿到相关操作、操作,进而调用原生Native的方法 不符合我们自定义的URL Schema,我们直接转发,请求真正的服务
例如
1 2 3 4 5 6 7 8 9 10 get existOrderRedirect () { let url : string; if (this .env .isHelloBikeApp ) { url = 'hellobike://hellobike.com/xxxxx_xxx?from_type=xxxx&selected_tab=xxxxx' ; } else if (this .env .isSFCApp ) { url = 'hellohitch://hellohitch.com/xxx/xxxx?bottomTab=xxxx' ; } return url; }
URL Schema 这套桥接本质就是:
Web 侧构造一个特定 URL(如 myapp://share?…)
通过 location.href / iframe.src / a 标签触发一次“导航请求”
Native 侧在 WebView 的导航拦截回调里识别这个 URL
命中自定义 schema 就不真的跳转页面,而是解析参数并调用对应 Native 能力
未命中就放行,正常加载网页链接
这种方式从早期就存在,兼容性 很好,但是由于是基于URL的方式,长度 受到限制而且不太直观,同时数据格式 有限制,而且建立请求有时间耗时。
Native端调用Web端
Native端调用Web端,本质上其实是Natvie有运行JS的能力(WebView),所以Web端把方法挂在Window上,Native就能够执行这段JS代码
Android Android提供了evaluateJavascript来执行JS代码,并且可以获取返回值执行回调:
1 2 3 4 5 6 7 String jsCode = String.format("window.showWebDialog('%s')" , text);webView.evaluateJavascript(jsCode, new ValueCallback <String>() { @Override public void onReceiveValue (String value) { } });
IOS IOS的WKWebView使用evaluateJavaScript:
1 2 3 4 [webView evaluateJavaScript:@"执行的JS代码" completionHandler:^ (id _Nullable response, NSError * _Nullable error) { }];
一种JSBridge的实现方式
Web侧挂全局函数:Web端在每次调用Native方法后在window上挂返回结果的回调,Native端把调用方法的结果通过返回结果的回调传回给Web端
可能有点绕,直接看下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class MainActivity extends AppCompatActivity { private WebView webView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.webview_layout); webView = findViewById(R.id.webview); WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true ); webView.addJavascriptInterface(new JsBridge (), "JsBridge" ); webView.loadUrl("http://10.168.2.149:5500/h5-test.html" ); } public class JsBridge { @JavascriptInterface public void sendDataToApp (String value) { sendResponseToH5(value); } public void sendResponseToH5 (final String data) { runOnUiThread(() -> { webView.evaluateJavascript("javascript:window.JsBridge.receiveDataFromApp('" + data + "')" , null ); }); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function sendDataToApp ( ) { if (window .JsBridge && typeof window .JsBridge .sendDataToApp === "function" ) { window .JsBridge .receiveDataFromApp = function (data ) { document .getElementById ("app" ).innerHTML = "Received data from App: " + data; delete window .window .JsBridge .receiveDataFromApp ; }; window .JsBridge .sendDataToApp ("Hello from Web" ); document .getElementById ("log" ).innerHTML = "Data sent to App" ; } else { document .getElementById ("log" ).innerHTML = "sent data failed" ; } }
这种实现方式缺点其实很明显:
全局污染:挂了一堆临时全局函数。
不支持并发调用:后面挂的全局函数会覆盖前面的
另一种实现方式:事件派发
Native在返回结果时不直接调某个全局函数,而是通过事件派发的方式
这种方式的核心是:
Web 调 Native 前先生成一个requestId,吊用Native方法时带上这个requestId
Native 返回时统一触发同一个事件(例如 native:callback),并传回requestId
Web 通过 requestId 判断这条回包是不是自己的
直接看示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class MainActivity extends AppCompatActivity { private WebView webView; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.webview_layout); webView = findViewById(R.id.webview); WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true ); webView.addJavascriptInterface(new JsBridge (), "JsBridge" ); webView.loadUrl("http://10.168.2.149:5500/h5-test.html" ); } public class JsBridge { @JavascriptInterface public void sendDataToApp (String value) { sendResponseToH5(value); } private void sendResponseToH5 (final String data) { runOnUiThread(() -> { String jsCode = "window.dispatchEvent(new CustomEvent('native:callback', { detail: " + data + " }))" ; webView.evaluateJavascript(jsCode, null ); }); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function sendDataToAppByEventBus ( ) { if (window .JsBridge && typeof window .JsBridge .sendDataToApp === "function" ) { const requestId = `req_${Date .now()} _${Math .random().toString(16 ).slice(2 )} ` ; const payload = { requestId, payload : "Hello from Web" , }; const callback = (event ) => { if (!(event instanceof CustomEvent )) return ; const detail = event.detail || {}; if (detail.requestId !== requestId) return ; document .getElementById ("app" ).innerHTML = "Received data from App: " + JSON .stringify (detail); window .removeEventListener ("native:callback" , callback); }; window .addEventListener ("native:callback" , callback); window .JsBridge .sendDataToApp (JSON .stringify (payload)); document .getElementById ("log" ).innerHTML = "Data sent to App by event bus" ; } else { document .getElementById ("log" ).innerHTML = "sent data failed" ; } }
这种方式相比“全局函数回调”更适合并发场景:
可以同时监听多次调用,不会出现 window.xxxResult 被覆盖的问题
通过 requestId 可以准确匹配每次调用的返回结果