WebView 与原生通信的 5 种方式,你用过几种?

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 #移动端开发 #原生通信

相关推荐
2501_915909065 天前
iOS应用签名的三种方法全解析:从官方到第三方工具
android·ios·小程序·https·uni-app·iphone·webview
帅次6 天前
Android 高级工程师专题深挖:WebView、Context 与初始化链
android·binder·webview·zygote·web app·dalvik
游戏开发爱好者811 天前
深入理解iOSTime Profiler:提升iOS应用性能的关键工具
android·ios·小程序·https·uni-app·iphone·webview
willhuo15 天前
# 自动化数据采集技术研究与实现:基于Playwright的抖音网页自动化方案
运维·selenium·c#·自动化·chrome devtools·webview
2501_9159090615 天前
苹果App Store上架全流程指南从注册到上线
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张19 天前
iOS应用性能优化全解析:卡顿、耗电、启动与瘦身
android·ios·性能优化·小程序·uni-app·iphone·webview
撒旦物种20 天前
Android WebView 获取内容高度
android·webview
REDcker20 天前
iOS 与 Android:浏览器引擎、WebView 与生态差异概览
android·ios·内核·浏览器·webview
XiaoLeisj20 天前
Android 短视频项目实战:从登录态回流、设置页动作分发到缓存清理、协议页复用与密码重置的完整实现个人中心与设置模块
android·mvvm·webview·arouter