调试与调优
本文介绍 HarmonyOS 应用开发中的调试方法、日志系统、性能分析工具以及常见问题的解决方案。
日志系统(hilog)
HarmonyOS 使用 hilog 作为统一的日志系统,支持分级日志、格式化输出和隐私参数控制。
基础用法
typescript
import { hilog } from '@kit.PerformanceAnalysisKit'
const DOMAIN = 0x0001 // 日志域,用于分类过滤
const TAG = 'MyApp' // 日志标签
// 不同级别的日志
hilog.debug(DOMAIN, TAG, '调试信息')
hilog.info(DOMAIN, TAG, '普通信息')
hilog.warn(DOMAIN, TAG, '警告信息')
hilog.error(DOMAIN, TAG, '错误信息')
hilog.fatal(DOMAIN, TAG, '致命错误')格式化输出
typescript
import { hilog } from '@kit.PerformanceAnalysisKit'
const DOMAIN = 0x0001
const TAG = 'UserService'
// 公开参数(明文显示)
hilog.info(DOMAIN, TAG, '用户登录成功: %{public}s', userId)
hilog.info(DOMAIN, TAG, '用户年龄: %{public}d', age)
hilog.info(DOMAIN, TAG, '账户余额: %{public}f', balance)
// 隐私参数(生产环境脱敏显示为 ***)
hilog.info(DOMAIN, TAG, '手机号: %{private}s', phoneNumber)
hilog.info(DOMAIN, TAG, '密码: %{private}s', password)
// 混合使用
hilog.info(
DOMAIN,
TAG,
'用户 %{public}s 的余额为 %{private}f',
userName,
balance
)重要
%{public}s表示公开参数,日志中明文显示%{private}s表示隐私参数,发布版本会自动脱敏为***- 开发调试时建议使用
public,生产环境敏感信息务必使用private
在 DevEco Studio 查看日志
- 连接设备(真机或模拟器)
- 打开底部工具栏的 HiLog 面板
- 设置过滤条件:
tag:MyApp— 按标签过滤level:I— 按级别过滤(D/I/W/E/F)domain:0x0001— 按域过滤
命令行查看日志
bash
# 查看所有日志
hdc hilog
# 按标签过滤
hdc hilog -T MyApp
# 按级别过滤(D=Debug, I=Info, W=Warn, E=Error, F=Fatal)
hdc hilog -L I
# 组合过滤
hdc hilog -T MyApp -L W
# 清除日志缓冲区
hdc hilog -c
# 持续监控日志(类似 tail -f)
hdc hilog -g
# 保存日志到文件
hdc hilog > app.log日志最佳实践
typescript
class Logger {
private domain: number
private tag: string
constructor(tag: string, domain: number = 0x0001) {
this.tag = tag
this.domain = domain
}
debug(message: string, ...args: unknown[]) {
hilog.debug(this.domain, this.tag, message, ...args)
}
info(message: string, ...args: unknown[]) {
hilog.info(this.domain, this.tag, message, ...args)
}
warn(message: string, ...args: unknown[]) {
hilog.warn(this.domain, this.tag, message, ...args)
}
error(message: string, ...args: unknown[]) {
hilog.error(this.domain, this.tag, message, ...args)
}
}
// 使用示例
const logger = new Logger('HomePage')
logger.info('页面加载完成')
logger.error('请求失败: %{public}s', JSON.stringify(error))断点调试
基础断点
- 在代码行号左侧单击,添加断点(红色圆点)
- 点击工具栏的 Debug 按钮(虫子图标)启动调试
- 应用运行到断点处会自动暂停
typescript
@Entry
@Component
struct Index {
@State message: string = 'Hello'
build() {
Column() {
Button('点击')
.onClick(() => {
// 在此处设置断点
this.message = 'Clicked'
console.info(this.message) // 断点会停在这里
})
}
}
}调试操作
| 操作 | 快捷键 | 说明 |
|---|---|---|
| Step Over | F8 | 执行当前行,不进入函数内部 |
| Step Into | F7 | 进入函数内部 |
| Step Out | Shift+F8 | 跳出当前函数 |
| Resume | F9 | 继续运行到下一个断点 |
| Stop | Ctrl+F2 | 停止调试 |
条件断点
右键点击断点,设置触发条件:
typescript
// 设置条件:当 count > 10 时才暂停
for (let i = 0; i < 100; i++) {
// 条件断点: i > 10
processItem(i)
}日志断点
右键点击断点,选择 Log message to console,可以在不暂停的情况下输出日志:
当前值为: {变量名}异常断点
- 打开 Run → View Breakpoints(Ctrl+Shift+F8)
- 点击 + 添加 JavaScript Exception Breakpoint
- 选择捕获所有异常或指定类型的异常
表达式求值
在调试暂停时:
- 选中变量或表达式
- 右键选择 Evaluate Expression(Alt+F8)
- 可以实时计算表达式值或修改变量
typescript
// 调试时可以在 Evaluate Expression 中执行:
// this.message = '新值'
// JSON.stringify(someObject)
// someArray.filter(item => item.id > 10)Profiler 性能分析
DevEco Studio 内置 Profiler 工具,提供 CPU、内存、网络、FPS 等多维度性能分析。
启动 Profiler
- 连接设备(真机性能数据更准确)
- 选择 View → Tool Windows → Profiler
- 或点击工具栏 Profiler 图标
- 选择要分析的应用进程
CPU 分析
用于分析函数执行耗时、找出性能瓶颈。
使用步骤:
- 在 Profiler 中选择 CPU 标签
- 点击 Record 开始录制
- 在设备上执行需要分析的操作
- 点击 Stop 停止录制
- 查看火焰图和函数耗时列表
火焰图解读:
┌─────────────────────────────────────┐
│ mainThread │
│ ├─ aboutToAppear (120ms) │
│ │ ├─ fetchData (100ms) │ ← 重点关注
│ │ │ ├─ parseJSON (30ms) │
│ │ │ └─ networkRequest (70ms) │
│ │ └─ initUI (20ms) │
│ └─ build (50ms) │
└─────────────────────────────────────┘优化建议:
typescript
// 优化前:在 aboutToAppear 中同步加载数据
aboutToAppear() {
// 阻塞主线程 100ms+
const data = this.loadLargeDataSync()
this.items = data
}
// 优化后:异步加载,避免阻塞 UI
async aboutToAppear() {
// 使用异步,主线程继续渲染
this.items = await this.loadLargeDataAsync()
}
// 进一步优化:使用 TaskPool 将耗时操作放到后台线程
import { taskpool } from '@kit.ArkTS'
@Concurrent
function processLargeData(rawData: object[]) {
// 在后台线程执行
return rawData.map(item => heavyComputation(item))
}
async aboutToAppear() {
const task = new taskpool.Task(processLargeData, rawData)
this.items = await taskpool.execute(task)
}内存分析(Memory)
用于检测内存泄漏、分析对象分配。
使用步骤:
- 在 Profiler 中选择 Memory 标签
- 查看实时内存使用曲线
- 点击 Dump Java Heap 获取内存快照
- 分析对象数量和占用
内存快照分析:
| 指标 | 说明 | 健康范围 |
|---|---|---|
| Java Heap | ArkTS 对象占用的堆内存 | < 256MB |
| Native Heap | C/C++ 层内存 | 根据应用类型 |
| Graphics | 图形纹理内存 | < 128MB |
常见内存问题:
typescript
// 问题 1:闭包导致内存泄漏
class DataManager {
private listeners: Array<() => void> = []
addListener(callback: () => void) {
this.listeners.push(callback)
}
// 忘记移除监听器,导致回调和引用的对象无法释放
}
// 修复:提供移除方法,在组件销毁时调用
removeListener(callback: () => void) {
const index = this.listeners.indexOf(callback)
if (index > -1) {
this.listeners.splice(index, 1)
}
}
// 问题 2:全局缓存无限增长
const cache = new Map<string, object>()
// 修复:使用 LRU 缓存,限制大小
class LRUCache<K, V> {
private cache = new Map<K, V>()
private maxSize: number
constructor(maxSize: number) {
this.maxSize = maxSize
}
set(key: K, value: V) {
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(key, value)
}
}内存快照对比(Snapshot)
用于精确定位内存泄漏:
- 在操作前点击 Capture 获取基准快照
- 执行可疑操作(如反复打开/关闭页面)
- 再次点击 Capture 获取对比快照
- 查看 Diff 标签,找出持续增长的对象类型
网络分析(Network)
用于分析 HTTP 请求的耗时和流量:
- 在 Profiler 中选择 Network 标签
- 执行网络请求
- 查看请求瀑布图:
- DNS 解析时间
- 连接建立时间
- 请求发送时间
- 响应等待时间(TTFB)
- 数据传输时间
优化建议:
typescript
// 优化前:串行请求
const user = await fetchUser()
const orders = await fetchOrders(user.id)
const coupons = await fetchCoupons(user.id)
// 总耗时 = 三个请求之和
// 优化后:并行请求
const [user, coupons] = await Promise.all([
fetchUser(),
fetchCoupons()
])
const orders = await fetchOrders(user.id)
// 总耗时 ≈ max(前两个) + 第三个FPS 分析
用于检测 UI 卡顿:
- 在 Profiler 中选择 FPS 标签
- 查看实时帧率曲线
- 绿色区域(> 55fps)表示流畅
- 红色区域(< 30fps)表示卡顿
常见卡顿原因:
typescript
// 原因 1:build() 中执行耗时操作
build() {
// 错误:在 build 中计算
const sortedItems = this.items.sort(complexCompare)
Column() {
ForEach(sortedItems, (item) => { ... })
}
}
// 修复:提前计算或使用 LazyForEach
@State items: string[] = []
private sortedItems: string[] = []
aboutToAppear() {
this.sortedItems = this.items.sort(complexCompare)
}
build() {
Column() {
List() {
LazyForEach(this.dataSource, (item) => { ... })
}
}
}
// 原因 2:频繁的状态更新
@State counter: number = 0
// 错误:每 16ms 更新一次
setInterval(() => {
this.counter++
}, 16)
// 修复:降低更新频率或使用动画
setInterval(() => {
this.counter++
}, 1000) // 改为 1 秒常见错误和解决方案
编译期错误
ArkTS 类型错误
typescript
// 错误:使用 any 类型
let data: any = fetchData()
// 修复:使用具体类型
interface UserData {
id: number
name: string
}
let data: UserData = fetchData() as UserData找不到模块
Error: Cannot find module '@kit.SomeKit'解决方案:
- 检查模块名称拼写(注意大小写)
- 确认 SDK 版本支持该模块
- 执行
ohpm install安装依赖 - 检查
oh-package.json5中的依赖声明
资源引用错误
typescript
// 错误:资源不存在
Image($r('app.media.non_existent_image'))
// 修复:确认资源文件存在
// resources/base/media/app_icon.png
Image($r('app.media.app_icon'))运行期错误
权限拒绝(Permission Denied)
Error: Permission denied解决方案:
- 在
module.json5中声明权限:
json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.CAMERA",
"reason": "$string:camera_reason"
}
]
}
}- 对于危险权限,需要动态申请:
typescript
import { abilityAccessCtrl, PermissionRequestResult } from '@kit.AbilityKit'
async function requestCameraPermission() {
const atManager = abilityAccessCtrl.createAtManager()
const result = await atManager.requestPermissionsFromUser(
getContext(),
['ohos.permission.CAMERA']
)
if (result.authResults[0] === 0) {
// 授权成功
return true
} else {
// 授权被拒绝
return false
}
}白屏/页面不显示
排查步骤:
- 查看 hilog 错误日志
- 检查
build()是否有多个根容器:
typescript
// 错误:多个根容器
build() {
Text('A')
Text('B')
}
// 修复:使用单一根容器
build() {
Column() {
Text('A')
Text('B')
}
}- 检查
aboutToAppear是否抛出异常:
typescript
aboutToAppear() {
try {
this.loadData()
} catch (error) {
hilog.error(0x0001, 'Index', '加载失败: %{public}s', JSON.stringify(error))
this.errorMessage = '加载失败,请重试'
}
}状态不刷新
typescript
// 问题:修改对象属性不触发刷新
@State user: User = { name: '张三', age: 20 }
changeName() {
this.user.name = '李四' // 不会触发 UI 刷新
}
// 修复 1:重新赋值整个对象
changeName() {
this.user = { ...this.user, name: '李四' }
}
// 修复 2:使用 @Observed 和 @ObjectLink(API 11+)
@Observed
class User {
name: string = '张三'
age: number = 20
}
@Entry
@Component
struct Index {
@State user: User = new User()
changeName() {
this.user.name = '李四' // 现在会触发刷新
}
}网络请求失败
可能原因及解决方案:
| 现象 | 原因 | 解决方案 |
|---|---|---|
ECONNREFUSED | 服务器未启动或地址错误 | 检查服务器状态和 URL |
ETIMEDOUT | 连接超时 | 检查网络连接,增加超时时间 |
Permission denied | 未声明 INTERNET 权限 | 在 module.json5 中声明 |
Cleartext traffic not permitted | 明文 HTTP 被禁止 | 使用 HTTPS 或配置 cleartext |
json5
// module.json5 中允许明文 HTTP(仅开发期)
{
"module": {
"cleartextTrafficAllowed": true
}
}注意
生产环境必须使用 HTTPS,cleartextTrafficAllowed 仅用于开发调试。
hdc 命令行工具
hdc(HarmonyOS Device Connector)是连接和管理设备的命令行工具。
设备管理
bash
# 列出已连接的设备
hdc list targets
# 指定设备执行命令(多设备时)
hdc -t <device_id> <command>
# 查看设备信息
hdc shell cat /etc/os-release
# 重启设备
hdc shell reboot文件传输
bash
# 发送文件到设备
hdc file send local.txt /data/app/el2/100/base/com.example.myapp/haps/entry/files/
# 从设备拉取文件
hdc file recv /data/app/el2/100/base/com.example.myapp/haps/entry/files/data.json ./
# 发送目录
hdc file send ./assets /data/local/tmp/assets应用管理
bash
# 安装 HAP
hdc install entry-default-signed.hap
# 卸载应用
hdc uninstall com.example.myapp
# 强制停止应用
hdc shell bm force-stop com.example.myapp
# 清除应用数据
hdc shell bm clean -n com.example.myapp -c日志查看
bash
# 实时查看日志
hdc hilog
# 按标签过滤
hdc hilog -T MyApp
# 按级别过滤(D/I/W/E/F)
hdc hilog -L E
# 清除日志
hdc hilog -c
# 保存日志
hdc hilog > app.log调试命令
bash
# 进入设备 shell
hdc shell
# 查看进程
hdc shell ps -ef | grep com.example.myapp
# 查看内存使用
hdc shell hidumper -s Memory -a com.example.myapp
# 查看 CPU 使用
hdc shell hidumper -s CpuUsage -a com.example.myapp
# 获取设备 UDID(用于调试签名)
hdc shell bm get --udid
# 截屏
hdc shell snapshot_display -f /data/screen.png
hdc file recv /data/screen.png ./
# 录制屏幕
hdc shell screenrecord /data/video.mp4
# 按 Ctrl+C 停止录制
hdc file recv /data/video.mp4 ./性能优化要点
渲染优化
typescript
// 1. 使用 LazyForEach 替代 ForEach(大数据列表)
List() {
LazyForEach(this.dataSource, (item: Item) => {
ListItem() {
ItemComponent({ item })
}
}, (item: Item) => item.id.toString())
}
// 2. 避免不必要的布局嵌套
// 优化前:多层嵌套
Column() {
Column() {
Column() {
Text('内容')
}
}
}
// 优化后:扁平化
Column() {
Text('内容')
}
// 3. 使用条件渲染替代隐藏
// 优化前:始终创建所有组件
if (this.showA) {
ComponentA()
}
if (this.showB) {
ComponentB()
}
// 4. 图片优化
Image('https://example.com/large.jpg')
.width(100)
.height(100)
.objectFit(ImageFit.Cover)
// 建议:使用合适尺寸的图片,或配置图片缓存策略状态管理优化
typescript
// 1. 避免频繁的状态更新
// 优化前:每次循环都更新
for (let i = 0; i < 100; i++) {
this.progress = i // 触发 100 次刷新
}
// 优化后:批量更新
let finalProgress = 0
for (let i = 0; i < 100; i++) {
finalProgress = i
}
this.progress = finalProgress // 只触发 1 次刷新
// 2. 使用 @Watch 监听特定变化
@State @Watch('onCountChange') count: number = 0
onCountChange() {
// 只在 count 变化时执行
this.updateDerivedData()
}
// 3. 合理拆分状态
// 避免一个 @State 对象包含过多字段
// 将不相关的状态拆分到不同组件启动优化
typescript
// 1. 延迟加载非首屏内容
@Entry
@Component
struct Index {
@State isReady: boolean = false
async aboutToAppear() {
// 先显示骨架屏
this.isReady = false
// 异步加载数据
const data = await this.loadData()
this.data = data
this.isReady = true
}
build() {
if (!this.isReady) {
SkeletonScreen() // 骨架屏
} else {
MainContent({ data: this.data })
}
}
}
// 2. 使用预加载
// 在 Ability 中预加载下一个页面
onWindowStageCreate(windowStage: window.WindowStage) {
// 预加载资源
this.preloadResources()
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0001, 'EntryAbility', '加载失败')
return
}
hilog.info(0x0001, 'EntryAbility', '加载成功')
})
}内存优化
typescript
// 1. 及时释放资源
class ResourceManager {
private imageCache: Map<string, image.PixelMap> = new Map()
clearCache() {
this.imageCache.forEach((pixelMap) => {
pixelMap.release() // 释放图片资源
})
this.imageCache.clear()
}
}
// 2. 使用弱引用(API 12+)
import { WeakRef } from '@kit.ArkTS'
const weakCache = new WeakMap<object, unknown>()
// 3. 避免内存泄漏
@Component
struct MyComponent {
private timerId: number = -1
aboutToAppear() {
this.timerId = setInterval(() => {
this.doSomething()
}, 1000)
}
aboutToDisappear() {
// 必须清理定时器
if (this.timerId !== -1) {
clearInterval(this.timerId)
this.timerId = -1
}
}
}网络优化
typescript
// 1. 请求合并
// 优化前:多个独立请求
const user = await fetchUser()
const profile = await fetchProfile()
const settings = await fetchSettings()
// 优化后:合并为一个请求
const { user, profile, settings } = await fetchUserData()
// 2. 缓存策略
class CacheManager {
private cache = new Map<string, { data: unknown; expireAt: number }>()
get<T>(key: string): T | null {
const item = this.cache.get(key)
if (item && item.expireAt > Date.now()) {
return item.data as T
}
return null
}
set(key: string, data: unknown, ttlMs: number) {
this.cache.set(key, {
data,
expireAt: Date.now() + ttlMs
})
}
}
// 3. 图片懒加载和缓存
Image(this.imageUrl)
.alt($r('app.media.placeholder'))
.objectFit(ImageFit.Cover)
// 配合 LazyForEach 实现列表图片懒加载DevEco 代码检查
Inspect Code
- 选择 Code → Inspect Code
- 选择检查范围(整个项目或指定文件)
- 查看检查结果:
- Error:必须修复的问题
- Warning:建议修复的问题
- Suggestion:优化建议
常见检查项
| 检查项 | 说明 | 严重程度 |
|---|---|---|
| Unused import | 未使用的导入 | Warning |
| Null pointer | 潜在的空指针 | Error |
| Type mismatch | 类型不匹配 | Error |
| Deprecated API | 使用已废弃的 API | Warning |
| Resource leak | 资源未释放 | Warning |