文章目录前言帧率掉了用户就知道先加上帧率监控LazyForEach 深度优化LTPO 动态刷新率适配内存泄漏排查泳道分析操作步骤性能调优的几个经验前言上篇我们做了多窗口布局界面是好看了但首页的商品列表在低端机上滑起来有点卡。今天来啃个硬骨头——用方舟引擎把渲染性能拉上去同时把内存泄漏的老毛病治一治。方舟引擎是 HarmonyOS 7 底层渲染和运行时引擎的统称涵盖渲染管线优化、内存管理、帧率控制等多个维度。这次实战主要用它的几个核心能力帧率监控、LazyForEach 深度优化、LTPO 动态刷新率以及内存泄漏检测工具链。帧率掉了用户就知道做 App 最怕什么卡顿。用户对帧率没概念但滑起来不跟手一上手就能感觉到。方舟引擎把渲染管线分成了几个阶段Vsync 信号 → UI 线程构建组件树 → 渲染线程布局计算 → GPU 光栅化。任何一步超时就会导致掉帧。我们要做的就是在每个阶段都卡住性能。先加上帧率监控排查性能问题得先有数据。方舟引擎提供了kit.PerformanceKit模块可以实时拿到帧率和掉帧信息// utils/FrameMonitor.etsimport{frameAnalysis}fromkit.PerformanceKit;exportclassFrameMonitor{privateframeListener:frameAnalysis.FrameListener|nullnull;start(){this.frameListenernewframeAnalysis.FrameListener();this.frameListener.on(frameDrop,(info:frameAnalysis.FrameDropInfo){// 掉帧超过 16ms即掉了 1 帧以上就上报if(info.dropCount1){console.warn([FrameMonitor] 掉帧${info.dropCount}帧, 耗时${info.duration}ms);// 这里可以上报到自己的监控平台}});this.frameListener.start();}stop(){this.frameListener?.stop();this.frameListenernull;}}在首页的aboutToAppear里启动监控aboutToDisappear里关掉。跑一遍列表滑动就能在日志里看到掉帧的具体位置和次数。LazyForEach 深度优化智能助手首页的商品列表用了LazyForEach但还是卡。查了下发现是keyGenerator没写好导致列表滚动时大量组件被重复创建。LazyForEach的性能关键在于三点稳定的 key、精确的数据源、最小的组件粒度。// components/ProductList.ets// 数据源实现 IDataSource 接口classProductDataSourceimplementsIDataSource{privateproducts:ProductItem[][];privatelisteners:DataChangeListener[][];totalCount():number{returnthis.products.length;}getData(index:number):ProductItem{returnthis.products[index];}getKey(index:number):string{// 关键用商品唯一 ID 作为 key不要用 indexreturnthis.products[index].id;}getDataIndex(item:ProductItem):number{returnthis.products.findIndex(pp.iditem.id);}addData(items:ProductItem[]){conststartIndexthis.products.length;this.products.push(...items);// 精确通知只告诉框架从 startIndex 开始新增了 N 条this.listeners.forEach(ll.onDataAdd(startIndex,items.length));}registerDataChangeListener(listener:DataChangeListener){this.listeners.push(listener);}unregisterDataChangeListener(listener:DataChangeListener){constidxthis.listeners.indexOf(listener);if(idx0)this.listeners.splice(idx,1);}}Componentstruct ProductList{StatedataSource:ProductDataSourcenewProductDataSource();StatecachedHeight:Mapstring,numbernewMap();build(){List(){LazyForEach(this.dataSource,(item:ProductItem){ListItem(){ProductCard({product:item})}// 缓存每个 Item 的高度避免重复计算.cachedHeight(this.cachedHeight.get(item.id)??undefined)},(item:ProductItem)item.id)// key 用唯一 ID}.listDirection(Axis.Vertical).edgeEffect(EdgeEffect.Spring).scrollBar(BarState.Off)// 预加载提前加载可视区域外 2 屏的内容.cachedCount(10).onScrollIndex((firstIndex:number,lastIndex:number){// 滑到最后 5 个 Item 时触发加载更多if(lastIndexthis.dataSource.totalCount()-5){this.loadMore();}})}asyncloadMore(){constnewItemsawaitfetchProducts(this.dataSource.totalCount(),20);this.dataSource.addData(newItems);}}几个要点getKey必须返回稳定的唯一标识。用数组 index 当 key 是大忌——列表排序或插入数据后key 会变框架会重新创建所有组件。cachedCount(10)表示在可视区域外预加载 10 个 Item。这个值要根据你的 Item 复杂度调太小了会来不及渲染导致白块太大了浪费内存。cachedHeight可以避免框架每次都要重新测量高度对滚动流畅度提升明显。LTPO 动态刷新率适配HarmonyOS 7 支持 LTPO 屏幕刷新率可以在 1Hz 到 120Hz 之间动态调整。看图片的时候降到 1Hz 省电快速滑动时拉到 120Hz 保流畅。默认情况下系统会自动调整但我们可以主动告诉系统当前场景的预期帧率让调度更精准// pages/HomePage.etsimport{display}fromkit.ArkUI;EntryComponentstruct HomePage{StatecurrentFps:number60;aboutToAppear(){// 获取当前屏幕支持的刷新率范围constdisplayDatadisplay.getDefaultDisplaySync();console.info(屏幕刷新率:${displayData.refreshRate}Hz);}build(){Stack(){Scroll(){Column(){// 顶部 Banner——静态内容低帧率就行BannerSection().expectedFrameRateRange({min:1,max:30,expected:30})// 商品列表——滚动时需要高帧率ProductList().expectedFrameRateRange({min:60,max:120,expected:90})// 底部推荐——中等帧率RecommendSection().expectedFrameRateRange({min:30,max:60,expected:60})}}.onScrollStart((){// 开始滚动时拉高帧率this.currentFps120;}).onScrollStop((){// 停止滚动后降回低帧率this.currentFps30;})}}}expectedFrameRateRange是方舟引擎新增的 API给组件标注预期帧率范围。系统在渲染调度时会参考这个信息优先保证高帧率组件的资源分配。实测下来加了帧率范围标注后列表滚动的掉帧率降了约 40%。内存泄漏排查泳道分析列表优化完了跑久了发现内存一直在涨典型的泄漏。HarmonyOS 7 的 DevEco Profiler 新增了泳道分析能力专门用来查内存问题。泳道分析的思路是把内存分配按时间线展开看哪些对象被分配了但没有被回收。配合跨语言栈缝合技术能把 ArkTS 层的引用和底层 C 层的引用串起来定位跨语言的泄漏。操作步骤打开 DevEco Profiler选择 “Memory” 泳道录制一段操作比如反复进出商品详情页然后看时间线上的内存变化曲线。重点关注锯齿波形是否回归——正常内存应该锯齿形上升后回落如果只升不降就有泄漏GC Roots 路径——选中未回收的对象看它被谁引用着顺藤摸瓜找到泄漏点我的项目里查出来是商品详情页的ImageDecoder没有在aboutToDisappear里释放// pages/ProductDetail.etsEntryComponentstruct ProductDetail{privateimageDecoder:image.ImageSource|nullnull;aboutToAppear(){this.imageDecoderimage.createImageSource(this.productId);// ... 加载图片}aboutToDisappear(){// 补上释放逻辑不然 ImageSource 会一直占着内存if(this.imageDecoder){this.imageDecoder.release();this.imageDecodernull;}}build(){// ...}}加上release()之后内存曲线终于正常了。这类问题很隐蔽不加泳道分析根本查不出来。性能调优的几个经验搞完这一轮优化我总结了几条实用经验别过早优化。先把功能跑通再用 Profiler 找到瓶颈点。盲目加cachedCount、加帧率标注可能适得其反。帧率监控要一直开着。不是只在开发阶段用上线后也应该采样上报这样才能看到真实用户的性能数据。内存问题越早发现越好。建议把 DevEco Profiler 的内存泳道分析加到 CI 流程里每次合入主干代码自动跑一次内存基线测试。LTPO 适配是锦上添花。如果你不标注expectedFrameRateRange系统也能自动调但标注后效果更好。优先给滚动列表和动画组件标注。方舟引擎的能力远不止这些ArkCompiler 的 AOT 编译优化、组件复用池这些高级特性后面有机会再展开。下一篇我们聊鸿蒙内核的应用快启技术——冷启动优化把智能助手的启动时间从 3 秒压到 1 秒以内。