Skip to content

调试与调优

本文介绍 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 查看日志

  1. 连接设备(真机或模拟器)
  2. 打开底部工具栏的 HiLog 面板
  3. 设置过滤条件:
    • 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))

断点调试

基础断点

  1. 在代码行号左侧单击,添加断点(红色圆点)
  2. 点击工具栏的 Debug 按钮(虫子图标)启动调试
  3. 应用运行到断点处会自动暂停
typescript
@Entry
@Component
struct Index {
  @State message: string = 'Hello'

  build() {
    Column() {
      Button('点击')
        .onClick(() => {
          // 在此处设置断点
          this.message = 'Clicked'
          console.info(this.message)  // 断点会停在这里
        })
    }
  }
}

调试操作

操作快捷键说明
Step OverF8执行当前行,不进入函数内部
Step IntoF7进入函数内部
Step OutShift+F8跳出当前函数
ResumeF9继续运行到下一个断点
StopCtrl+F2停止调试

条件断点

右键点击断点,设置触发条件:

typescript
// 设置条件:当 count > 10 时才暂停
for (let i = 0; i < 100; i++) {
  // 条件断点: i > 10
  processItem(i)
}

日志断点

右键点击断点,选择 Log message to console,可以在不暂停的情况下输出日志:

当前值为: {变量名}

异常断点

  1. 打开 Run → View Breakpoints(Ctrl+Shift+F8)
  2. 点击 + 添加 JavaScript Exception Breakpoint
  3. 选择捕获所有异常或指定类型的异常

表达式求值

在调试暂停时:

  1. 选中变量或表达式
  2. 右键选择 Evaluate Expression(Alt+F8)
  3. 可以实时计算表达式值或修改变量
typescript
// 调试时可以在 Evaluate Expression 中执行:
// this.message = '新值'
// JSON.stringify(someObject)
// someArray.filter(item => item.id > 10)

Profiler 性能分析

DevEco Studio 内置 Profiler 工具,提供 CPU、内存、网络、FPS 等多维度性能分析。

启动 Profiler

  1. 连接设备(真机性能数据更准确)
  2. 选择 View → Tool Windows → Profiler
  3. 或点击工具栏 Profiler 图标
  4. 选择要分析的应用进程

CPU 分析

用于分析函数执行耗时、找出性能瓶颈。

使用步骤:

  1. 在 Profiler 中选择 CPU 标签
  2. 点击 Record 开始录制
  3. 在设备上执行需要分析的操作
  4. 点击 Stop 停止录制
  5. 查看火焰图和函数耗时列表

火焰图解读:

┌─────────────────────────────────────┐
│  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)

用于检测内存泄漏、分析对象分配。

使用步骤:

  1. 在 Profiler 中选择 Memory 标签
  2. 查看实时内存使用曲线
  3. 点击 Dump Java Heap 获取内存快照
  4. 分析对象数量和占用

内存快照分析:

指标说明健康范围
Java HeapArkTS 对象占用的堆内存< 256MB
Native HeapC/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)

用于精确定位内存泄漏:

  1. 在操作前点击 Capture 获取基准快照
  2. 执行可疑操作(如反复打开/关闭页面)
  3. 再次点击 Capture 获取对比快照
  4. 查看 Diff 标签,找出持续增长的对象类型

网络分析(Network)

用于分析 HTTP 请求的耗时和流量:

  1. 在 Profiler 中选择 Network 标签
  2. 执行网络请求
  3. 查看请求瀑布图:
    • 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 卡顿:

  1. 在 Profiler 中选择 FPS 标签
  2. 查看实时帧率曲线
  3. 绿色区域(> 55fps)表示流畅
  4. 红色区域(< 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'

解决方案:

  1. 检查模块名称拼写(注意大小写)
  2. 确认 SDK 版本支持该模块
  3. 执行 ohpm install 安装依赖
  4. 检查 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

解决方案:

  1. module.json5 中声明权限:
json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:camera_reason"
      }
    ]
  }
}
  1. 对于危险权限,需要动态申请:
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
  }
}

白屏/页面不显示

排查步骤:

  1. 查看 hilog 错误日志
  2. 检查 build() 是否有多个根容器:
typescript
// 错误:多个根容器
build() {
  Text('A')
  Text('B')
}

// 修复:使用单一根容器
build() {
  Column() {
    Text('A')
    Text('B')
  }
}
  1. 检查 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

  1. 选择 Code → Inspect Code
  2. 选择检查范围(整个项目或指定文件)
  3. 查看检查结果:
    • Error:必须修复的问题
    • Warning:建议修复的问题
    • Suggestion:优化建议

常见检查项

检查项说明严重程度
Unused import未使用的导入Warning
Null pointer潜在的空指针Error
Type mismatch类型不匹配Error
Deprecated API使用已废弃的 APIWarning
Resource leak资源未释放Warning

下一步