以下是重新设计的封装方案,涵盖双向通信、异步回调、错误处理和日志记录等功能。


Vue.js 与原生 APP 通信封装方案

1. 需求概述

在混合开发中,Vue.js 前端页面需要与原生 APP 进行通信,常见的需求包括:

  • 从 Vue 向原生发送数据(如用户操作、请求原生功能)。

  • 从原生向 Vue 发送数据(如原生状态更新、回调)。

  • 触发原生页面或功能(如打开设置、调用摄像头)。

  • 跨平台兼容性(iOS 和 Android)。

  • 支持异步回调(原生调用 Vue 方法)。

  • 错误处理与日志记录(便于调试)。


2. 封装设计

我们将封装一个通用的通信模块 NativeBridge,并拆分为以下子模块:

  1. NativeBridge:核心通信模块,封装发送消息、接收消息、调用原生方法等功能。

  2. NativeLogger:日志记录模块,用于记录通信过程中的信息和错误。

  3. NativeCallbackManager:异步回调管理模块,支持原生调用 Vue 方法。


3. 代码实现

3.1 NativeLogger.js

日志记录模块,用于记录通信过程中的信息和错误。

// NativeLogger.js
const log = {
  info(message) {
    console.log(`[NativeBridge] INFO: ${message}`);
  },
  error(message) {
    console.error(`[NativeBridge] ERROR: ${message}`);
  }
};

export default log;
3.2 NativeCallbackManager.js

异步回调管理模块,支持原生调用 Vue 方法。

// NativeCallbackManager.js
const callbacks = {};

export const registerCallback = (callback) => {
  const callbackId = Date.now().toString(36); // 生成唯一回调 ID
  callbacks[callbackId] = callback;
  return callbackId;
};

export const invokeCallback = (callbackId, data) => {
  if (callbacks[callbackId]) {
    callbacks[callbackId](data);
    delete callbacks[callbackId];
  }
};
3.3 NativeBridge.js

核心通信模块,封装发送消息、接收消息、调用原生方法等功能。

// NativeBridge.js
import log from "./NativeLogger";
import { registerCallback, invokeCallback } from "./NativeCallbackManager";

const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isAndroid = /Android/.test(navigator.userAgent);

const platform = isIOS ? "iOS" : isAndroid ? "Android" : null;

export const sendMessageToNative = (data, callback) => {
  if (!platform) {
    log.error("Unsupported platform");
    return;
  }

  try {
    const callbackId = callback ? registerCallback(callback) : null;
    const message = { data, callbackId };

    if (isIOS) {
      window.webkit.messageHandlers.nativeListener.postMessage(message);
    } else if (isAndroid) {
      window.parent.postMessage(message, "*");
    }

    log.info(`Message sent to native: ${JSON.stringify(data)}`);
  } catch (error) {
    log.error(`Failed to send message: ${error.message}`);
  }
};

export const receiveMessageFromNative = (callback) => {
  window.addEventListener("message", (event) => {
    if (event.source === window.parent) {
      try {
        const { data, callbackId } = event.data;
        log.info(`Message received from native: ${JSON.stringify(data)}`);

        if (callbackId) {
          invokeCallback(callbackId, data);
        } else {
          callback(data);
        }
      } catch (error) {
        log.error(`Failed to handle message: ${error.message}`);
      }
    }
  });
};

export const callNativeMethod = (methodName, data) => {
  if (!platform) {
    log.error("Unsupported platform");
    return;
  }

  try {
    if (isIOS) {
      window.webkit.messageHandlers[methodName].postMessage(data);
    } else if (isAndroid) {
      window.nativeBridge[methodName](data);
    }

    log.info(`Called native method: ${methodName}`);
  } catch (error) {
    log.error(`Failed to call native method: ${error.message}`);
  }
};

4. 在 Vue 组件中使用

4.1 Vue 组件示例

在 Vue 组件中,可以通过导入 NativeBridge 模块来使用封装好的通信功能。

<template>
  <div>
    <button @click="sendMessage">Send Message to Native</button>
    <button @click="openNativePage">Open Native Page</button>
    <button @click="callNativeMethod">Call Native Method</button>
  </div>
</template>

<script>
import { sendMessageToNative, receiveMessageFromNative, callNativeMethod } from "./NativeBridge";

export default {
  mounted() {
    // 监听原生发送的消息
    receiveMessageFromNative((message) => {
      console.log("Received message from native:", message);
    });
  },
  methods: {
    sendMessage() {
      // 向原生发送消息并接收回调
      sendMessageToNative({ text: "Hello from Vue!" }, (response) => {
        console.log("Native response:", response);
      });
    },
    openNativePage() {
      // 触发原生页面
      window.location.href = "myapp://openPage";
    },
    callNativeMethod() {
      // 调用原生方法
      callNativeMethod("sendMessage", { text: "Hello from Vue!" });
    },
  },
};
</script>

5. 原生端实现

5.1 iOS 实现

在 iOS 中,通过 WKWebViewWKScriptMessageHandler 接收和处理消息。

import WebKit

class ViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let config = WKWebViewConfiguration()
        let userContentController = WKUserContentController()
        userContentController.add(self, name: "nativeListener")
        userContentController.add(self, name: "sendMessage")
        config.userContentController = userContentController
        webView = WKWebView(frame: self.view.bounds, configuration: config)
        view.addSubview(webView)
        webView.load(URLRequest(url: URL(string: "https://your-vue-app.com")!))
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "nativeListener" {
            if let body = message.body as? [String: Any] {
                print("Received message: \(body)")
                // 处理消息
                if let callbackId = body["callbackId"] as? String {
                    webView.evaluateJavaScript("window.invokeCallback('\(callbackId)', { response: 'Hello from iOS!' })")
                }
            }
        } else if message.name == "sendMessage" {
            if let body = message.body as? [String: Any] {
                print("Received message: \(body)")
                // 处理消息
            }
        }
    }
}
5.2 Android 实现

在 Android 中,通过 addJavascriptInterface 暴露原生方法。

import android.webkit.WebView;
import android.webkit.JavascriptInterface;

public class MainActivity extends AppCompatActivity {
    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webView = findViewById(R.id.webview);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(new NativeBridge(), "nativeBridge");
        webView.loadUrl("https://your-vue-app.com");
    }

    public class NativeBridge {
        @JavascriptInterface
        public void sendMessage(String message, String callbackId) {
            // 处理消息
            Log.d("NativeBridge", "Received message: " + message);
            webView.evaluateJavascript("javascript:invokeCallback('" + callbackId + "', { response: 'Hello from Android!' })", null);
        }
    }
}

6. 进一步扩展

如果需要更高级的功能,比如文件传输、状态同步等,可以在 NativeBridge.js 中添加更多的方法。例如:

export const uploadFileToNative = (file) => {
  if (!platform) {
    log.error("Unsupported platform");
    return;
  }

  try {
    if (isIOS) {
      window.webkit.messageHandlers.uploadFile.postMessage(file);
    } else if (isAndroid) {
      window.nativeBridge.uploadFile(file);
    }

    log.info(`File upload request sent`);
  } catch (error) {
    log.error(`Failed to upload file: ${error.message}`);
  }
};

7. 总结

通过模块化设计和功能拆分,我们优化了 Vue.js 与原生 APP 的通信模块,使其具备以下特点:

  1. 异步回调支持:支持原生调用 Vue 方法。

  2. 错误处理与日志记录:便于调试和问题排查。

  3. 灵活的双向通信:支持多种通信方式,包括消息传递、URL Scheme 和原生方法调用。

  4. 易扩展性:模块化设计使得功能扩展更加方便。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐