JSBridge原理与实现全解析

发布于:2025-07-31 ⋅ 阅读:(13) ⋅ 点赞:(0)

JSBridge 是用于连接 JavaScript(H5) 和原生应用(iOS/Android)的桥梁,允许它们之间相互调用方法。

🌉 一、JSBridge 双向通信流程图

Android
iOS
成功
失败
JavaScript 调用原生
平台判断
window.AndroidJSBridge.call
webkit.messageHandlers.postMessage
原生解析 method/params/callbackId
执行原生操作
设备信息/分享/相机等
invokeCallback 回调 JS
返回错误信息
JS 执行回调函数

⚙️二、基本调用模式

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 {
    // 可以在这里处理页面加载事件
}

🛡️ 三、安全与性能优化建议

  1. 安全防护

    • 方法白名单校验:原生端只响应预注册的方法名
    if (!allowedMethods.contains(method)) { 
        sendError(callbackId, "Method forbidden");
    }
    
    • 输入消毒:对JS传入参数进行正则校验
    • HTTPS 通信:防止中间人攻击
  2. 性能优化

    • 避免主线程阻塞:Android 使用 Handler 处理耗时操作
    • 回调超时机制:JS 端设置 5s 超时清理回调函数
    setTimeout(() => {
        delete JSBridge.callbacks[callbackId];
    }, 5000);
    
    • 大数据分片传输:超过 1MB 数据使用分块传输
  3. 跨平台兼容

    • 统一调用接口:封装 JSBridge.invoke() 抹平平台差异
    • 版本检测逻辑:
    const isIOS = /(iPhone|iPad)/i.test(navigator.userAgent);
    const isNewIOS = isIOS && !!window.webkit?.messageHandlers;
    

💻 四、完整调用示例

JavaScript Native callNative('share', {title:'Hello'}, cb) 解析参数,执行分享 invokeCallback(cbId, {success:true}) 执行回调函数 JavaScript Native

通过该设计,JSBridge 实现了 跨平台调用标准化(Android/iOS 接口统一)、双向通信可靠化(回调ID机制)、安全控制精细化(方法白名单+参数校验)。实际开发建议使用开源库(如https://github.com/marcuswestin/WebViewJavascriptBridge)减少底层适配成本。


网站公告

今日签到

点亮在社区的每一天
去签到