鸿蒙插件调试时,日志该打在 Flutter 侧还是 ArkTS 侧
适合谁看
-
正在调试鸿蒙 MethodChannel 插件的人
-
遇到"页面没反应但原生好像跑了"的人
-
不想把日志打成一团的人
问题背景
鸿蒙插件调试比普通页面调试复杂,因为你面对的是两层运行时:
-
Flutter/Dart
-
ArkTS/鸿蒙原生侧
如果日志层次不分清,很快就会陷入:这条日志是谁打的?问题到底出在哪一层?
项目中的真实场景
食界探味当前的日志分布:
|
文件 |
侧 |
日志内容 |
|---|---|---|
|
|
ArkTS |
引擎创建、权限申请、识别回调 |
|
|
ArkTS |
引擎创建、播报回调、停止 |
|
|
ArkTS |
订阅状态、蒙层设置 |
|
|
ArkTS |
入口推送、pending 缓存 |
|
|
Flutter |
通道调用结果 |
|
|
Flutter |
路由跳转、pending 消费 |
|
|
Flutter |
事件接收、状态变化 |
|
|
Flutter |
语音识别结果、AI 提交 |
核心实现
一、ArkTS 侧负责记录原生能力真实状态
鸿蒙原生侧更适合记录:
|
日志类型 |
说明 |
示例 |
|---|---|---|
|
权限申请结果 |
用户是否授权 |
|
|
引擎创建结果 |
TTS/ASR 引擎是否创建成功 |
|
|
系统状态变化 |
防窥状态、事件触发 |
|
|
监听器回调 |
回调是否触发 |
|
|
错误详情 |
原生错误码和信息 |
|
SpeechRecognitionPlugin 的日志:
// SpeechRecognitionPlugin.ets
// 引擎创建
console.info(TAG, 'Engine created successfully');
// 权限申请
console.error(TAG, `requestPermission failed: ${JSON.stringify(err)}`);
// 识别回调
console.info(TAG, `onResult: ${JSON.stringify(result)}`);
console.info(TAG, `onComplete sessionId: ${sessionId}`);
console.error(TAG, `onError code: ${errorCode}, msg: ${errorMessage}`);
// 引擎销毁
console.info(TAG, 'Engine shutdown');
TextToSpeechPlugin 的日志:
// TextToSpeechPlugin.ets
// 引擎创建
console.info(TAG, 'TTS engine created successfully');
// 播报回调
console.info(TAG, `onStart requestId: ${requestId}`);
console.info(TAG, `onComplete requestId: ${requestId}`);
console.info(TAG, `onStop requestId: ${requestId}`);
console.error(TAG, `onError requestId: ${requestId}, code: ${errorCode}, msg: ${errorMessage}`);
// 播报调用
console.info(TAG, 'speak called');
// 引擎销毁
console.info(TAG, 'TTS engine shutdown');
AntiPeepProtectionPlugin 的日志:
// AntiPeepProtectionPlugin.ets
// 订阅状态
console.info(TAG, 'Anti-peep status subscription registered');
console.info(TAG, 'Anti-peep status subscription removed');
// 事件触发
console.info(TAG, 'Anti-peep status PASS');
console.warning(TAG, 'Anti-peep status HIDE');
// 蒙层设置
console.info(TAG, 'Anti-peep mask layer shown');
// 设置请求
console.info(TAG, 'Anti-peep option dialog already requested');
二、Flutter 侧负责记录"页面最终拿到了什么"
Flutter 侧更适合记录:
|
日志类型 |
说明 |
示例 |
|---|---|---|
|
通道调用是否成功 |
MethodChannel 结果 |
|
|
页面是否收到事件 |
事件处理 |
|
|
路由是否真正跳转 |
路由日志 |
|
|
UI 状态是否被更新 |
状态变化 |
|
|
业务逻辑结果 |
最终行为 |
|
intent_navigation_channel.dart 的日志:
// intent_navigation_channel.dart
// 路由跳转
AppLogger.info(
'Intent navigation: pageId="${payload.pageId}" -> route="$route"',
);
// pending 消费
AppLogger.warning('consumePendingNavigation failed: $e');
anti_peep_protection_channel.dart 的日志:
// anti_peep_protection_channel.dart
// 事件接收
if (event == 'HIDE') {
AppLogger.warning(
'$message - possible anti-peek trigger detected on collection screen',
);
} else {
AppLogger.info(message);
}
ai_explore_coordinator.dart 的日志:
// ai_explore_coordinator.dart
// 工具调用
AppLogger.info('[AI助手] 工具调用: ${toolCall.name}(${toolCall.arguments})');
// 错误
AppLogger.error('[AI助手] 对话出错: $e');
AppLogger.error('[AI助手] 语音识别出错: $e');
AppLogger.error('[AI助手] TTS 出错: $e');
三、两边日志不该重复写同一件事
如果 ArkTS 已经记录了"引擎启动成功",Flutter 侧就没必要再重复假装记录这件事。Flutter 应该记录的是"最终拿到了什么返回值"。
正确的日志分工:
|
事件 |
ArkTS 记录 |
Flutter 记录 |
|---|---|---|
|
TTS 引擎创建 |
|
不记录(没必要) |
|
TTS 播报完成 |
|
不记录(没必要) |
|
TTS 播报调用 |
|
不记录(没必要) |
|
Flutter 收到结果 |
不记录(ArkTS 不知道) |
不记录(正常流程) |
|
Flutter 路由跳转 |
不记录 |
|
错误场景的日志分工:
|
事件 |
ArkTS 记录 |
Flutter 记录 |
|---|---|---|
|
TTS 引擎创建失败 |
|
|
|
TTS 播报出错 |
|
|
|
语音识别失败 |
|
|
|
权限拒绝 |
|
|
四、调试时最好围绕链路打点
以语音识别为例,完整的链路日志:
用户点击语音按钮
│
├─ Flutter: [AI助手] 开始语音输入
│
├─ Flutter: SpeechRecognitionChannel.startListening()
│
├─ ArkTS: requestMicrophonePermission()
│ ├─ 成功: (无日志,正常流程)
│ └─ 失败: requestPermission failed: xxx
│
├─ ArkTS: createEngine()
│ ├─ 成功: Engine created successfully
│ └─ 失败: Failed to create engine: xxx
│
├─ ArkTS: startListening()
│ └─ console.info: startListening
│
├─ ArkTS: onResult()
│ └─ console.info: onResult: {result: "想吃鸡蛋", isLast: true}
│
├─ ArkTS: onComplete()
│ └─ console.info: onComplete sessionId: xxx
│
├─ ArkTS: shutdownEngine()
│ └─ console.info: Engine shutdown
│
├─ Flutter: 收到文本 "想吃鸡蛋"
│
├─ Flutter: [AI助手] 工具调用: search_dishes(...)
│
└─ Flutter: [AI助手] 对话完成
按这条链路逐层观察,就能快速定位问题出在哪一层。
五、日志级别建议
|
级别 |
ArkTS 使用场景 |
Flutter 使用场景 |
|---|---|---|
|
|
引擎创建成功、回调触发 |
路由跳转、事件接收 |
|
|
状态异常(如防窥 HIDE) |
通道调用失败(如 MissingPluginException) |
|
|
引擎创建失败、识别出错 |
对话出错、TTS 出错 |
不要把 info 级别的日志打太多——生产环境会很吵。warning 和 error 才是真正需要关注的。
关键代码位置
|
文件 |
日志内容 |
|---|---|
|
|
原生日志 |
|
|
原生日志 |
|
|
原生日志 |
|
|
Flutter 日志 |
|
|
Flutter 日志 |
|
|
Flutter 日志 |
日志分工总结
ArkTS 侧(原生事实)
├─ 权限申请结果
├─ 引擎创建成功/失败
├─ 系统状态变化
├─ 监听器回调触发
└─ 错误详情(错误码 + 信息)
Flutter 侧(页面结果)
├─ 通道调用是否成功
├─ 页面是否收到事件
├─ 路由是否真正跳转
├─ UI 状态是否被更新
└─ 业务逻辑结果(工具调用、对话完成)
常见坑
-
所有日志只打在一侧 — 看不到完整链路
-
ArkTS 和 Flutter 两边记录完全重复的信息 — 日志太多,噪音大
-
日志没有链路上下文 — 看不出是哪次调用
-
只看"成功"日志,不记录失败和空结果 — 问题出在失败路径
-
日志级别不分 — info 和 error 混在一起,生产环境很吵
-
没有 TAG 前缀 — ArkTS 侧多插件日志混在一起
可复用模板
ArkTS 侧日志模板
const TAG = 'YourPlugin';
// 成功
console.info(TAG, 'Engine created successfully');
console.info(TAG, `onComplete requestId: ${requestId}`);
// 失败
console.error(TAG, `Failed to create engine: ${err.message}`);
console.error(TAG, `onError code: ${errorCode}, msg: ${errorMessage}`);
// 状态变化
console.info(TAG, `onResult: ${JSON.stringify(result)}`);
Flutter 侧日志模板
// 成功
AppLogger.info('Intent navigation: pageId="$pageId" -> route="$route"');
// 失败
AppLogger.warning('consumePendingNavigation failed: $e');
AppLogger.error('[AI助手] 对话出错: $e');
// 事件
AppLogger.info('Anti-peep event: $event');
链路日志检查清单
语音识别链路:
□ ArkTS: 权限申请结果
□ ArkTS: 引擎创建结果
□ ArkTS: 识别回调
□ ArkTS: 引擎销毁
□ Flutter: 收到文本
□ Flutter: 提交 AI
TTS 链路:
□ ArkTS: 引擎创建结果
□ ArkTS: 播报回调
□ ArkTS: 播报调用
□ ArkTS: 引擎销毁
防窥链路:
□ ArkTS: 订阅状态
□ ArkTS: 状态变化
□ ArkTS: 蒙层设置
□ Flutter: 事件接收
□ Flutter: 状态更新
本篇总结
调试鸿蒙插件时,Flutter 和 ArkTS 两边都要打日志,但两边记录的内容应该不同:
-
ArkTS 侧 — 记录原生能力真实状态(权限、引擎、回调、错误)
-
Flutter 侧 — 记录页面最终拿到了什么(通道结果、路由跳转、状态更新)
只要按链路打点,定位问题会快很多。两边日志不该重复写同一件事,否则噪音太大。日志级别也要分清——info 记正常流程,warning/error 记异常。
更多推荐





所有评论(0)