JSBridge 是用于連接 JavaScript(H5) 和原生應用(iOS/Android)的橋梁,允許它們之間相互調用方法。
🌉 一、JSBridge 雙向通信流程圖
??二、基本調用模式
1. JavaScript橋接層
jsbridge-demo.js
// JS 調用原生方法
const callNativeMethod = (methodName, params, callback) => {// 生成唯一回調 IDconst callbackId = `cb_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;// 保存回調函數window.JSBridge.callbacks[callbackId] = callback;// 構造調用數據const data = {method: methodName,params: params,callbackId: callbackId};// 不同平臺的調用方式if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {// iOS: 通過 WebView 的 URL Scheme 調用window.webkit.messageHandlers.JSBridge.postMessage(data);} else if (/(Android)/i.test(navigator.userAgent)) {// Android: 通過 addJavascriptInterface 提供的對象調用window.AndroidJSBridge.call(JSON.stringify(data));}
}// 原生調用 JS 回調的方法
window.JSBridge = {callbacks: {},// 原生通過此方法回調 JSinvokeCallback: (callbackId, result) => {const callback = window.JSBridge.callbacks[callbackId];if (callback && typeof callback === 'function') {callback(result);// 執行后刪除回調,避免內存泄漏delete window.JSBridge.callbacks[callbackId];}}
};// 示例1:調用原生獲取設備信息
callNativeMethod('getDeviceInfo', {}, function(result) {console.log('設備信息:', result);document.getElementById('deviceInfo').innerText = JSON.stringify(result);
});// 示例2:調用原生分享功能
document.getElementById('shareBtn').addEventListener('click', function() {callNativeMethod('share', {title: '分享標題',content: '分享內容',url: 'https://example.com'}, function(success) {if (success) {alert('分享成功');} else {alert('分享取消');}});
});
2. 原生端橋階層實現
Android端(Java)
AndroidJSBridge.java
package com.example.jsbridge;import android.content.Context;
import android.os.Build;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import org.json.JSONException;
import org.json.JSONObject;public class AndroidJSBridge {private final Context mContext;private final WebView mWebView;// 構造函數,接收上下文和WebView實例public AndroidJSBridge(Context context, WebView webView) {this.mContext = context;this.mWebView = webView;}// 暴露給JS調用的方法,必須加@JavascriptInterface注解@JavascriptInterfacepublic void call(String data) {try {// 解析JS傳遞的JSON數據JSONObject jsonData = new JSONObject(data);String method = jsonData.getString("method");JSONObject params = jsonData.getJSONObject("params");String callbackId = jsonData.getString("callbackId");// 根據方法名處理不同邏輯switch (method) {case "getDeviceInfo":handleGetDeviceInfo(callbackId);break;case "share":handleShare(params, callbackId);break;default:sendErrorCallback(callbackId, "Method not found: " + method);}} catch (JSONException e) {e.printStackTrace();}}// 處理獲取設備信息private void handleGetDeviceInfo(String callbackId) {try {JSONObject result = new JSONObject();result.put("model", Build.MODEL); // 設備型號result.put("brand", Build.BRAND); // 設備品牌result.put("system", "Android " + Build.VERSION.RELEASE); // 系統版本result.put("sdkInt", Build.VERSION.SDK_INT); // SDK版本sendSuccessCallback(callbackId, result);} catch (JSONException e) {e.printStackTrace();sendErrorCallback(callbackId, "Failed to get device info");}}// 處理分享功能private void handleShare(JSONObject params, String callbackId) {try {String title = params.getString("title");String content = params.getString("content");String url = params.getString("url");// 這里只是模擬分享成功,實際項目中應調用系統分享功能boolean shareSuccess = true;// 回調JS告知結果sendSuccessCallback(callbackId, new JSONObject().put("success", shareSuccess));} catch (JSONException e) {e.printStackTrace();sendErrorCallback(callbackId, "Share parameters error");}}// 發送成功回調private void sendSuccessCallback(String callbackId, JSONObject result) {String js = String.format("window.JSBridge.invokeCallback('%s', %s)",callbackId, result.toString());runOnMainThread(js);}// 發送錯誤回調private void sendErrorCallback(String callbackId, String errorMsg) {try {JSONObject error = new JSONObject();error.put("error", errorMsg);String js = String.format("window.JSBridge.invokeCallback('%s', %s)",callbackId, error.toString());runOnMainThread(js);} catch (JSONException e) {e.printStackTrace();}}// 在主線程執行JS代碼private void runOnMainThread(final String js) {mWebView.post(() -> mWebView.evaluateJavascript(js, null));}
}
MainActivity.java
package com.example.jsbridge;import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private WebView mWebView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mWebView = findViewById(R.id.webView);initWebView();}private void initWebView() {WebSettings webSettings = mWebView.getSettings();// 啟用JavaScriptwebSettings.setJavaScriptEnabled(true);// 允許通過file協議加載的JS訪問其他文件webSettings.setAllowFileAccessFromFileURLs(true);// 允許通過http協議加載的JS訪問文件webSettings.setAllowUniversalAccessFromFileURLs(true);// 注冊JSBridgemWebView.addJavascriptInterface(new AndroidJSBridge(this, mWebView), "AndroidJSBridge");// 加載本地HTML文件或遠程URLmWebView.loadUrl("file:///android_asset/index.html");}@Overrideprotected void onDestroy() {super.onDestroy();// 銷毀WebView防止內存泄漏if (mWebView != null) {mWebView.destroy();}}
}
iOS端(Swift)
IOSJSBridge.swift
import UIKit
import WebKitclass IOSJSBridge: NSObject, WKScriptMessageHandler {private weak var webView: WKWebView?init(webView: WKWebView) {self.webView = webViewsuper.init()}// 處理JS發送的消息func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {guard message.name == "JSBridge",let data = message.body as? [String: Any],let method = data["method"] as? String,let callbackId = data["callbackId"] as? String else {return}let params = data["params"] as? [String: Any] ?? [:]// 根據方法名處理不同邏輯switch method {case "getDeviceInfo":handleGetDeviceInfo(callbackId: callbackId)case "share":handleShare(params: params, callbackId: callbackId)default:sendErrorCallback(callbackId: callbackId, errorMsg: "Method not found: \(method)")}}// 處理獲取設備信息private func handleGetDeviceInfo(callbackId: String) {let device = UIDevice.currentlet result: [String: Any] = ["model": device.model,"name": device.name,"system": "iOS \(device.systemVersion)","identifierForVendor": device.identifierForVendor?.uuidString ?? ""]sendSuccessCallback(callbackId: callbackId, result: result)}// 處理分享功能private func handleShare(params: [String: Any], callbackId: String) {guard let title = params["title"] as? String,let content = params["content"] as? String,let url = params["url"] as? String else {sendErrorCallback(callbackId: callbackId, errorMsg: "Invalid share parameters")return}// 這里只是模擬分享成功,實際項目中應調用系統分享功能let shareSuccess = truesendSuccessCallback(callbackId: callbackId, result: ["success": shareSuccess])}// 發送成功回調private func sendSuccessCallback(callbackId: String, result: [String: Any]) {guard let jsonData = try? JSONSerialization.data(withJSONObject: result),let jsonString = String(data: jsonData, encoding: .utf8),let webView = webView else {return}let js = "window.JSBridge.invokeCallback('\(callbackId)', \(jsonString))"webView.evaluateJavaScript(js, completionHandler: nil)}// 發送錯誤回調private func sendErrorCallback(callbackId: String, errorMsg: String) {let error = ["error": errorMsg]sendSuccessCallback(callbackId: callbackId, result: error)}
}
WebViewController.swift
import UIKit
import WebKitclass WebViewController: UIViewController {private var webView: WKWebView!override func viewDidLoad() {super.viewDidLoad()setupWebView()loadWebContent()}private func setupWebView() {// 配置WebViewlet config = WKWebViewConfiguration()let userContentController = WKUserContentController()// 創建JSBridge處理器并注冊let iOSJSBridgeHandler = IOSJSBridge(webView: webView)userContentController.add(iOSJSBridgeHandler, name: "JSBridge")config.userContentController = userContentControllerwebView = WKWebView(frame: view.bounds, configuration: config)webView.navigationDelegate = selfwebView.autoresizingMask = [.flexibleWidth, .flexibleHeight]view.addSubview(webView)}private func loadWebContent() {// 加載本地HTML文件或遠程URLif let url = Bundle.main.url(forResource: "index", withExtension: "html") {webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())} else {// 備用:加載遠程URLlet url = URL(string: "https://example.com")!webView.load(URLRequest(url: url))}}
}// 實現WKNavigationDelegate協議
extension WebViewController: WKNavigationDelegate {// 可以在這里處理頁面加載事件
}
🛡? 三、安全與性能優化建議
-
安全防護
- 方法白名單校驗:原生端只響應預注冊的方法名
if (!allowedMethods.contains(method)) { sendError(callbackId, "Method forbidden"); }
- 輸入消毒:對JS傳入參數進行正則校驗
- HTTPS 通信:防止中間人攻擊
-
性能優化
- 避免主線程阻塞:Android 使用
Handler
處理耗時操作 - 回調超時機制:JS 端設置 5s 超時清理回調函數
setTimeout(() => {delete JSBridge.callbacks[callbackId]; }, 5000);
- 大數據分片傳輸:超過 1MB 數據使用分塊傳輸
- 避免主線程阻塞:Android 使用
-
跨平臺兼容
- 統一調用接口:封裝
JSBridge.invoke()
抹平平臺差異 - 版本檢測邏輯:
const isIOS = /(iPhone|iPad)/i.test(navigator.userAgent); const isNewIOS = isIOS && !!window.webkit?.messageHandlers;
- 統一調用接口:封裝
💻 四、完整調用示例
通過該設計,JSBridge 實現了 跨平臺調用標準化(Android/iOS 接口統一)、雙向通信可靠化(回調ID機制)、安全控制精細化(方法白名單+參數校驗)。實際開發建議使用開源庫(如https://github.com/marcuswestin/WebViewJavascriptBridge)減少底層適配成本。