JSBridge 是用于连接 JavaScript(H5) 和原生应用(iOS/Android)的桥梁,允许它们之间相互调用方法。
🌉 一、JSBridge 双向通信流程图
⚙️二、基本调用模式
1. JavaScript桥接层
jsbridge-demo.js
// JS 调用原生方法
const callNativeMethod = (methodName, params, callback) => {
// 生成唯一回调 ID
const 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: {},
// 原生通过此方法回调 JS
invokeCallback: (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注解
@JavascriptInterface
public 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;
@Override
protected 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();
// 启用JavaScript
webSettings.setJavaScriptEnabled(true);
// 允许通过file协议加载的JS访问其他文件
webSettings.setAllowFileAccessFromFileURLs(true);
// 允许通过http协议加载的JS访问文件
webSettings.setAllowUniversalAccessFromFileURLs(true);
// 注册JSBridge
mWebView.addJavascriptInterface(
new AndroidJSBridge(this, mWebView),
"AndroidJSBridge"
);
// 加载本地HTML文件或远程URL
mWebView.loadUrl("file:///android_asset/index.html");
}
@Override
protected void onDestroy() {
super.onDestroy();
// 销毁WebView防止内存泄漏
if (mWebView != null) {
mWebView.destroy();
}
}
}
iOS端(Swift)
IOSJSBridge.swift
import UIKit
import WebKit
class IOSJSBridge: NSObject, WKScriptMessageHandler {
private weak var webView: WKWebView?
init(webView: WKWebView) {
self.webView = webView
super.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.current
let 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 = true
sendSuccessCallback(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 WebKit
class WebViewController: UIViewController {
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
loadWebContent()
}
private func setupWebView() {
// 配置WebView
let config = WKWebViewConfiguration()
let userContentController = WKUserContentController()
// 创建JSBridge处理器并注册
let iOSJSBridgeHandler = IOSJSBridge(webView: webView)
userContentController.add(iOSJSBridgeHandler, name: "JSBridge")
config.userContentController = userContentController
webView = WKWebView(frame: view.bounds, configuration: config)
webView.navigationDelegate = self
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)
}
private func loadWebContent() {
// 加载本地HTML文件或远程URL
if let url = Bundle.main.url(forResource: "index", withExtension: "html") {
webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
} else {
// 备用:加载远程URL
let 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)减少底层适配成本。