📖 故事背景:两个王国的贸易通道
想象在移动开发的大陆上,存在两个繁荣的王国:
-
Web 王国:擅长快速建造「数字商店」(网页),但缺乏制造「魔法道具」(调用摄像头、支付等原生功能)的技术
-
Native 帝国:拥有强大的「魔法工坊」,能制造各种神奇道具,但建造商店的速度很慢
为了实现互利共赢,两国决定在边境(WebView)上修建一座「贸易大桥」(JSBridge),让:
- Web 商人能使用 Native 帝国的魔法道具
- Native 工匠能快速搭建 Web 风格的商店并控制其行为
🏗️ 大桥的核心构造:双向通信通道
1. Native → Web:魔法道具的远程展示
Native 帝国要向 Web 王国展示魔法道具的效果,相当于「远程表演魔术」。
实现原理:Native 通过 WebView 执行 JS 代码,就像在 Web 王国的广场上立一块「直播屏幕」。
Android 代码示例:
java
// Native 端调用 Web 端的 showWebDialog 方法
String magicShow = "展示火焰魔法";
String jsCode = String.format("window.showWebDialog('%s')", magicShow);
webView.evaluateJavascript(jsCode, new ValueCallback<String>() {
@Override
public void onReceiveValue(String result) {
// Web 端返回的魔术反馈
Log.d("JSBridge", "Web 观众反馈:" + result);
}
});
Web 端接收代码:
javascript
// Web 王国的魔术展示区
function showWebDialog(message) {
alert("Native 帝国正在表演:" + message);
// 可以返回观众的掌声(回调结果)
return "观众欢呼!";
}
2. Web → Native:魔法道具的定制订单
Web 商人需要向 Native 帝国订购魔法道具,有两种「下单方式」。
📮 方式一:信使传信(URL Scheme 拦截)
Web 商人在信封上写下特殊地址(URL Scheme),Native 守卫拦截信件并处理订单。
Web 端下单代码:
javascript
// 订购「扫码魔法」道具
function orderMagic(orderType, params) {
// 生成特殊地址:jsbridge://magic/scan?params=...
const url = `jsbridge://magic/${orderType}?${JSON.stringify(params)}`;
// 用隐形信鸽(iframe)发送信件
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(() => iframe.remove(), 100);
}
// 调用示例:订购扫码魔法
orderMagic("scan", { type: "二维码" });
Native 端收信处理:
java
// Native 帝国的邮差(WebViewClient)
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("jsbridge://")) {
// 解析信件内容
String order = url.substring(12); // 去掉 jsbridge://
String orderType = order.split("\?")[0];
String params = order.split("\?")[1];
// 处理订单:如果是扫码魔法
if ("magic/scan".equals(orderType)) {
showScanPage(params); // 打开扫码页面
}
return true; // 拦截成功,不再发送到 Web 邮局
}
return super.shouldOverrideUrlLoading(view, url);
}
});
🏢 方式二:设立办事处(注入 JS API)
Native 帝国在 Web 王国设立办事处(注入全局对象),Web 商人可直接上门下单。
Native 端设立办事处:
java
// Native 帝国的办事处人员
class MagicOffice {
private Context context;
public MagicOffice(Context context) {
this.context = context;
}
// 处理「打开宝箱」的订单
@JavascriptInterface
public void openChest(String chestType) {
Toast.makeText(context, "Native 正在打开" + chestType + "宝箱",
Toast.LENGTH_SHORT).show();
// 打开宝箱后通知 Web 商人
String jsCode = "window.onChestOpened('" + chestType + "宝箱已打开')";
webView.evaluateJavascript(jsCode, null);
}
}
// 把办事处注册到 Web 王国
webView.addJavascriptInterface(new MagicOffice(this), "NativeMagic");
Web 端上门下单:
javascript
// Web 商人直接访问办事处
function orderChest(chestType) {
// 调用 Native 办事处的 openChest 方法
NativeMagic.openChest(chestType);
// 注册宝箱打开的回调
window.onChestOpened = function(result) {
document.getElementById("result").textContent = result;
};
}
// 调用示例:订购黄金宝箱
orderChest("黄金");
🧩 复杂订单的处理:带回调的双向通信
当 Web 商人订购需要定制的魔法道具时,需要「订单回执」机制。
1. Web 下订单时附带回执地址
javascript
// Web 端下单并等待回执
function orderCustomMagic(magicType, params, callback) {
// 生成唯一订单号
const orderId = "ORD" + Date.now();
// 保存回执地址(回调函数)
window.magicCallbacks[orderId] = callback;
// 下单时附带订单号
const data = {
type: magicType,
params: params,
orderId: orderId
};
NativeMagic.processOrder(JSON.stringify(data));
}
// 注册回执处理中心
window.magicCallbacks = {};
window.onMagicResult = function(result) {
const { orderId, data } = JSON.parse(result);
if (window.magicCallbacks[orderId]) {
// 按订单号找到对应的回执地址(回调函数)
window.magicCallbacks[orderId](data);
delete window.magicCallbacks[orderId]; // 销毁回执地址
}
};
2. Native 处理订单并按地址回执
java
// Native 端处理带回执的订单
@JavascriptInterface
public void processOrder(String orderData) {
try {
JSONObject order = new JSONObject(orderData);
String magicType = order.getString("type");
String orderId = order.getString("orderId");
// 处理魔法订单(耗时操作)
new Handler().postDelayed(() -> {
String result = "魔法[" + magicType + "]已制作完成";
// 按订单号回执
String jsCode = "window.onMagicResult('{" +
""orderId":"" + orderId + ""," +
""data":"" + result + ""}')";
webView.evaluateJavascript(jsCode, null);
}, 2000); // 模拟魔法制作时间
} catch (JSONException e) {
e.printStackTrace();
}
}
🛡️ 大桥的安全与兼容性
1. Android 的版本兼容问题
-
4.2 之前:使用
addJavascriptInterface
有安全漏洞,像年久失修的旧桥 -
4.2 之后:新增
@JavascriptInterface
注解,如同加固后的新桥
java
// 兼容新旧桥的代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
// 新桥:使用 @JavascriptInterface 注解
webView.addJavascriptInterface(new SafeMagicOffice(this), "SafeMagic");
} else {
// 旧桥:使用拦截 prompt 的方式
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
if (message.startsWith("JSBridge:")) {
// 解析旧桥的通信内容
String data = message.substring(9);
handleLegacyBridge(data);
result.confirm("旧桥已收到");
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
});
}
2. URL Scheme 的长度限制
太长的订单内容(URL 参数)会导致信件超重,解决方案:
- 使用 POST 方式传递大数据(部分 WebView 支持)
- 分拆成多封小信件(分段传输)
- 优先使用办事处方式(注入 API)传递复杂数据
🌐 实战:完整的 JSBridge 框架封装
1. Native 端框架核心
java
public class JSBridge {
private WebView webView;
private Context context;
private static JSBridge instance;
private JSBridge(Context context, WebView webView) {
this.context = context;
this.webView = webView;
initBridge();
}
public static JSBridge getInstance(Context context, WebView webView) {
if (instance == null) {
instance = new JSBridge(context, webView);
}
return instance;
}
private void initBridge() {
// 注册办事处(注入 API)
webView.addJavascriptInterface(new BridgeInterface(), "JSBridge");
// 配置信鸽拦截器(URL Scheme)
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("jsbridge://")) {
handleUrlScheme(url);
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
});
}
// 处理信鸽信件(URL Scheme)
private void handleUrlScheme(String url) {
// 解析 URL 中的命令和参数
// ... 省略解析逻辑
String action = "默认命令";
String params = "{}";
// 分发命令到对应的处理函数
dispatchAction(action, params);
}
// 命令分发中心
private void dispatchAction(String action, String params) {
if ("showCamera".equals(action)) {
openCamera(params);
} else if ("pay".equals(action)) {
processPayment(params);
}
// 其他命令处理...
}
// 桥接接口实现
class BridgeInterface {
@JavascriptInterface
public void callNative(String action, String params) {
dispatchAction(action, params);
}
}
// 打开相机的 Native 实现
private void openCamera(String params) {
// 启动相机 Intent
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
((Activity) context).startActivityForResult(intent, 1001);
}
// 处理支付的 Native 实现
private void processPayment(String params) {
// 调用支付 SDK
Log.d("JSBridge", "处理支付请求:" + params);
// 支付完成后通知 Web 端
String jsCode = "window.onPaymentResult('支付成功')";
webView.evaluateJavascript(jsCode, null);
}
}
2. Web 端框架核心
javascript
// Web 端 JSBridge 框架
window.JSBridge = {
// 命令队列(处理桥未就绪的情况)
commandQueue: [],
isBridgeReady: false,
// 初始化桥接
init: function() {
// 检查 Native 办事处是否存在
if (window.JSBridge_Native) {
this.isBridgeReady = true;
// 发送积压的命令
this.commandQueue.forEach(cmd => this.executeCommand(cmd));
this.commandQueue = [];
} else {
// 桥未就绪,500ms 后重试
setTimeout(() => this.init(), 500);
}
},
// 执行命令
executeCommand: function(command) {
if (this.isBridgeReady) {
// 直接调用 Native 办事处
window.JSBridge_Native.callNative(command.action, command.params);
} else {
// 命令入队等待
this.commandQueue.push(command);
}
},
// 调用 Native 功能
call: function(action, params, callback) {
// 生成唯一回调 ID
const callbackId = "CB_" + Date.now();
window.JSBridge_callbacks[callbackId] = callback;
// 构造命令参数
const command = {
action: action,
params: JSON.stringify({...params, callbackId: callbackId})
};
this.executeCommand(command);
}
};
// 注册回调处理中心
window.JSBridge_callbacks = {};
window.onJSBridgeResult = function(result) {
const { callbackId, data } = JSON.parse(result);
if (window.JSBridge_callbacks[callbackId]) {
window.JSBridge_callbacks[callbackId](data);
delete window.JSBridge_callbacks[callbackId];
}
};
// 初始化桥接
JSBridge.init();
📌 总结:JSBridge 的核心要点
-
双向通信的本质:
- Native→Web:通过 WebView 执行 JS 代码,如同远程操控
- Web→Native:通过 URL 拦截或注入 API,如同写信或上门拜访
-
回调机制的实现:
- 核心是「唯一标识 + 回调映射」,就像订单号对应回执地址
-
实际应用建议:
-
优先使用注入 API 方式(办事处),性能和体验更优
-
复杂数据传输避免使用 URL Scheme(信鸽),防止信件超重
-
注意 Android 版本兼容,4.2 后必须使用
@JavascriptInterface
注解
-
通过这座「JSBridge 贸易大桥」,Web 王国和 Native 帝国实现了互利共赢,就像现实中 Hybrid 开发通过 JSBridge 结合了 Web 的灵活和 Native 的强大能力。掌握了这座桥的构造原理,你就能在移动开发的大陆上自由穿梭啦! 🌉