鸿蒙原生 + Flutter 混合开发:Flutter 的日志为什么打不出来
鸿蒙原生 + Flutter 混合开发:Flutter 的日志为什么打不出来
这篇解决一个很坑人的问题:你在 Flutter 里写了
不夸张地说,这是混合开发初期最浪费时间的一个坑——代码明明跑通了,就是不知道执行到哪一步。
一、现象:日志"打了,但没打"
| 你做的事 | 你以为的结果 | 实际结果 |
|---|---|---|
Flutter 里 print('进来了') |
DevEco 的 Log 窗口能看到 | ❌ 一条都没有 |
Flutter 里 debugPrint('进来了') |
同上 | ❌ 一条都没有 |
原生里 hilog.info(...) |
能看到 | ✅ 正常输出 |
结论:在鸿蒙的嵌入式场景下,Dart 侧的 print / debugPrint 不会进入系统日志(hilog)。
而 DevEco Studio 的 Log 窗口显示的就是 hilog,所以你在里面永远搜不到 Flutter 的日志。
注意:这不代表 Flutter 的日志"没产生",而是它没有走到系统日志这条路上。
想看,就必须自己把它送出去。
二、解决思路:借通道把日志送出去
既然原生能写日志,那就让 Flutter 把日志通过通道丢给原生,由原生负责输出。
Flutter: HybridTrace.log('...')
│ 复用已有的 MethodChannel,方法名固定叫 'trace'
▼
原生: case 'trace' → 用统一前缀写 hilog
▼
DevEco Log 窗口 / hdc shell hilog:按前缀一搜就能看到
三个设计要求(都是踩过坑总结的):
- 不新开通道——复用项目里已有的 MethodChannel,加一个
trace方法就行。 - 失败必须静默——日志本身绝对不能影响业务,发送失败就吞掉。
- 统一前缀——所有日志都带同一个前缀,排查时一个词筛出全链路。
三、Flutter 侧实现
新建一个 lib/hybrid_trace.dart,全局只有这一个入口:
import 'dart:async';
import 'package:flutter/services.dart';
/// 统一诊断日志。
///
/// 为什么不用 print / debugPrint:在鸿蒙嵌入式场景下它们不会进入 hilog,
/// DevEco 的 Log 窗口里一条都看不到。这里统一转发到原生输出。
///
/// 用法:HybridTrace.log('xxx'),失败静默(诊断日志不能影响业务)。
class HybridTrace {
const HybridTrace._();
/// 复用业务通道;方法名固定 'trace'
static const MethodChannel _channel =
MethodChannel('com.example.hybrid/native_calculator');
/// 统一前缀:原生侧按这个词过滤
static const String tag = '[HybridTrace]';
static void log(String message) {
unawaited(_send(message)); // 不 await:日志不能拖慢业务
}
static Future<void> _send(String message) async {
try {
await _channel.invokeMethod<void>('trace', <String, Object?>{
'message': message,
});
} catch (_) {
// 发送失败直接吞掉:诊断日志绝不影响业务
}
}
}
调用处就和写 print 一样简单:
HybridTrace.log('subtract first=$first second=$second');
await _nativeChannel.invokeMethod<dynamic>('subtract', {'first': 12, 'second': 5});
HybridTrace.log('subtract 返回 $result');
四、原生侧实现
在已有的 setMethodCallHandler 里加一个分支就行(关键是那个统一前缀):
// 统一前缀,和 Dart 侧对齐
const TAG_TRACE = '[HybridTrace]';
channel.setMethodCallHandler({
onMethodCall: (call, result) => {
// ① 诊断日志入口
if (call.method === 'trace') {
hilog.info(0x0000, 'HybridDemo', '%{public}s, %{public}s',
TAG_TRACE, call.argument('message') as string);
result.success(null); // ⚠️ 必须回一次,否则 Flutter 侧一直等
return;
}
if (call.method === 'subtract') {
// ② 顺手把每次调用也记一条,排查时能看出"调到了没"
hilog.info(0x0000, 'HybridDemo', '%{public}s, ←Dart %{public}s',
TAG_TRACE, call.method);
// ... 业务逻辑
result.success(argInt(call, 'first') - argInt(call, 'second'));
return;
}
result.notImplemented();
}
});
五、iOS / Android 也一样
这套做法是平台无关的:只要原生侧把 trace 方法实现出来,Flutter 代码一行都不用改。
| 平台 | 输出方式 | 代码 |
|---|---|---|
| HarmonyOS | hilog | hilog.info(domain, tag, '%{public}s, %{public}s', PREFIX, msg) |
| iOS | os_log | os_log("%{public}@", log: .default, type: .info, "\(PREFIX) \(msg)") |
| Android | Logcat | Log.i("HybridTrace", msg) |
记住一句话:同一套 Dart 代码,靠原生各自的实现落到各自的日志系统里。
六、日志格式:四段式,能拼出全链路
光有日志还不够,能看出"卡在哪一段"才是关键。约定一个四段式格式:
| 环节 | 谁打 | 格式 | 示例 |
|---|---|---|---|
| Flutter 发起调用 | Flutter(在通道封装里统一打) | →native <方法名> {参数} |
→native subtract {first: 12, second: 5} |
| 原生收到调用 | 原生(方法分发入口) | ←Dart <方法名> |
←Dart subtract |
| 原生推事件 | 原生(事件发送处) | →Dart emitEvent(<类型>) |
→Dart emitEvent(deviceInfo) |
| Flutter 收到事件 | Flutter(事件监听里统一打) | ←native event <类型> {载荷} |
←native event deviceInfo {...} |
真机日志长这样(按前缀一筛就出来):
[HybridTrace], →native subtract {first: 12, second: 5}
[HybridTrace], ←Dart subtract
[HybridTrace], →Dart emitEvent(deviceInfo)
[HybridTrace], ←native event deviceInfo {deviceId: 3AQ0...}
一眼就能判断:"原生日志有、Dart 的 →native 没有" → 是 Flutter 没调到;"←Dart 有、事件没有" → 是原生处理中出了问题。
最省事的做法:在通道封装的最底层(所有调用都经过的那个方法)统一打第一条和第四条,业务代码里就不用重复写了。
七、怎么抓日志
| 平台 | 命令 / 位置 |
|---|---|
| HarmonyOS | hdc shell hilog | grep HybridTrace |
| HarmonyOS(IDE) | DevEco Studio 的 Log 窗口,过滤框输入 HybridTrace |
| Android | adb logcat | grep HybridTrace |
| iOS | Console.app 或 Xcode 控制台,过滤 HybridTrace |
八、几个注意事项
| 注意点 | 说明 |
|---|---|
| 别打隐私 | hilog 的格式串区分 %{public}s 和 %{private}s。设备号、文件路径、用户标识这类内容,正式包建议用 %{private}s |
| 别打超大对象 | 单个日志行有长度上限,会被截断。要打列表就只打数量、标识和状态,不要 JSON.stringify(整个列表) |
| 失败要静默 | 日志发送是 try/catch 吞异常的,绝不能因为"日志失败"让业务挂掉 |
| 不要每次调用都 await | 用 unawaited 发出去就行,日志不该拖慢业务 |
| 前缀要唯一 | 别用 log、debug 这种通用词,否则过滤时满屏噪音 |
| 原生必须回结果 | trace 分支也要 result.success(null),否则 Flutter 侧那个 await 会一直挂着 |
浙公网安备 33010602011771号