第33次:性能优化实战

本文不做空泛理论,而是专门总结当前项目里已经出现的性能优化思路。虽然它不是一个超大应用,但在服务缓存、列表刷新、局部滚动、条件渲染等方面已经有不少值得学习的做法。


为什么这个项目也需要性能意识

很多人会觉得“数据不大、页面不多,就不用管性能”。其实并不是这样。移动端项目尤其是学习类应用,性能问题往往体现在这些细节里:

  • 页面切换时卡顿
  • 列表状态不同步
  • 长内容滚动不顺畅
  • 不必要的重复初始化

当前项目虽然规模不算大,但已经在多个位置体现了“先把基础性能做好”的意识。

数据层优化

ProgressService / BookmarkService 缓存

页面层优化

Builder 拆分 + 条件渲染

列表优化

ForEach key / 局部刷新

内容优化

局部 Scroll + 高度约束

初始化优化

统一在 Index 初始化服务


一、服务缓存是这个项目最明显的优化点

最典型的例子有两个:

  • ProgressService.cachedProgress
  • BookmarkService.cachedBookmarks

为什么这很重要?因为:

  • 首页、模块详情、课时详情、个人中心都会读进度
  • 模块详情、课时详情、收藏页都会读收藏状态

如果每次都异步去本地读取,不仅写法麻烦,还会增加刷新成本。服务层缓存的价值在于:

  • 把高频读取变成同步读取
  • 让页面判断状态更轻
  • 降低重复 IO

这就是学习型应用里非常实用的一类优化。


二、统一初始化本身也是一种性能优化

Index.ets 会在启动时统一初始化:

  • 存储
  • 教程数据
  • 搜索服务
  • 徽章服务
  • 收藏服务

这样做避免了多个子页面分别重复初始化同一套基础数据。对于当前项目来说,这种集中初始化比“每页自己准备数据”更稳,也更省资源。

这说明性能优化不一定都是低层技巧,很多时候它首先是一种结构设计能力。


三、列表刷新为什么要精准

项目中有一个很值得注意的小技巧,就是 ModuleDetail 里的 bookmarkVersion。它不是粗暴整页重载,而是通过更换 ForEach 的 key 来让列表项重新渲染。

这类“精准刷新”思路很重要,因为移动端最怕的就是:

  • 明明只改了一项状态
  • 却整页大面积重绘

当前项目虽然没有做很复杂的虚拟列表,但已经体现了“局部变化局部刷新”的意识,这一点非常值得保留。


四、长内容为什么都做了局部滚动

当前项目里很多容易变长的内容区都采用了局部滚动策略,例如:

  • CodeBlock 代码区
  • CodePlayground 代码预览区
  • 源码详情页源码区
  • 开源项目详情页完整示例区

并且常常会配合:

  • constraintSize({ maxHeight: ... })
  • scrollBar(BarState.Auto/Off)

这样做的好处非常直接:

  • 页面整体结构不会被一段长内容撑坏
  • 只有真正需要滚动的区域才滚
  • 用户阅读路径更稳定

这是一种既提升性能、也提升体验的写法。


五、Builder 拆分为什么也算性能优化

Index.ets 这种大页面,如果所有 UI 都堆在一个 build() 里,不仅难维护,状态变化时也更难判断哪些区域该更新。当前项目通过大量 @Builder 拆分:

  • HomeContent
  • QuickAccessSection
  • RecommendedModulesSection
  • ProfileContent
  • BadgesSection

这首先是可维护性优化,但也间接有利于性能,因为页面结构更清晰,状态依赖关系也更容易控制。

换句话说,代码结构好,本身就是性能优化的前提。


六、当前项目还能优化哪些地方

既然这一篇叫“实战”,就不能只夸优点。结合当前源码和实际构建结果,还能继续优化的地方也很明确:

  • Index.ets 体积较大,后续可以继续拆分子组件。
  • 一些页面仍然频繁使用过时 API,未来升级时要同步梳理导航性能和状态同步策略。
  • 下载页、详情页中的大段文本和代码,如果未来数据量继续增大,可以进一步考虑懒加载或分段渲染。
  • rawfile 中包含较多源码型资源,构建时已经出现 source code check 警告,后续发布前需要继续收敛打包体积和资源结构。

教程把这些讲出来,才是真正对项目负责。


七、自己检查性能时可以怎么做

  1. 打开首页,切换各个 Tab,观察是否有明显卡顿。
  2. 连续收藏和取消收藏,看列表刷新是否即时而且稳定。
  3. 打开长代码页面,测试局部滚动是否顺畅。
  4. 进入题库、错题本、源码详情页,观察页面初次加载节奏。
  5. 再结合构建输出,梳理哪些是“警告级风险”,哪些是“真实卡顿点”。

这样你会建立起更真实的性能判断能力,而不是只看空洞指标。


八、本篇常见坑

1. 把性能优化理解成最后再做

很多结构性优化,其实从一开始就应该考虑。

2. 只盯着算法,不看页面结构

页面拆分、服务缓存、滚动策略同样重要。

3. 发现刷新问题就整页重载

更好的办法往往是局部刷新和状态精确管理。

4. 忽略构建阶段的资源警告

这些警告常常和最终体积、加载效率有关。


本篇小结

当前项目里的性能优化不是靠某一个大招,而是靠一系列基础但有效的做法叠加出来的:

  • 服务缓存
  • 统一初始化
  • 精准列表刷新
  • 长内容局部滚动
  • Builder 拆分

这正是中小型鸿蒙项目最实用的优化路线。


跟着真实源码继续往下看

进度缓存的真实代码如下:

export class ProgressService {
  private static cachedProgress: UserProgress | null = null;

  static getCachedProgress(): UserProgress {
    return ProgressService.cachedProgress ?? DEFAULT_USER_PROGRESS;
  }
}

收藏列表局部刷新的真实代码如下:

ForEach(this.module.lessons, (lesson: Lesson) => {
  ListItem() {
    LessonItem({ lesson: lesson })
  }
}, (lesson: Lesson) => `${lesson.id}_${this.bookmarkVersion}`)

按这个顺序动手

  1. 打开 ProgressService.ets,确认缓存字段和同步读取方法。
  2. 再打开 ModuleDetail.ets,看 bookmarkVersion 怎么参与 ForEach key。
  3. 最后去几个长代码页确认局部滚动区域是否都做了高度限制。

课后练习

  1. 自己列出项目里三个“缓存换同步体验”的例子。
  2. 思考 Index.ets 如果继续拆分,最先该拆哪个内容区,为什么。
  3. 对照当前构建输出,写下你认为最值得优先处理的一条性能或工程警告。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐