系列文章《鸿蒙开发从入门到专家》第 47 篇 · 中级
写代码一半时间在写,另一半时间在调。但很多人调试全靠 console.log,遇到偶发崩溃、分布式问题、多线程竞态就束手无策。
这篇把鸿蒙调试工具链过一遍。从最基本的断点调试,到 attach 到已运行进程,到用 HiLog 做链路追踪,覆盖实际开发中最需要的调试场景。
一、断点调试
DevEco Studio 基于 IntelliJ 平台,断点调试的操作和 Android Studio 几乎一样。
1.1 基本操作
在代码行号左侧点击设置断点(红点),然后点击 Debug 按钮(虫子图标或 Shift+F9)以调试模式运行。执行到断点时应用暂停,Debug 面板显示变量、调用栈、线程信息。
1.2 条件断点
在循环或高频回调中,每次都断下来不现实。右键断点,在 Condition 框中输入表达式,只有条件为 true 时才暂停:
index === 5
这样只在第 5 次循环时断下,其余循环直接跳过。
1.3 日志断点(Logpoint)
这个功能很多人不知道。有时候你想知道某行代码执行了没有,但又不想暂停程序。右键断点,取消勾选 “Suspend”,勾选 “Evaluate and log”,输入要打印的内容:
"Processing item " + item.id + ", value=" + item.value
程序运行到这行时会输出日志但不暂停。比改代码加 console.log 然后重新编译方便得多。
1.4 变量监视
在 Watches 面板中添加表达式,断点暂停时实时显示值。比如监视 this.list.length 或 response.data.items[0].name,方便观察复杂对象的状态变化。
二、Attach 到已运行进程
有时候 bug 只在特定操作序列下出现。用 Debug 模式从头启动应用,手动重现操作序列可能要花几分钟。更高效的方式是先正常启动应用,复现到出问题前一步,然后 attach 调试器。
2.1 操作步骤
先用 Run 模式正常启动应用。
执行操作序列到接近出现 bug 的位置。
在 DevEco Studio 中点击 “Attach to Process” 按钮(带虫子图标的下拉箭头)。
选择目标设备的进程。
断点生效,继续操作触发 bug。
2.2 限制
Attach 到进程后,之前已执行的代码不会回溯。但此时断点已生效,后续执行的代码会在断点处暂停。这个方式特别适合调试"用户操作了 N 步后才出现"的问题。
2.3 分布式调试
鸿蒙分布式场景涉及跨设备调用。调试时可以在多个设备的进程上同时 attach。比如手机发起跨端调用到平板,在平板的 Ability 上打断点,能看到跨端调用的参数和返回值。
三、HiLog:鸿蒙的日志系统
3.1 为什么不用 console.log
console.log 在鸿蒙中有几个问题:
输出到标准输出,容易被系统海量日志淹没。
无法按标签、级别过滤。
Release 模式下性能损耗大(不会自动移除)。
不能跨设备关联日志。
HiLog 是鸿蒙原生的日志系统,支持分级、标签化、隐私过滤。
3.2 HiLog 基本用法
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN = 0xFF00;
const TAG = 'MyApp_Main';
// 四个级别
hilog.debug(DOMAIN, TAG, '%{public}s: debug message', 'functionA');
hilog.info(DOMAIN, TAG, '%{public}s: user logged in', username);
hilog.warn(DOMAIN, TAG, '%{public}s: slow response %{public}dms', url, elapsed);
hilog.error(DOMAIN, TAG, '%{public}s: request failed, code=%{public}d', url, errorCode);
3.3 隐私标记
%{public}s 表示公开内容,会原样输出。%{private}s 表示隐私内容,在日志中会被替换为 ***:
hilog.info(DOMAIN, TAG, 'token=%{private}s', userToken);
// 输出: token=***
这个设计在 Release 包中很有用。敏感数据不会被日志泄露,但开发时可以通过命令动态打开隐私日志:
hdc shell hilog -p off # 关闭隐私过滤,显示完整内容
hdc shell hilog -p on # 开启隐私过滤
3.4 封装 Logger
直接写 HiLog 太啰嗦。建议封装一个 Logger 工具:
// utils/Logger.ts
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN = 0xFF00;
export class Logger {
private tag: string;
constructor(tag: string) {
this.tag = tag;
}
debug(msg: string, ...args: any[]): void {
hilog.debug(DOMAIN, this.tag, `%{public}s`, this.format(msg, args));
}
info(msg: string, ...args: any[]): void {
hilog.info(DOMAIN, this.tag, `%{public}s`, this.format(msg, args));
}
warn(msg: string, ...args: any[]): void {
hilog.warn(DOMAIN, this.tag, `%{public}s`, this.format(msg, args));
}
error(msg: string, ...args: any[]): void {
hilog.error(DOMAIN, this.tag, `%{public}s`, this.format(msg, args));
}
private format(msg: string, args: any[]): string {
let result = msg;
args.forEach((arg, i) => {
result = result.replace(`{${i}}`, String(arg));
});
return result;
}
}
// 使用
const logger = new Logger('UserService');
logger.info('User {0} logged in from {1}', userId, deviceName);
3.5 查看日志
在 DevEco Studio 底部 Log 面板查看,或用 hdc 命令行:
# 查看全部日志
hdc shell hilog
# 按 tag 过滤
hdc shell hilog | grep MyApp_Main
# 按级别过滤(D=Debug, I=Info, W=Warn, E=Error)
hdc shell hilog -L D
# 清空日志缓冲
hdc shell hilog -r
四、链路追踪
4.1 用 HiLog 做请求链路追踪
网络请求链路是调试高频场景。一个请求从前端发出到返回,经过网络层、业务逻辑、UI 更新。用统一的 traceId 串联整条链路:
export class NetworkTracer {
private logger: Logger;
private traceId: number = 0;
constructor() {
this.logger = new Logger('Network');
}
startTrace(url: string): string {
const id = `trace_${Date.now()}_${++this.traceId}`;
this.logger.info('[{0}] Start request: {1}', id, url);
return id;
}
logRequest(id: string, method: string, headers: Record<string, string>): void {
this.logger.info('[{0}] Sending {1} request', id, method);
}
logResponse(id: string, statusCode: number, duration: number): void {
this.logger.info('[{0}] Response {1} in {2}ms', id, statusCode, duration);
if (duration > 3000) {
this.logger.warn('[{0}] Slow request: {1}ms', id, duration);
}
}
logError(id: string, error: string): void {
this.logger.error('[{0}] Error: {1}', id, error);
}
}
使用:
const tracer = new NetworkTracer();
async function fetchData(url: string) {
const traceId = tracer.startTrace(url);
try {
const response = await fetch(url);
tracer.logResponse(traceId, response.status, 150);
return response;
} catch (e) {
tracer.logError(traceId, e.message);
throw e;
}
}
在日志中搜索 trace_1234 就能看到完整链路。
4.2 ArkUI Inspector
ArkUI Inspector 是 DevEco Studio 里的 UI 组件树查看工具。在底部工具栏打开 ArkUI Inspector(连接设备后可用)。
功能:
查看组件树层级结构。
实时修改组件属性(宽高、颜色),立即在设备上预览。
检查组件是否过度嵌套。
过度嵌套是性能问题的常见来源。如果一个简单列表项的组件树有 15 层深度,渲染开销就会偏高。Inspector 能直观看到层级深度。
4.3 Bytrace / HiTrace
系统级链路追踪,适合跨进程、跨线程的时序问题。通过 hdc 执行:
# 抓取一段 trace
hdc shell bytrace -t 5 -b 8192 --overwrite avs > trace.ftrace
# 或用 hitrace(新版)
hdc shell hitrace --time 5s -o /data/local/tmp/trace.ftrace
抓到的 trace 文件可以用 Chrome 的 chrome://tracing 打开,查看系统级的事件时序。这对分析"分布式调用延迟"“多线程时序问题”"渲染管线耗时分布"很有用。
五、调试常见问题速查
小结
调试工具链用好了,定位 bug 的效率能差出十倍。记住几个要点:
条件断点和日志断点是利器,能减少无效暂停。
Attach 适合调试"操作 N 步后才出现"的问题。
HiLog 比 console.log 强在分级、标签、隐私过滤。
链路追踪用统一 traceId 串联整条调用链。
ArkUI Inspector 看组件嵌套深度,Bytrace 看系统级时序。
下一篇讲 SDK 二次封装。当你有多个业务模块重复封装同一套系统能力时,该不该抽象一个内部 framework?