适合谁看

  • 正在做 Flutter 鸿蒙项目调试但不知道从何入手的开发者

  • 遇到"MethodChannel 调用失败但不知道哪层出错"问题的人

  • 想提高 Flutter 鸿蒙项目调试效率的开发者

问题背景

Flutter 鸿蒙项目的错误链路比纯 Flutter 项目长得多:

Flutter Widget → Flutter 业务逻辑 → MethodChannel → ArkTS Plugin → 鸿蒙系统 API

任何一个环节出错,都可能导致功能异常。但错误信息可能只在某一层可见:

  • Flutter 侧的 MissingPluginException 只说明"原生插件不可用"

  • ArkTS 侧的 console.error 只在 DevEco Studio 或 hdc 日志中可见

  • 系统 API 的错误可能被 ArkTS 侧吞掉

调试的关键在于:同时看多层日志,时间对齐,找到真正的错误源头

项目中的真实场景

食界探味的典型调试场景:

  1. 语音识别不工作:Flutter 调用了 startListening,但没有收到结果

  2. 卡片跳转失败:点击桌面卡片,Flutter 页面没有跳转

  3. 防窥保护异常:激活防窥后,Flutter 遮罩没有显示

每个场景都需要跨层排查。

核心实现

工具一:hdc 日志抓取

hdc(HarmonyOS Device Connector)是鸿蒙的设备调试工具,可以抓取系统日志:

# 查看所有日志
hdc hilog

# 过滤特定 TAG
hdc hilog | grep "SpeechRecognitionPlugin"

# 过滤错误日志
hdc hilog | grep -E "ERROR|error"

# 保存日志到文件
hdc hilog > debug.log

ArkTS 侧的日志输出:

// ArkTS 侧的日志
console.info(TAG, 'Engine created successfully');
console.error(TAG, `Failed to create engine: ${err.message}`);
console.warn(TAG, `channel not ready, storing pageId "${pageId}" as pending`);

hdc 日志的格式:

06-22 10:30:15.123  1234  5678 I C00000: SpeechRecognitionPlugin: Engine created successfully
06-22 10:30:15.456  1234  5678 E C00000: SpeechRecognitionPlugin: Failed to create engine: timeout

格式:日期 时间 PID TID 级别 TAG: 消息

工具二:DevEco Studio 断点调试

DevEco Studio 支持对 ArkTS 代码进行断点调试:

  1. 在 ArkTS 代码中设置断点

  2. 启动调试模式(Run → Debug)

  3. 触发断点后查看变量值

关键断点位置:

// SpeechRecognitionPlugin.ets - 建议设置断点的位置
private async handleStartListening(call: MethodCall, result: MethodResult): Promise<void> {
  // 断点 1:检查 Flutter 侧传来的参数
  this.pendingResult = result;

  const hasPermission = await this.requestMicrophonePermission();
  // 断点 2:检查权限申请结果
  if (!hasPermission) {
    // ...
  }

  try {
    await this.createEngine();
    // 断点 3:检查引擎是否创建成功
    this.setupListener();
    this.startListening();
  } catch (err) {
    // 断点 4:捕获异常
  }
}

工具三:Flutter DevTools

Flutter DevTools 是 Flutter 官方的调试工具,可以在鸿蒙设备上使用:

# 启动 Flutter DevTools
flutter run --observatory-port=8080

# 在浏览器中打开 DevTools
# http://127.0.0.1:8080

Flutter DevTools 的关键功能:

  • Widget Inspector:查看 Widget 树和布局

  • Network:查看网络请求

  • Logging:查看 Flutter 日志

  • Performance:查看帧率和性能

协同排查方法

场景一:语音识别不工作

排查步骤:

# 1. 查看 Flutter 侧日志
# 在 Flutter 代码中添加日志
AppLogger.info('Starting speech recognition...');

# 2. 查看 ArkTS 侧日志
hdc hilog | grep "SpeechRecognition"

# 3. 检查权限状态
hdc hilog | grep "permission"

# 4. 检查引擎状态
hdc hilog | grep "Engine"

时间对齐示例:

# Flutter 侧日志(10:30:15.100)
Starting speech recognition...

# ArkTS 侧日志(10:30:15.150)
SpeechRecognitionPlugin: attached to engine

# ArkTS 侧日志(10:30:15.200)
SpeechRecognitionPlugin: requestPermission failed: ...

# 结论:权限申请失败,导致引擎未创建

场景二:卡片跳转失败

排查步骤:

# 1. 查看卡片点击事件
hdc hilog | grep "DailyRecommendCard"

# 2. 查看 EntryAbility 接收的 Want 参数
hdc hilog | grep "EntryAbility"

# 3. 查看 IntentNavigationPlugin 的日志
hdc hilog | grep "IntentNavigation"

# 4. 查看 Flutter 侧路由日志
# 在 Flutter 代码中检查 IntentNavigationChannel 的日志

时间对齐示例:

# ArkTS 侧日志(10:30:20.100)
DailyRecommendCard: openDishDetail called

# ArkTS 侧日志(10:30:20.150)
EntryAbility: onCreate, pageId: dish_detail, dishId: beef-curry-mok1r6xe-6jhu

# ArkTS 侧日志(10:30:20.200)
IntentNavigationPlugin: channel not ready, storing as pending

# Flutter 侧日志(10:30:20.300)
IntentNavigationChannel: consumePendingNavigation received

# Flutter 侧日志(10:30:20.350)
IntentNavigationChannel: navigating to /dish/beef-curry-mok1r6xe-6jhu

# 结论:正常流程,如果页面没跳转,检查 GoRouter 配置

场景三:MethodChannel 调用失败

排查步骤:

# 1. 检查 Flutter 侧是否调用了 invokeMethod
# 在 Flutter 代码中添加日志
AppLogger.info('Calling method: $method');

# 2. 检查 ArkTS 侧是否收到了调用
hdc hilog | grep "onMethodCall"

# 3. 检查 ArkTS 侧是否返回了结果
hdc hilog | grep "result.success\|result.error"

# 4. 检查 Flutter 侧是否收到了结果

时间对齐示例:

# Flutter 侧日志(10:30:25.100)
Calling method: startListening

# ArkTS 侧日志(无)
# 结论:MethodChannel 未连接,插件未注册

# 或者:

# ArkTS 侧日志(10:30:25.150)
SpeechRecognitionPlugin: onMethodCall: startListening

# ArkTS 侧日志(10:30:25.200)
SpeechRecognitionPlugin: Engine created successfully

# Flutter 侧日志(10:30:25.300)
Speech recognition completed: 牛肉咖喱

# 结论:正常流程

日志规范

为了让多层日志更容易对齐,建议统一日志格式:

// Flutter 侧日志规范
class AppLogger {
  static void info(String message) {
    print('[Flutter][INFO] $message');
  }

  static void warning(String message) {
    print('[Flutter][WARN] $message');
  }

  static void error(String message) {
    print('[Flutter][ERROR] $message');
  }
}
// ArkTS 侧日志规范
const TAG = 'SpeechRecognitionPlugin';

console.info(TAG, `[ArkTS][INFO] Engine created`);
console.error(TAG, `[ArkTS][ERROR] Failed: ${err.message}`);
console.warn(TAG, `[ArkTS][WARN] Channel not ready`);

日志格式:[层级][级别] 消息

常见调试技巧

技巧一:MethodChannel 双向日志

在 MethodChannel 的两端都添加日志:

// Flutter 侧
static Future<String> startListening() async {
  AppLogger.info('[Channel] Calling startListening');
  final result = await _channel.invokeMethod<String>('startListening');
  AppLogger.info('[Channel] startListening result: $result');
  return result ?? '';
}
// ArkTS 侧
onMethodCall(call: MethodCall, result: MethodResult): void {
  console.info(TAG, `[Channel] Received: ${call.method}`);
  switch (call.method) {
    case 'startListening':
      this.handleStartListening(call, result);
      break;
  }
}

技巧二:时间戳对齐

在日志中添加时间戳:

// Flutter 侧
final timestamp = DateTime.now().millisecondsSinceEpoch;
AppLogger.info('[$timestamp] Calling startListening');
// ArkTS 侧
const timestamp = Date.now();
console.info(TAG, `[$timestamp] Received: ${call.method}`);

技巧三:错误码标准化

在 ArkTS 侧使用标准化的错误码:

// 错误码定义
enum ErrorCode {
  PERMISSION_DENIED = 'PERMISSION_DENIED',
  ENGINE_CREATE_FAILED = 'ENGINE_CREATE_FAILED',
  CHANNEL_NOT_READY = 'CHANNEL_NOT_READY',
}

// 使用
result.error(ErrorCode.PERMISSION_DENIED, '麦克风权限被拒绝', null);

Flutter 侧根据错误码决定处理策略:

try {
  await _channel.invokeMethod('startListening');
} on PlatformException catch (e) {
  switch (e.code) {
    case 'PERMISSION_DENIED':
      _showPermissionGuide();
      break;
    case 'ENGINE_CREATE_FAILED':
      _showRetryDialog();
      break;
    default:
      _showGenericError();
  }
}

关键代码位置

  • app/ohos/entry/src/main/ets/plugins/ — ArkTS 侧插件日志

  • app/lib/core/platform/ — Flutter 侧 Channel 日志

  • app/lib/core/utils/logger.dart — Flutter 日志工具

  • app/ohos/entry/src/main/ets/entryability/EntryAbility.ets — Ability 生命周期日志

鸿蒙侧实现

鸿蒙侧的调试工具:

  1. hdc hilog:抓取系统日志

  2. DevEco Studio:ArkTS 代码断点调试

  3. console.log/info/error:代码内日志输出

hdc 常用命令:

# 查看设备连接状态
hdc list targets

# 安装应用
hdc install app.hap

# 启动应用
hdc shell aa start -a EntryAbility -b com.foodvoyage

# 查看日志
hdc hilog | grep "TAG"

# 清除日志
hdc hilog -r

Flutter 侧实现

Flutter 侧的调试工具:

  1. Flutter DevTools:Widget Inspector、Network、Logging

  2. print/AppLogger:代码内日志输出

  3. flutter run --debug:调试模式运行

Flutter DevTools 的关键功能:

  • Widget Inspector:查看 Widget 树,检查布局问题

  • Performance:查看帧率,检测性能瓶颈

  • Network:查看网络请求,检查 API 调用

  • Logging:查看 Flutter 日志,时间线对齐

常见坑

  • 坑 1:hdc 日志太多,找不到关键信息。使用 grep 过滤特定 TAG,或使用 --pid 过滤特定进程。

  • 坑 2:ArkTS 断点不生效。检查 DevEco Studio 是否正确连接到设备,断点是否在可执行代码行上。

  • 坑 3:Flutter DevTools 连接失败。检查端口是否被占用,设备是否正确连接。

  • 坑 4:时间戳不一致。Flutter 和 ArkTS 的系统时间可能有微差,需要用相对时间对齐。

  • 坑 5:日志被系统吞掉。某些系统级别的错误不会输出到 hilog,需要查看系统 dump 信息。

可复用模板

// Flutter 侧 - 调试日志封装模板
class DebugLogger {
  static const bool _debugMode = kDebugMode;
  static final List<String> _logs = [];

  static void log(String tag, String message) {
    if (!_debugMode) return;

    final timestamp = DateTime.now().toIso8601String();
    final log = '[$timestamp][$tag] $message';
    _logs.add(log);
    print(log);
  }

  static void dump() {
    for (final log in _logs) {
      print(log);
    }
  }

  static List<String> getLogs() => List.unmodifiable(_logs);
}
// 鸿蒙侧 - 调试日志封装模板
const DEBUG_MODE = true;

function debugLog(tag: string, message: string): void {
  if (!DEBUG_MODE) return;

  const timestamp = Date.now();
  console.info(tag, `[$timestamp] $message`);
}

function debugError(tag: string, message: string): void {
  if (!DEBUG_MODE) return;

  const timestamp = Date.now();
  console.error(tag, `[$timestamp] $message`);
}

本篇总结

Flutter 鸿蒙项目的调试,核心是"三方协同":hdc 抓取 ArkTS 侧日志、DevEco Studio 断点调试 ArkTS 代码、Flutter DevTools 调试 Flutter 代码。排查问题的关键是"时间对齐"——在同一时间点查看三层日志,找到真正的错误源头。建立"先查 Flutter 日志 → 再查 ArkTS 日志 → 最后查系统日志"的排查顺序,可以大大提高调试效率。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐