JS 与原生 App 交互方法总结
一、概述
JS 与原生 App(Android/iOS)交互是混合开发(Hybrid App)的核心技术,主要实现 Web 页面与原生功能的双向通信。
二、主要交互方法
目前主流的 JS 与原生 App 交互方法有三种:
- URL Scheme 方式
- 注入 API 方式
- WebViewJavascriptBridge 方式(消息队列机制)
三、详细分析
1. URL Scheme 方式
底层原理
- 通过构造自定义 URL Scheme(如
jsbridge://method?params=...) - 利用 WebView 的 URL 加载拦截机制
- 解析 URL 获取方法名和参数,执行对应原生功能
调用顺序
JS 端 → 构造 URL → 设置 window.location.href → WebView 拦截 URL → 原生解析处理 → 返回结果
代码示例
JS 端代码:
// 构造并触发 URL Scheme
function callNative(method, params = {}) {
const url = `jsbridge://${method}?params=${encodeURIComponent(JSON.stringify(params))}`;
window.location.href = url;
}
// 接收原生返回结果(由原生通过 evaluateJavascript 调用)
function nativeCallback(method, result) {
console.log(`原生响应 ${method}:`, result);
// 处理返回结果
}
// 调用示例
callNative('share', { title: '分享标题', content: '分享内容' });
Android 端代码:
// 设置 WebViewClient 拦截 URL
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("jsbridge://")) {
handleJSBridgeCall(url);
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
});
// 处理 JSBridge 调用
private void handleJSBridgeCall(String url) {
// 解析 URL
Uri uri = Uri.parse(url);
String method = uri.getHost();
String paramsStr = uri.getQueryParameter("params");
JSONObject params = new JSONObject(paramsStr);
// 根据方法名处理
JSONObject result = new JSONObject();
if ("share".equals(method)) {
// 执行分享逻辑
result.put("success", true);
result.put("message", "分享成功");
}
// 返回结果给 JS
String js = String.format("javascript:nativeCallback('%s', JSON.parse('%s'));",
method, result.toString());
webView.evaluateJavascript(js, null);
}
iOS 端代码:
// WKWebView 拦截 URL
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url, url.scheme == "jsbridge" {
handleJSBridgeCall(url: url)
decisionHandler(.cancel) // 取消默认加载
return
}
decisionHandler(.allow)
}
// 处理 JSBridge 调用
private func handleJSBridgeCall(url: URL) {
let method = url.host()
let paramsStr = url.queryParameters?["params"] ?? "{}"
let params = try? JSONSerialization.jsonObject(with: paramsStr.data(using: .utf8)!, options: []) as? [String: Any]
// 根据方法名处理
var result = [String: Any]()
if method == "share" {
// 执行分享逻辑
result = ["success": true, "message": "分享成功"]
}
// 返回结果给 JS
let resultJson = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted)
let resultStr = String(data: resultJson!, encoding: .utf8)!
let js = "nativeCallback('\(method!)', JSON.parse('\(resultStr)'))"
webView.evaluateJavaScript(js, completionHandler: nil)
}
2. 注入 API 方式
底层原理
- 通过 WebView 提供的 API 向 JS 环境注入原生对象
- JS 直接调用注入对象的方法
- 支持同步/异步调用,依赖平台提供的安全机制
调用顺序
原生 → 向 JS 环境注入对象 → JS 直接调用对象方法 → 原生执行对应功能 → 返回结果
代码示例
JS 端代码:
// 调用原生注入的对象方法
if (window.nativeBridge) {
// 同步调用示例
const result = window.nativeBridge.getDeviceInfo();
console.log('设备信息:', result);
// 异步调用示例(通过回调)
window.nativeBridge.share({
title: '分享标题',
content: '分享内容'
}, (success) => {
console.log('分享结果:', success);
});
}
Android 端代码:
// 创建要注入的对象
public class NativeBridge {
private Context context;
private WebView webView;
public NativeBridge(Context context, WebView webView) {
this.context = context;
this.webView = webView;
}
// 供 JS 调用的方法必须添加此注解
@JavascriptInterface
public String getDeviceInfo() {
// 获取设备信息
JSONObject info = new JSONObject();
info.put("device", "Android Device");
info.put("os", "Android 12");
return info.toString();
}
@JavascriptInterface
public void share(String paramsStr, final String callback) {
JSONObject params = new JSONObject(paramsStr);
String title = params.optString("title");
String content = params.optString("content");
// 执行分享逻辑
boolean success = true;
// 通过 JS 回调返回结果
String js = String.format("javascript:eval('%s')(%b);", callback, success);
webView.post(() -> webView.evaluateJavascript(js, null));
}
}
// 注入对象到 JS 环境
webView.addJavascriptInterface(new NativeBridge(this, webView), "nativeBridge");
iOS 端代码:
// 创建 JavaScript 桥接对象
class NativeBridge: NSObject {
private weak var webView: WKWebView?
init(webView: WKWebView) {
self.webView = webView
}
// 暴露给 JS 的方法
@objc func getDeviceInfo(_ completion: @escaping (String) -> Void) {
// 获取设备信息
let info = [
"device": "iOS Device",
"os": "iOS 16"
]
let jsonData = try? JSONSerialization.data(withJSONObject: info, options: [])
let jsonStr = String(data: jsonData!, encoding: .utf8)!
completion(jsonStr)
}
@objc func share(_ params: [String: Any], completion: @escaping (Bool) -> Void) {
let title = params["title"] as? String ?? ""
let content = params["content"] as? String ?? ""
// 执行分享逻辑
let success = true
completion(success)
}
}
// 配置 WKWebView
let configuration = WKWebViewConfiguration()
let nativeBridge = NativeBridge(webView: webView)
configuration.userContentController.add(nativeBridge, name: "nativeBridge")
// JS 调用示例(WKScriptMessageHandler)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "nativeBridge" {
let data = message.body as! [String: Any]
let method = data["method"] as! String
let params = data["params"] as! [String: Any]
let callbackId = data["callbackId"] as! String
// 根据方法名处理
if method == "share" {
share(params) { success in
let js = "nativeBridge.callback('\(callbackId)', \(success))"
self.webView?.evaluateJavaScript(js, completionHandler: nil)
}
}
}
}
3. WebViewJavascriptBridge 方式
底层原理
- 基于消息队列机制,通过
window.prompt/console.log等方式传递消息 - 在 JS 环境中创建桥接对象,统一通信接口
- 支持异步回调,解决了参数限制和安全问题
调用顺序
JS → 创建 Bridge 对象 → 注册处理函数 → 调用 bridge.callHandler → 构造消息 → 通过 prompt 发送 → 原生拦截 → 处理消息 → 调用 bridge.registerHandler 注册的处理函数
代码示例
JS 端代码:
// Bridge 初始化
(function() {
if (window.WebViewJavascriptBridge) return;
window.WebViewJavascriptBridge = {
callHandler: function(handlerName, data, callback) {
const message = {
handlerName: handlerName,
data: data || {},
callbackId: 'cb_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
};
if (callback) {
this._callbacks[message.callbackId] = callback;
}
// 通过 prompt 发送消息
window.prompt('WebViewJavascriptBridge:' + JSON.stringify(message), '');
},
registerHandler: function(handlerName, handler) {
this._handlers[handlerName] = handler;
},
_callbacks: {},
_handlers: {}
};
// 触发初始化完成事件
const event = new Event('WebViewJavascriptBridgeReady');
document.dispatchEvent(event);
})();
// 调用原生方法
window.WebViewJavascriptBridge.callHandler('share', {
title: '分享标题',
content: '分享内容'
}, function(response) {
console.log('分享结果:', response);
});
// 注册供原生调用的方法
window.WebViewJavascriptBridge.registerHandler('onLoginSuccess', function(data) {
console.log('登录成功:', data);
// 处理登录成功逻辑
});
Android 端代码:
// 设置 WebChromeClient 拦截 prompt
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
if (message.startsWith("WebViewJavascriptBridge:")) {
String jsonStr = message.substring("WebViewJavascriptBridge:".length());
handleBridgeMessage(jsonStr);
result.confirm("success");
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
});
// 处理 Bridge 消息
private void handleBridgeMessage(String jsonStr) {
JSONObject message = new JSONObject(jsonStr);
String handlerName = message.optString("handlerName");
JSONObject data = message.optJSONObject("data");
String callbackId = message.optString("callbackId");
// 根据 handlerName 处理
JSONObject result = new JSONObject();
if ("share".equals(handlerName)) {
// 执行分享逻辑
result.put("success", true);
result.put("message", "分享成功");
}
// 调用 JS 回调
if (!callbackId.isEmpty()) {
String js = String.format(
"javascript:window.WebViewJavascriptBridge._callbacks['%s'](JSON.parse('%s'));delete window.WebViewJavascriptBridge._callbacks['%s'];",
callbackId, result.toString(), callbackId
);
webView.evaluateJavascript(js, null);
}
}
// 调用 JS 方法
private void callJsMethod() {
JSONObject data = new JSONObject();
data.put("userId", "123456");
data.put("token", "abcdef123456");
String js = String.format(
"javascript:window.WebViewJavascriptBridge._handlers['onLoginSuccess'](JSON.parse('%s'));",
data.toString()
);
webView.evaluateJavascript(js, null);
}
iOS 端代码:
// 设置 WKUIDelegate 拦截 prompt
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
if prompt.hasPrefix("WebViewJavascriptBridge:") {
let jsonStr = prompt.dropFirst("WebViewJavascriptBridge:".count)
handleBridgeMessage(jsonStr: String(jsonStr))
completionHandler("success")
return
}
completionHandler(nil)
}
// 处理 Bridge 消息
private func handleBridgeMessage(jsonStr: String) {
let message = try? JSONSerialization.jsonObject(with: jsonStr.data(using: .utf8)!, options: []) as? [String: Any]
let handlerName = message?["handlerName"] as? String
let data = message?["data"] as? [String: Any]
let callbackId = message?["callbackId"] as? String
// 根据 handlerName 处理
var result = [String: Any]()
if handlerName == "share" {
// 执行分享逻辑
result = ["success": true, "message": "分享成功"]
}
// 调用 JS 回调
if let callbackId = callbackId {
let resultJson = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted)
let resultStr = String(data: resultJson!, encoding: .utf8)!
let js = """
window.WebViewJavascriptBridge._callbacks['\(callbackId)'](JSON.parse('\(resultStr)'));
delete window.WebViewJavascriptBridge._callbacks['\(callbackId)'];
"""
webView.evaluateJavaScript(js, completionHandler: nil)
}
}
// 调用 JS 方法
private func callJsMethod() {
let data = [
"userId": "123456",
"token": "abcdef123456"
]
let dataJson = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
let dataStr = String(data: dataJson!, encoding: .utf8)!
let js = "window.WebViewJavascriptBridge._handlers['onLoginSuccess'](JSON.parse('\(dataStr)'))"
webView.evaluateJavaScript(js, completionHandler: nil)
}
四、三种方法的优缺点比较
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| URL Scheme | 兼容性好,实现简单,无需修改原生代码结构 | 参数长度有限制,安全性低,易被拦截 | 简单交互,旧版本兼容 |
| 注入 API | 使用简单,调用方便,性能好 | Android 4.2 以下有安全漏洞,参数类型限制多 | 高性能要求,简单交互 |
| WebViewJavascriptBridge | 功能完善,支持异步回调,安全性高,参数灵活 | 实现复杂,需要引入框架或自行实现 | 复杂交互,高安全性要求 |
五、最佳实践建议
- 优先选择 WebViewJavascriptBridge:功能完善,安全性高,是目前主流方案
- 安全第一:无论使用哪种方式,都要进行参数验证和过滤
- 统一接口:定义清晰的通信协议,规范方法名和参数格式
- 错误处理:完善的错误处理机制,提高稳定性
- 性能优化:避免频繁通信,批量处理请求
六、常用框架推荐
- WebViewJavascriptBridge:iOS/Android 跨平台支持
- DSBridge:更强大的跨平台 JS Bridge 框架
- EasyJSWebView:轻量级的 JS Bridge 实现
- Cordova/PhoneGap:成熟的混合开发框架,内置完善的 JS Bridge 机制