【移动端知识】移动端多 WebView 互访方案:Android、iOS 与鸿蒙实现

发布于:2025-07-21 ⋅ 阅读:(15) ⋅ 点赞:(0)

移动端多 WebView 互访方案:Android、iOS 与鸿蒙实现

一、核心架构设计

平台层
JS调用
消息路由
调用目标
执行JS
Android
Native Bridge
iOS
鸿蒙
WebView A
平台控制器
WebView B
WebView B页面

二、Android 平台实现

1. 基础通信架构

// WebViewBridge.java
public class WebViewBridge {
    private WeakReference<WebView> mWebViewRef;
    private String mBridgeName;
    
    public WebViewBridge(WebView webView, String bridgeName) {
        mWebViewRef = new WeakReference<>(webView);
        mBridgeName = bridgeName;
        webView.addJavascriptInterface(this, bridgeName);
    }
    
    @JavascriptInterface
    public void postMessage(String targetBridge, String message) {
        MainActivity activity = (MainActivity) mWebViewRef.get().getContext();
        activity.routeMessage(targetBridge, message);
    }
    
    public void receiveMessage(String message) {
        WebView webView = mWebViewRef.get();
        if (webView != null) {
            String js = String.format("window.%s.onMessage('%s')", 
                                     mBridgeName, message);
            webView.evaluateJavascript(js, null);
        }
    }
}

2. 控制器实现

// MainActivity.java
public class MainActivity extends AppCompatActivity {
    private WebView webViewA, webViewB;
    private WebViewBridge bridgeA, bridgeB;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        webViewA = findViewById(R.id.webview_a);
        webViewB = findViewById(R.id.webview_b);
        
        // 初始化桥接
        bridgeA = new WebViewBridge(webViewA, "BridgeA");
        bridgeB = new WebViewBridge(webViewB, "BridgeB");
        
        // 加载HTML
        webViewA.loadUrl("file:///android_asset/webview_a.html");
        webViewB.loadUrl("file:///android_asset/webview_b.html");
    }
    
    // 消息路由方法
    public void routeMessage(String targetBridge, String message) {
        if ("BridgeA".equals(targetBridge)) {
            bridgeA.receiveMessage(message);
        } else if ("BridgeB".equals(targetBridge)) {
            bridgeB.receiveMessage(message);
        }
    }
}

3. WebView 安全配置

// 启用JavaScript
webView.getSettings().setJavaScriptEnabled(true);

// 防止内存泄漏
webView.setWebViewClient(new WebViewClient());
webView.setWebChromeClient(new WebChromeClient());

// 文件访问权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    webView.getSettings().setAllowFileAccessFromFileURLs(true);
    webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
}

三、iOS 平台实现 (Swift)

1. WKWebView 通信桥接

// WebViewBridge.swift
class WebViewBridge: NSObject, WKScriptMessageHandler {
    private weak var webView: WKWebView?
    private let bridgeName: String
    
    init(webView: WKWebView, bridgeName: String) {
        self.webView = webView
        self.bridgeName = bridgeName
        super.init()
        webView.configuration.userContentController.add(self, name: bridgeName)
    }
    
    func userContentController(_ controller: WKUserContentController, 
                              didReceive message: WKScriptMessage) {
        guard let body = message.body as? [String: Any],
              let target = body["target"] as? String,
              let msg = body["message"] as? String else {
            return
        }
        
        if let delegate = UIApplication.shared.delegate as? AppDelegate {
            delegate.routeMessage(target: target, message: msg)
        }
    }
    
    func sendMessage(_ message: String) {
        let js = "window.\(bridgeName).onMessage('\(message)')"
        webView?.evaluateJavaScript(js, completionHandler: nil)
    }
}

2. AppDelegate 路由控制

// AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var webViewA: WKWebView!
    var webViewB: WKWebView!
    var bridgeA: WebViewBridge!
    var bridgeB: WebViewBridge!
    
    func application(_ application: UIApplication, 
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // 创建WebView
        webViewA = WKWebView(frame: .zero)
        webViewB = WKWebView(frame: .zero)
        
        // 初始化桥接
        bridgeA = WebViewBridge(webView: webViewA, bridgeName: "BridgeA")
        bridgeB = WebViewBridge(webView: webViewB, bridgeName: "BridgeB")
        
        // 加载HTML
        if let urlA = Bundle.main.url(forResource: "webview_a", withExtension: "html") {
            webViewA.loadFileURL(urlA, allowingReadAccessTo: urlA)
        }
        
        if let urlB = Bundle.main.url(forResource: "webview_b", withExtension: "html") {
            webViewB.loadFileURL(urlB, allowingReadAccessTo: urlB)
        }
        
        return true
    }
    
    // 消息路由
    func routeMessage(target: String, message: String) {
        if target == "BridgeA" {
            bridgeA.sendMessage(message)
        } else if target == "BridgeB" {
            bridgeB.sendMessage(message)
        }
    }
}

四、鸿蒙平台实现 (HarmonyOS)

1. WebView 通信桥接

// HarmonyBridge.java
public class HarmonyBridge {
    private WebView webView;
    private String bridgeName;
    private Context context;
    
    public HarmonyBridge(Context context, WebView webView, String bridgeName) {
        this.context = context;
        this.webView = webView;
        this.bridgeName = bridgeName;
        this.webView.addJsInterface(this, bridgeName);
    }
    
    @JSFunction
    public void postMessage(String targetBridge, String message) {
        // 获取主Ability
        MainAbility mainAbility = (MainAbility) context;
        mainAbility.routeMessage(targetBridge, message);
    }
    
    public void receiveMessage(String message) {
        String js = "window." + bridgeName + ".onMessage('" + message + "')";
        webView.executeJs(js);
    }
}

2. Ability 控制器实现

// MainAbility.java
public class MainAbility extends Ability {
    private WebView webViewA;
    private WebView webViewB;
    private HarmonyBridge bridgeA;
    private HarmonyBridge bridgeB;
    
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        DirectionalLayout layout = new DirectionalLayout(this);
        
        // 创建WebViewA
        webViewA = new WebView(this);
        webViewA.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
        webViewA.setHeight(ComponentContainer.LayoutConfig.MATCH_CONTENT);
        webViewA.setWeight(1);
        webViewA.load("file:///assets/webview_a.html");
        
        // 创建WebViewB
        webViewB = new WebView(this);
        webViewB.setWidth(ComponentContainer.LayoutConfig.MATCH_PARENT);
        webViewB.setHeight(ComponentContainer.LayoutConfig.MATCH_CONTENT);
        webViewB.setWeight(1);
        webViewB.load("file:///assets/webview_b.html");
        
        // 初始化桥接
        bridgeA = new HarmonyBridge(this, webViewA, "BridgeA");
        bridgeB = new HarmonyBridge(this, webViewB, "BridgeB");
        
        // 添加到布局
        layout.addComponent(webViewA);
        layout.addComponent(webViewB);
        setUIContent(layout);
    }
    
    // 消息路由
    public void routeMessage(String targetBridge, String message) {
        if ("BridgeA".equals(targetBridge)) {
            bridgeA.receiveMessage(message);
        } else if ("BridgeB".equals(targetBridge)) {
            bridgeB.receiveMessage(message);
        }
    }
}

3. 鸿蒙 WebView 配置

// 启用JavaScript
WebConfig webConfig = webView.getWebConfig();
webConfig.setJavaScriptPermit(true);

// 设置WebView代理
webView.setWebAgent(new WebAgent() {
    @Override
    public boolean onPageStart(WebView webView, String url) {
        // 页面开始加载处理
        return true;
    }
});

五、通用 JavaScript 接口

1. 跨平台通信 SDK

// bridge-sdk.js
class CrossWebViewBridge {
    constructor(bridgeName) {
        this.bridgeName = bridgeName;
        this.messageHandlers = {};
    }
    
    postMessage(targetBridge, message) {
        // Android
        if (window.AndroidBridge) {
            window.AndroidBridge.postMessage(targetBridge, message);
        }
        // iOS
        else if (window.webkit && window.webkit.messageHandlers[this.bridgeName]) {
            window.webkit.messageHandlers[this.bridgeName].postMessage({
                target: targetBridge,
                message: message
            });
        }
        // HarmonyOS
        else if (window.HarmonyBridge) {
            window.HarmonyBridge.postMessage(targetBridge, message);
        }
    }
    
    onMessage(handler) {
        this.messageHandlers['default'] = handler;
    }
    
    // 供原生调用的方法
    __onNativeMessage(message) {
        if (this.messageHandlers['default']) {
            this.messageHandlersmessage;
        }
    }
}

// 初始化
window.BridgeA = new CrossWebViewBridge('BridgeA');
window.BridgeB = new CrossWebViewBridge('BridgeB');

2. HTML 页面集成

<!-- webview_a.html -->
<!DOCTYPE html>
<html>
<head>
    <title>WebView A</title>
    <script src="bridge-sdk.js"></script>
</head>
<body>
    <button onclick="sendMessage()">发送消息到WebView B</button>
    
    <script>
        // 初始化桥接
        const bridgeA = new CrossWebViewBridge('BridgeA');
        
        // 注册消息处理器
        bridgeA.onMessage(function(message) {
            console.log('WebViewA收到消息:', message);
            document.getElementById('output').innerText = message;
        });
        
        // 发送消息
        function sendMessage() {
            bridgeA.postMessage('BridgeB', 'Hello from WebView A');
        }
    </script>
    
    <div id="output"></div>
</body>
</html>

六、平台差异处理表

功能 Android iOS 鸿蒙 解决方案
JS接口注入 addJavascriptInterface WKUserContentController addJsInterface 统一桥接SDK
JS执行方式 evaluateJavascript evaluateJavaScript executeJs 封装原生方法
文件访问 需权限配置 沙盒限制 资源目录访问 使用相对路径
内存管理 WeakReference weak var 自动回收 弱引用处理
后台通信 Service支持 后台限制 ServiceAbility 消息队列缓存

七、高级功能实现

1. 双向实时通信

// 在WebView A中
bridgeA.onMessage(function(message) {
    console.log('实时消息:', message);
    // 立即回复
    bridgeA.postMessage('BridgeB', '收到消息');
});

// 在WebView B中
setInterval(() => {
    bridgeB.postMessage('BridgeA', `心跳 ${Date.now()}`);
}, 5000);

2. 文件传输支持

// Android 文件传输
@JavascriptInterface
public void sendFile(String base64Data, String fileName) {
    byte[] data = Base64.decode(base64Data, Base64.DEFAULT);
    // 保存文件
    File file = new File(getFilesDir(), fileName);
    try (FileOutputStream fos = new FileOutputStream(file)) {
        fos.write(data);
    }
    
    // 通知目标WebView
    String message = "file://" + file.getAbsolutePath();
    routeMessage("BridgeB", message);
}

3. 安全控制措施

// Android 源验证
private boolean isValidOrigin(String origin) {
    return Arrays.asList(
        "file:///android_asset/", 
        "https://trusted-domain.com"
    ).contains(origin);
}

// 在WebViewClient中
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    if (!isValidOrigin(request.getUrl().toString())) {
        return true; // 阻止加载
    }
    return false;
}

八、调试与监控

1. 统一日志系统

class BridgeLogger {
    constructor(bridgeName) {
        this.bridgeName = bridgeName;
    }
    
    log(message, level = 'info') {
        // 发送日志到原生
        const payload = {
            type: 'log',
            level: level,
            message: message
        };
        this.bridge.postMessage('Logger', JSON.stringify(payload));
    }
}

// 集成到SDK
CrossWebViewBridge.prototype.log = function(message) {
    this.logger.log(message);
};

2. 性能监控

// Android 性能监控
private void startPerformanceMonitoring() {
    new Timer().scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo();
            Debug.getMemoryInfo(memoryInfo);
            
            long totalMemory = memoryInfo.getTotalPss();
            int cpuUsage = getCpuUsage();
            
            String message = String.format(
                "{\"memory\":%d,\"cpu\":%d}", 
                totalMemory, cpuUsage
            );
            
            bridgeA.postMessage("Monitor", message);
        }
    }, 0, 5000); // 每5秒监控一次
}

九、最佳实践建议

  1. 消息协议标准化

    {
      "version": "1.0",
      "id": "uuid",
      "timestamp": 1685091200,
      "source": "BridgeA",
      "target": "BridgeB",
      "type": "text/json/file",
      "payload": {}
    }
    
  2. 错误处理机制

    try {
      bridgeA.postMessage("BridgeB", largeData);
    } catch (e) {
      if (e.message.includes("Message too long")) {
        // 分片发送
        sendInChunks(largeData);
      }
    }
    
  3. 心跳保活

    // Android心跳服务
    public class HeartbeatService extends Service {
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            new Thread(() -> {
                while (true) {
                    if (webViewA != null) {
                        webViewA.post(() -> 
                            webViewA.evaluateJavascript("BridgeA.ping()", null)
                        );
                    }
                    Thread.sleep(30000);
                }
            }).start();
            return START_STICKY;
        }
    }
    

十、完整实现流程图

WebView A Native Bridge Platform Router WebView B postMessage(target, data) 路由请求 调用目标bridge 执行onMessage (可选)回复消息 WebView A Native Bridge Platform Router WebView B

通过以上方案,可在Android、iOS和鸿蒙平台上实现高效稳定的WebView间通信。关键点在于:

  1. 使用标准化通信协议
  2. 统一JavaScript接口
  3. 平台特定桥接实现
  4. 完善的安全控制
  5. 性能优化措施

实际部署时建议:

  • 在小流量环境验证
  • 逐步完善错误处理
  • 添加详细日志监控
  • 定期进行安全审计

网站公告

今日签到

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