原生 App 与 Uniapp 的双向通信是通过 “桥接层(Bridge)” 实现的,核心原理是:在原生代码与 Uniapp 的 JS 代码之间建立一个通信通道,通过标准化的消息格式(如 JSON)传递数据,实现“原生调用 Uniapp 方法”和“Uniapp 调用原生方法”的双向交互。以下从底层机制、实现方式、跨平台差异三个维度详细说明。
一、双向通信的底层机制
Uniapp 本质是基于 WebView 或自研渲染引擎(如 weex)的跨平台框架,其与原生的通信依赖 “JS 桥接(JS Bridge)” 技术,核心分为三个部分:
通信通道:
- 原生端通过注入全局对象(如 Android 的
addJavascriptInterface、iOS 的WKScriptMessageHandler)或拦截 URL Schema(如uni://协议)接收 JS 调用。 - Uniapp 的 JS 端通过调用原生注入的全局方法(如
window.NativeBridge)或发送事件,向原生传递消息。
- 原生端通过注入全局对象(如 Android 的
消息格式:
双方约定统一的消息结构(通常为 JSON),包含:method:调用的方法名(如openCamera);params:传递的参数(如{ "width": 1080 });callbackId:回调唯一标识(用于区分异步调用的返回结果)。
回调机制:
异步调用时,通过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 端:
使用DCUniSDK的evaluateJavaScript执行 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 桥接”技术,通过以下流程实现:
- Uniapp 调用原生:JS 调用原生注册的模块方法 → 原生执行逻辑 → 原生通过回调返回结果。
- 原生调用 Uniapp:原生执行 JS 代码或发送事件 → Uniapp 执行对应方法 → JS 通过回调返回结果。
这种机制既保留了原生功能的性能优势,又发挥了 Uniapp 跨平台开发的效率,是混合开发中功能复用和数据交互的核心基础。