WebView 与原生通信的 5 种方式,你用过几种?
做移动端 H5 开发,和原生 App 通信是必修课,今天盘点 5 种主流方案
前言
做 WebView 内嵌开发,H5 页面经常需要和原生 App 交互:
- 获取用户信息、设备信息
- 调用原生功能:拍照、定位、支付、分享
- 跳转到原生页面
- 原生通知 H5 更新数据
这就涉及到 WebView 与原生的通信问题。今天盘点 5 种主流方案,分析各自的优缺点和适用场景。
方式一:拦截 URL Scheme
原理
H5 发起一个特殊格式的 URL 请求,原生拦截这个请求,解析参数执行操作。
实现方式
H5 端
javascript
// 发起请求
function callNative(action, params) {
const query = Object.keys(params || {})
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
const url = `myapp://bridge?action=${action}${query ? '&' + query : ''}`;
// 方式1:location 跳转
window.location.href = url;
// 方式2:iframe(部分场景更稳定)
const iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.display = 'none';
document.body.appendChild(iframe);
setTimeout(() => document.body.removeChild(iframe), 100);
}
// 调用示例
callNative('getUserInfo');
callNative('openPage', { url: '/detail', id: '123' });
iOS 端(WKWebView)
swift
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
if url.scheme == "myapp" {
// 解析 URL,执行对应操作
let action = url.host // "bridge"
let query = url.query // "action=getUserInfo"
handleAction(action, params: parseQuery(query))
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
Android 端
java
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("myapp://")) {
// 解析 URL,执行对应操作
Uri uri = Uri.parse(url);
String action = uri.getQueryParameter("action");
handleAction(action, uri);
return true;
}
return false;
}
});
优缺点
优点
- 兼容性好,iOS / Android 通用
- 实现简单,原生拦截即可
- 支持异步回调
缺点
- 只能单向通信(H5 → 原生)
- URL 长度有限制(约 2KB)
- 参数需要编码,复杂数据传递麻烦
- location 跳转可能影响页面历史记录
适用场景
- 简单的功能调用
- 跳转原生页面
- 兼容老版本 WebView
方式二:注入 JS 全局对象
原理
原生往 WebView 注入一个全局对象,H5 直接调用这个对象的方法。
实现方式
iOS 端(WKWebView)
swift
// 定义原生对象
class NativeBridge: NSObject {
@objc func getUserInfo(_ callback: String) {
let userInfo = ["name": "张三", "id": "123"]
// 调用 H5 回调
webView.evaluateJavaScript("\(callback)(\(userInfo))")
}
@objc func openPage(_ params: String) {
// 解析参数,跳转页面
}
}
// 注入对象
let contentController = WKUserContentController()
contentController.add(NativeBridge(), name: "nativeBridge")
let config = WKWebViewConfiguration()
config.userContentController = contentController
Android 端
java
// 定义原生对象
public class NativeBridge {
@JavascriptInterface
public String getUserInfo() {
JSONObject userInfo = new JSONObject();
userInfo.put("name", "张三");
userInfo.put("id", "123");
return userInfo.toString();
}
@JavascriptInterface
public void openPage(String params) {
// 解析参数,跳转页面
}
}
// 注入对象
webView.addJavascriptInterface(new NativeBridge(), "nativeBridge");
H5 端
javascript
// 同步调用
const userInfo = window.nativeBridge.getUserInfo();
console.log(userInfo);
// 异步调用(iOS 需要回调函数名)
window.nativeBridge.getUserInfo('handleUserInfo');
function handleUserInfo(data) {
console.log('收到用户信息:', data);
}
优缺点
优点
- 调用方式直观,像调用普通 JS 函数
- 可以同步获取返回值(Android)
- 参数传递灵活,可以传 JSON
缺点
- iOS 和 Android 实现方式不同,需要统一封装
- Android 低版本存在安全漏洞(远程代码执行)
- iOS 只支持异步回调
适用场景
- 需要频繁调用的接口
- 需要返回值的场景
- 和原生团队配合开发
方式三:WKScriptMessageHandler(iOS 专用)
原理
iOS 的 WKWebView 提供了消息处理器机制,H5 发送消息,原生接收处理。
实现方式
iOS 端
swift
class ViewController: UIViewController, WKScriptMessageHandler {
override func viewDidLoad() {
let contentController = WKUserContentController()
contentController.add(self, name: "nativeBridge")
let config = WKWebViewConfiguration()
config.userContentController = contentController
webView = WKWebView(frame: view.bounds, configuration: config)
}
func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any] else { return }
let action = body["action"] as? String
let params = body["params"] as? [String: Any]
switch action {
case "getUserInfo":
let userInfo = ["name": "张三"]
// 回调 H5
webView.evaluateJavaScript("window.handleUserInfo(\(userInfo))")
case "openPage":
// 处理跳转
break
default:
break
}
}
}
H5 端
javascript
// 发送消息
function callNative(action, params) {
window.webkit.messageHandlers.nativeBridge.postMessage({
action: action,
params: params || {}
});
}
// 调用
callNative('getUserInfo');
callNative('openPage', { url: '/detail' });
优缺点
优点
- iOS 官方推荐方式
- 类型安全
- 性能好
缺点
- 只支持 iOS(WKWebView)
- Android 需要另外实现
方式四:console.log 拦截
原理
H5 通过 console.log 输出特定格式的信息,原生拦截 console 输出。
实现方式
H5 端
javascript
// 定义协议格式
function callNative(action, params) {
const message = JSON.stringify({
type: 'native-call',
action: action,
params: params || {}
});
console.log(message);
}
// 调用
callNative('getUserInfo');
iOS 端
swift
// 重写 console.log
let script = """
window.console = {
log: function(message) {
// 转发给原生
window.webkit.messageHandlers.console.postMessage(message);
}
};
"""
let userScript = WKUserScript(source: script,
injectionTime: .atDocumentStart,
forMainFrameOnly: true)
contentController.addUserScript(userScript)
Android 端
java
// 使用 WebChromeClient 拦截
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
String message = consoleMessage.message();
try {
JSONObject json = new JSONObject(message);
if ("native-call".equals(json.getString("type"))) {
String action = json.getString("action");
JSONObject params = json.getJSONObject("params");
handleAction(action, params);
return true;
}
} catch (Exception e) {
// 不是 JSON,正常 console.log
}
return super.onConsoleMessage(consoleMessage);
}
});
优缺点
优点
- 实现简单
- 不需要额外定义协议
缺点
- 污染 console 输出,调试时会有干扰
- 不是标准做法,不推荐用于生产环境
适用场景
- 快速原型开发
- 调试阶段临时方案
方式五:fetch / XMLHttpRequest 拦截
原理
H5 发起 HTTP 请求,原生拦截特定域名或路径的请求,返回模拟数据。
实现方式
H5 端
javascript
// 正常发请求
fetch('https://native.local/getUserInfo')
.then(res => res.json())
.then(data => console.log(data));
Android 瀑布流
java
// 使用 shouldInterceptRequest 拦截
webView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
if (url.startsWith("https://native.local/")) {
String path = request.getUrl().getPath();
String json = handleLocalRequest(path);
return new WebResourceResponse(
"application/json",
"UTF-8",
new ByteArrayInputStream(json.getBytes())
);
}
return super.shouldInterceptRequest(view, request);
}
});
iOS 端
iOS 需要使用 URLProtocol 或 WKURLSchemeHandler 来实现。
优缺点
优点
- 对 H5 完全透明,像调用普通接口
- 可以模拟完整的 HTTP 响应
- 方便调试和 Mock
缺点
- 实现较复杂
- iOS 实现难度大
适用场景
- 需要兼容 Web 端的场景
- API 模拟和调试
最佳实践:统一封装
实际项目中,需要兼容 iOS 和 Android,建议封装一个统一的 Bridge:
javascript
class NativeBridge {
constructor() {
this.callbacks = {};
this.callbackId = 0;
}
// 统一调用方法
call(action, params = {}) {
return new Promise((resolve, reject) => {
const callbackId = ++this.callbackId;
this.callbacks[callbackId] = { resolve, reject };
const message = {
action,
params,
callbackId
};
// 根据环境选择调用方式
if (window.webkit?.messageHandlers?.nativeBridge) {
// iOS WKWebView
window.webkit.messageHandlers.nativeBridge.postMessage(message);
} else if (window.nativeBridge) {
// Android 或 iOS UIWebView
window.nativeBridge.postMessage(JSON.stringify(message));
} else {
// 降级:URL Scheme
this.callByScheme(message);
}
});
}
// URL Scheme 降级方案
callByScheme(message) {
const query = Object.keys(message)
.map(key => `${key}=${encodeURIComponent(message[key])}`)
.join('&');
const iframe = document.createElement('iframe');
iframe.src = `myapp://bridge?${query}`;
iframe.style.display = 'none';
document.body.appendChild(iframe);
setTimeout(() => document.body.removeChild(iframe), 100);
}
// 接收原生回调
onCallback(callbackId, data) {
const callback = this.callbacks[callbackId];
if (callback) {
callback.resolve(data);
delete this.callbacks[callbackId];
}
}
}
// 全局实例
const bridge = new NativeBridge();
// 暴露给原生调用
window.nativeCallback = (callbackId, data) => {
bridge.onCallback(callbackId, data);
};
// 使用
bridge.call('getUserInfo').then(userInfo => {
console.log(userInfo);
});
bridge.call('openPage', { url: '/detail', id: '123' });
对比总结
| 方式 | 兼容性 | 复杂度 | 性能 | 推荐指数 |
|---|---|---|---|---|
| URL Scheme | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| 注入全局对象 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| WKScriptMessageHandler | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| console.log 拦截 | ⭐⭐⭐⭐ | ⭐ | ⭐⭐ | ⭐ |
| fetch 拦截 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
推荐方案:
- 新项目:注入全局对象 + 统一封装
- 老项目:URL Scheme 兼容
- iOS 专属:WKScriptMessageHandler
结语
WebView 与原生通信是内嵌开发的基础能力,选择合适的方案能让开发效率事半功倍。
你的项目用的是哪种方案?评论区聊聊!
#WebView #JSBridge #移动端开发 #原生通信