欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

在这里插入图片描述

前言

在鸿蒙(HarmonyOS)应用开发中,网络请求的调试一直是一个痛点。传统的 print 语句虽然简单,但在复杂的异步环境中极易被淹没,且无法直观展示请求/响应的完整结构。

调试网络请求时,我们通常使用 dio_log 或简单的 print。但当项目变大,日志满天飞,很难分清哪个是网络请求,哪个是业务日志。

talker 是 Flutter 界的新秀日志系统,而 talker_dio_logger 则是其专门为 Dio 设计的插件。它不仅能以整洁的格式打印请求/响应/错误,还能自动收集日志供 UI 展示,甚至支持 Shake-to-Show(摇一摇看日志)。

一、核心架构与原理解析

1.1 Talker 分层架构

Talker 的设计非常精妙,分为三层:

  • Core Layer (talker): 纯 Dart 实现。基于 StreamController 的事件总线。它定义了 TalkerLog(普通日志)、TalkerError(错误)、TalkerException(异常)等基类。
  • Middleware Layer: 中间件层。
    • TalkerDioLogger: 实现了 Dio 的 Interceptor 接口,在 onRequest, onResponse, onError 时向 Core 发送事件。
    • TalkerBlocObserver: 监听 Bloc/Cubit 状态变化。
    • TalkerRouteObserver: 监听 Navigator 路由跳转。
  • Presentation Layer (talker_flutter): UI 展示层。
    • TalkerScreen: 一个内置的 Material 风格页面,用于渲染日志列表、详情页、设置页。
    • TalkerMonitor: 实时监控页,展示 HTTP 成功率、错误率图表。

拦截器

记录 HTTP

观察者

onError

Stream 流

写入

摇一摇

Dio 客户端

TalkerDioLogger

Talker 核心层

Bloc/Cubit

Flutter 错误

TalkerScreen 界面

本地日志文件

鸿蒙设备

二、核心 API 详解与进阶配置

2.1 依赖安装

pubspec.yaml 中引入:

dependencies:
  dio: ^5.0.0
  talker_flutter: ^3.0.0
  talker_dio_logger: ^3.0.0

2.2 全局初始化 (main.dart)

为了捕获所有异常,我们需要在 runApp 之前初始化。

import 'package:flutter/material.dart';
import 'package:talker_flutter/talker_flutter.dart';
import 'package:talker_dio_logger/talker_dio_logger.dart';
import 'package:dio/dio.dart';

// 全局单例
late final Talker talker;

void main() {
  // 1. 初始化
  talker = TalkerFlutter.init(
    settings: TalkerSettings(
      maxHistoryItems: 1000, // 最多保留1000条
      useConsoleLogs: true,  // 控制台也打印
    ),
    // 适配鸿蒙:自定义日志颜色,避免在 DevEco Studio 控制台看不清
    // logger: TalkerLogger(
    //   formatter: ColoredLoggerFormatter(), 
    // ),
  );

  // 2. 捕获 Flutter 框架错误
  FlutterError.onError = (details) {
    talker.handle(details.exception, details.stack, 'Uncaught Flutter Error');
  };

  // 3. 捕获 Zone 错误 (异步异常)
  runApp(const MyApp());
}

在这里插入图片描述

2.3 Dio 拦截器配置 (重点)

TalkerDioLogger 提供了极其丰富的配置项,包括请求头过滤、响应体最大长度等。

final dio = Dio();

dio.interceptors.add(
  TalkerDioLogger(
    talker: talker, // 传入全局 talker 实例
    settings: const TalkerDioLoggerSettings(
      // 打印请求头/体
      printRequestHeaders: true,
      printRequestData: true,
      
      // 打印响应头/体
      printResponseHeaders: false, // 响应头通常不看,关掉省空间
      printResponseData: true,
      
      // 打印错误详情
      printResponseMessage: true,
      
      // 颜色配置 (建议使用默认,这也是它的亮点)
      requestPen: AnsiPen()..blue(), 
      responsePen: AnsiPen()..green(),
      errorPen: AnsiPen()..red(),
    ),
    
    // **高级:敏感信息脱敏**
    // 很多 App 登录接口会返回 Token,不想打印在日志里
    requestFilter: (RequestOptions options) {
      // 如果 URL 包含 '/login',则不打印 Body
      return !options.path.contains('/login'); 
    },
    // 或者只隐藏特定 Header
    responseFilter: (Response response) {
      return response.statusCode != 401; // 只记录非 401 的响应
    }
  ),
);

在这里插入图片描述

三、生产环境实战:打造 QA 验收利器

在 QA 测试阶段,我们可以在 Profile 模式下开启 TalkerScreen

3.1 入口集成:摇一摇与悬浮球

talker_flutter 默认集成了摇一摇(Shake)检测,但在某些定制 ROM 或平板上可能不灵敏。我们可以加一个 “调试入口”

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Talker Demo',
      navigatorObservers: [
        // 监听路由跳转日志
        TalkerRouteObserver(talker),
      ],
      home: Scaffold(
        appBar: AppBar(title: const Text('Home')),
        body: Center(
          child: Column(
            children: [
              Text('摇一摇手机,或者点击下面按钮'),
              ElevatedButton(
                onPressed: () {
                  // 手动打开调试页
                  Navigator.of(context).push(
                    MaterialPageRoute(
                      builder: (context) => TalkerScreen(talker: talker),
                    ),
                  );
                },
                child: const Text('打开网络监控台'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

3.2 自定义 Log 类型

除了网络日志,我们还可以记录业务日志。

// 记录一条普通日志
talker.info('User clicked login button');

// 记录一条警告
talker.warning('Battery level low: 15%');

// 记录一条严重错误
try {
  throw Exception('Database corrupt');
} catch (e, st) {
  talker.handle(e, st, 'Database Error');
}

这些日志在 TalkerScreen 中会有不同颜色的图标(蓝色 i,黄色 !,红色 x),且支持按类型筛选。

在这里插入图片描述

四、OpenHarmony 平台适配指南

4.1 日志持久化与分享

Talker 并不直接将日志写入文件系统(默认在内存 List<TalkerData> _history 中)。如果应用 Crash,日志就没了。

在鸿蒙上,我们可以结合 path_provider 将日志写入沙箱文件。

// 这是一个自定义的 TalkerObserver
class FileLoggerObserver extends TalkerObserver {
  final File _logFile;

  FileLoggerObserver(this._logFile);

  
  void onLog(TalkerData log) {
    _logFile.writeAsStringSync(
      '${log.generateTextMessage()}\n', 
      mode: FileMode.append,
    );
  }
}

// 初始化时添加
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/app_logs.txt');
talker = TalkerFlutter.init(
  observer: FileLoggerObserver(file),
);

当 QA 遇到 Bug 时,他可以在 TalkerScreen 右上角点击 “Share” 按钮。talker_flutter 会调用系统的 Share Sheet。在鸿蒙上,这会弹出分享面板,可以选择通过“华为分享”或“QQ/微信”发送 log.txt 给开发。

在这里插入图片描述

4.2 性能考量

虽然 Talker 性能很好,但频繁的字符串拼接和 JSON 格式化依然有开销。
强烈建议:仅在 debugModeprofileMode 下开启详细日志。在 releaseMode 下:

  1. 禁用 TalkerDioLogger(或者只记录 Error)。
  2. 设置 useConsoleLogs: false,避免拖慢 Logcat/DevEco Console。
final isRelease = const bool.fromEnvironment('dart.vm.product');

if (!isRelease) {
  dio.interceptors.add(TalkerDioLogger(...));
}

五、完整演示代码:GitHub API 浏览器

本示例展示一个使用 Dio 请求 GitHub API,并将所有交互记录在 Talker 中的完整 App。

import 'package:flutter/material.dart';
import 'package:talker_flutter/talker_flutter.dart';
import 'package:talker_dio_logger/talker_dio_logger.dart';
import 'package:dio/dio.dart';

void main() {
  final talker = TalkerFlutter.init();
  runApp(MaterialApp(home: GitBrowser(talker: talker)));
}

class GitBrowser extends StatefulWidget {
  final Talker talker;
  const GitBrowser({super.key, required this.talker});

  
  State<GitBrowser> createState() => _GitBrowserState();
}

class _GitBrowserState extends State<GitBrowser> {
  late Dio _dio;
  String _content = 'Ready';

  
  void initState() {
    super.initState();
    _dio = Dio();
    _dio.interceptors.add(
      TalkerDioLogger(
        talker: widget.talker,
        settings: const TalkerDioLoggerSettings(
          printRequestData: true,
          printResponseData: true,
          printResponseMessage: true,
        ),
      ),
    );
  }

  Future<void> _fetchUser() async {
    setState(() => _content = 'Loading...');
    try {
      widget.talker.info('Starting fetch user...');
      final response = await _dio.get('https://api.github.com/users/flutter');
      widget.talker.info('Fetch success: ${response.statusCode}');
      
      setState(() {
        _content = 'User: ${response.data['name']}\n'
                   'Bio: ${response.data['bio']}\n'
                   'Repos: ${response.data['public_repos']}';
      });
    } catch (e, st) {
      widget.talker.handle(e, st, 'Fetch Failed');
      setState(() => _content = 'Error: $e');
    }
  }

  Future<void> _causeError() async {
    try {
      await _dio.get('https://api.github.com/users/non_existent_user_123456');
    } catch (e) {
      // 这里的 404 会被 Talker 自动标红记录
      setState(() => _content = '404 Error Triggered');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Talker Dio Demo'),
        actions: [
          IconButton(
            icon: const Icon(Icons.monitor_heart),
            onPressed: () => Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => TalkerScreen(talker: widget.talker),
              ),
            ),
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              padding: const EdgeInsets.all(16),
              color: Colors.grey[200],
              child: Text(_content, textAlign: TextAlign.center),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: _fetchUser,
                  child: const Text('Get Flutter User'),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _causeError,
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.red[100]),
                  child: const Text('Trigger 404'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

在这里插入图片描述

六、总结

talker_dio_logger 是提升 Flutter 开发幸福感的神器。它将以往散落在控制台、Crashlytics、Server Logs 里的信息,聚合到了你的指尖。

对于 OpenHarmony 开发者,它填补了移动端抓包工具的空白。你不再需要费尽周折去配置 PC 端代理,只需摇一摇手机,所有网络细节尽收眼底。

最佳实践

  1. 全局单例:保持 Talker 实例全局唯一,以便在任何地方记录日志。
  2. 分类管理:善用 talker.log('msg', logLevel: LogLevel.debug) 来区分调试信息和关键业务信息。
  3. 安全第一:切记在发布版中通过 Filter 屏蔽用户隐私数据(Password/Token)。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐