原生与 Uniapp 的通信机制是如何实现双向调用的?

简介: 原生与 Uniapp 的通信机制是如何实现双向调用的?

原生 App 与 Uniapp 的双向通信是通过 “桥接层(Bridge)” 实现的,核心原理是:在原生代码与 Uniapp 的 JS 代码之间建立一个通信通道,通过标准化的消息格式(如 JSON)传递数据,实现“原生调用 Uniapp 方法”和“Uniapp 调用原生方法”的双向交互。以下从底层机制、实现方式、跨平台差异三个维度详细说明。

一、双向通信的底层机制

Uniapp 本质是基于 WebView 或自研渲染引擎(如 weex)的跨平台框架,其与原生的通信依赖 “JS 桥接(JS Bridge)” 技术,核心分为三个部分:

  1. 通信通道

    • 原生端通过注入全局对象(如 Android 的 addJavascriptInterface、iOS 的 WKScriptMessageHandler)或拦截 URL Schema(如 uni:// 协议)接收 JS 调用。
    • Uniapp 的 JS 端通过调用原生注入的全局方法(如 window.NativeBridge)或发送事件,向原生传递消息。
  2. 消息格式
    双方约定统一的消息结构(通常为 JSON),包含:

    • method:调用的方法名(如 openCamera);
    • params:传递的参数(如 { "width": 1080 });
    • callbackId:回调唯一标识(用于区分异步调用的返回结果)。
  3. 回调机制
    异步调用时,通过 callbackId 关联请求与响应。例如:Uniapp 调用原生方法后,原生处理完成后通过相同的 callbackId 将结果返回给 JS。

二、双向调用的具体实现方式

1. 方向一:Uniapp(JS)调用原生方法

Uniapp 通过 uni.requireNativePlugin 或直接调用原生注入的 JS 对象,触发原生代码逻辑。

步骤 1:原生端注册可被调用的方法
  • Android 端
    通过 UniModule 基类定义方法,并使用 @UniJSMethod 注解暴露给 JS(基于 Uniapp SDK):

    // 自定义原生模块(需继承 UniModule)
    public class NativeBridgeModule extends UniModule {
         
        // 注解 @UniJSMethod 标记该方法可被 Uniapp 调用
        @UniJSMethod(uiThread = true) // uiThread = true 表示在主线程执行
        public void openCamera(JSONObject params, UniJSCallback callback) {
         
            // 1. 解析参数(如照片尺寸)
            int width = params.optInt("width", 1080);
            // 2. 执行原生逻辑(调用相机)
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            mUniSDKInstance.getContext().startActivity(intent);
            // 3. 回调结果给 Uniapp
            JSONObject result = new JSONObject();
            result.put("code", 0);
            result.put("msg", "相机已打开");
            callback.invoke(result); // 异步回调
        }
    }
    
  • iOS 端
    通过 DCUniModule 基类定义方法,使用 DC_EXPORT_METHOD 宏暴露给 JS:

    // 自定义原生模块(继承 DCUniModule)
    @interface NativeBridgeModule : DCUniModule
    // 宏 DC_EXPORT_METHOD 声明方法可被 JS 调用
    DC_EXPORT_METHOD(@selector(openCamera:callback:))
    - (void)openCamera:(NSDictionary *)params callback:(DCUniJSCallback)callback;
    @end
    
    @implementation NativeBridgeModule
    - (void)openCamera:(NSDictionary *)params callback:(DCUniJSCallback)callback {
        // 1. 解析参数
        NSNumber *width = params[@"width"] ?: @1080;
        // 2. 执行原生逻辑(调用相机)
        UIImagePickerController *picker = [[UIImagePickerController alloc] init];
        picker.sourceType = UIImagePickerControllerSourceTypeCamera;
        [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:picker animated:YES completion:nil];
        // 3. 回调结果
        NSDictionary *result = @{@"code": @0, @"msg": @"相机已打开"};
        callback(result); // 异步回调
    }
    @end
    
步骤 2:Uniapp 端调用原生方法

通过 uni.requireNativePlugin 获取原生模块实例,直接调用暴露的方法:

// Uniapp 页面中调用原生相机
const nativeBridge = uni.requireNativePlugin('NativeBridgeModule'); // 模块名与原生类名一致
nativeBridge.openCamera(
  {
    width: 1080 }, // 参数
  (res) => {
    // 回调函数
    console.log('原生返回结果:', res); // 输出 { code: 0, msg: "相机已打开" }
  }
);

2. 方向二:原生调用 Uniapp(JS)方法

原生通过执行 JS 代码或发送事件,调用 Uniapp 中定义的方法,通常用于主动向 Uniapp 推送数据(如原生收到推送消息后通知 Uniapp 刷新页面)。

步骤 1:Uniapp 端注册可被调用的方法

将方法挂载到 window 对象(全局可访问),或通过 uni.onNativeEventReceive 监听原生事件:

// 方式 1:挂载到 window,供原生直接调用
window.updateUserInfo = function(userInfo, callback) {
   
  console.log('收到原生用户信息:', userInfo);
  // 执行 Uniapp 逻辑(如更新页面数据)
  this.userInfo = userInfo;
  // 回调结果给原生
  callback({
    code: 0, msg: "信息已更新" });
};

// 方式 2:监听原生发送的事件(推荐,解耦性更好)
uni.onNativeEventReceive((res) => {
   
  console.log('收到原生事件:', res);
  // 处理事件(如刷新页面)
}, "userInfoUpdated"); // 事件名:用于区分不同事件
步骤 2:原生端调用 Uniapp 方法
  • Android 端
    使用 UniSDKEngine.evaluateJavascript 执行 JS 代码,调用 window 方法或发送事件:

    // 方式 1:直接调用 window 上的方法
    String jsCode = "window.updateUserInfo(" +
        "{\"name\":\"张三\",\"age\":20}," + // 参数(JSON 字符串)
        "function(res) { " +
            "window.NativeCallback.onResult(JSON.stringify(res));" + // 接收 JS 回调
        "}" +
    ");";
    // 在主线程执行 JS 代码
    runOnUiThread(() -> {
         
        UniSDKEngine.evaluateJavascript(jsCode, null);
    });
    
    // 方式 2:发送事件(通过 Uniapp SDK 内置方法)
    UniSDKEngine.sendEvent(
        "userInfoUpdated", // 事件名(需与 Uniapp 监听的一致)
        "{\"name\":\"张三\",\"age\":20}" // 事件数据(JSON 字符串)
    );
    
  • iOS 端
    使用 DCUniSDKevaluateJavaScript 执行 JS 代码,或 postMessageToUniApp 发送事件:

    // 方式 1:直接调用 window 上的方法
    NSDictionary *userInfo = @{@"name": @"张三", @"age": @20};
    NSString *userInfoStr = [self dictionaryToJson:userInfo]; // 转为 JSON 字符串
    NSString *jsCode = [NSString stringWithFormat:
        @"window.updateUserInfo(%@, function(res) { "
            @"window.webkit.messageHandlers.NativeCallback.postMessage(res); "
        "});", userInfoStr];
    [[DCUniSDK sharedInstance] evaluateJavaScript:jsCode completionHandler:nil];
    
    // 方式 2:发送事件
    [[DCUniSDK sharedInstance] postMessageToUniApp:
        @"userInfoUpdated" // 事件名
        data:userInfo]; // 事件数据(NSDictionary)
    

三、跨平台差异与统一通信的最佳实践

1. 平台差异点

维度 Android 实现 iOS 实现
JS 调用原生 基于 UniModule + @UniJSMethod 基于 DCUniModule + DC_EXPORT_METHOD
原生调用 JS UniSDKEngine.evaluateJavascript DCUniSDK.evaluateJavaScript
事件发送 UniSDKEngine.sendEvent DCUniSDK.postMessageToUniApp
回调机制 UniJSCallback 接口 DCUniJSCallback 闭包

2. 最佳实践

  • 统一消息格式:无论是 JS 调用原生还是原生调用 JS,参数和返回值均使用 JSON 格式,避免类型转换问题(如数字、布尔值的跨语言兼容)。
  • 优先使用事件通信:对于非实时响应的场景(如原生推送数据给 Uniapp),推荐用“事件监听”模式(sendEvent + uni.onNativeEventReceive),减少方法名硬编码,降低耦合。
  • 处理异步调用:所有涉及 IO 或耗时操作的调用(如网络请求、硬件交互)必须异步化,通过回调返回结果,避免阻塞 UI 线程。
  • 权限与异常处理:原生方法需判断权限(如相机权限),若缺失则通过回调返回错误信息;Uniapp 需捕获原生调用失败的异常(如方法不存在)。
  • 避免频繁通信:高频数据交互(如实时定位)建议批量传递数据,或通过原生插件直接处理后再回调结果,减少通信开销。

总结

原生与 Uniapp 的双向通信基于“JS 桥接”技术,通过以下流程实现:

  1. Uniapp 调用原生:JS 调用原生注册的模块方法 → 原生执行逻辑 → 原生通过回调返回结果。
  2. 原生调用 Uniapp:原生执行 JS 代码或发送事件 → Uniapp 执行对应方法 → JS 通过回调返回结果。

这种机制既保留了原生功能的性能优势,又发挥了 Uniapp 跨平台开发的效率,是混合开发中功能复用和数据交互的核心基础。

相关文章
|
11月前
|
JavaScript 开发工具 Android开发
如何在原生 App 中调用 Uniapp 的页面?
如何在原生 App 中调用 Uniapp 的页面?
2319 138
|
11月前
|
移动开发 开发工具 Android开发
Uniapp与原生App集成的流程
Uniapp与原生App集成的流程
4055 156
|
11月前
|
JavaScript API 开发工具
如何在原生App中调用Uniapp的原生功能?
如何在原生App中调用Uniapp的原生功能?
1189 139
|
物联网 开发工具 Android开发
UniApp调用SDK原生接口
UniApp调用SDK原生接口
3446 0
UniApp调用SDK原生接口
|
移动开发 开发框架 JavaScript
uniapp如何与原生应用进行混合开发?
uniapp如何与原生应用进行混合开发?
1855 0
|
JavaScript API
uniapp使用Vue3挂载函数到全局
uniapp使用Vue3挂载函数到全局
1705 0
|
11月前
|
移动开发 JavaScript API
Uniapp 与原生 App 集成时如何解决兼容性问题?
Uniapp 与原生 App 集成时如何解决兼容性问题?
1339 136
|
11月前
|
移动开发 JavaScript 开发工具
uniapp接入app
uniapp接入app
1429 156
|
11月前
|
缓存 移动开发 JavaScript
如何优化UniApp开发的App的启动速度?
如何优化UniApp开发的App的启动速度?
1685 139