HarmonyOS LTPO 帧率实战:别把刷新率锁死 120Hz,expected 按内容填

本文聚焦 HarmonyOS 上基于 LTPO 屏幕的自适应刷新率与可变帧率能力。文中代码为便于说明自行编写,API 名称、枚举取值与版本号等事实性信息均标注官方出处;涉及真机功耗/帧率表现的部分已明确标注,未编造任何实测数据。

引子:一行设置,省的是电不是帧

先抛个场景。你做了个首页小转盘加载动画,丝般顺滑------但测试同事说"这页面手机发烫"。你查了一圈,最后发现罪魁是这么一行:

typescript 复制代码
sync.setExpectedFrameRateRange({ expected: 120, min: 120, max: 120 });

你以为"高帧率 = 体验好"。在 LTPO 屏上,这句等于告诉系统:"不管现在放什么,都给V哥 120 帧跑着。"系统是个老实孩子,你这么吩咐它就照办,于是手机一直在满帧空转,电哗哗掉,后盖温温热。改完这行之后,转盘照样转,功耗曲线却肉眼可见地往下走了。

动画又丝滑又省电,其实只差一行帧率设置------把帧率交给内容决定。

一、帧率不是越高越好:LTPO 把"刷新率"交还给了内容

LTPO(Low Temperature Polycrystalline Oxide,低温多晶氧化物)是 OLED 屏背板的一种驱动技术。官方一句话点明它的价值:

LTPO 屏支持 1~120Hz 的自适应刷新率,使应用在需要高刷新率的场景下提升流畅性,而在视频、静止等场景中使用低刷新率降低显示功耗。(基于 LTPO 的低功耗设计,更新于 2026-03-12)

翻成白话:看静止图文时,屏幕可以掉到 1Hz;快速滑动时拉到 120Hz。帧率跟着内容走,功耗和流畅就兼顾了。把 1Hz 和 120Hz 放在一起想就很直观------同一个屏幕,待机时几乎不刷,动起来才全速,这就是"自适应"的本意。

到了 HarmonyOS 7,系统在功耗层面进一步把 LTPO 可变帧率做成了一项系统级能力(虎嗅《HarmonyOS 7 正式发布》提到"在功耗上则引入 LTPO 可变帧率技术")。注意这里的重点:对开发者而言,接入这套能力用的还是既有的可变帧率接口,这些接口自 API 12(5.0) 就开放了。换句话说------本文讲的帧率控制能力不是 7.0 才新增的 API,而是 5.0 起就在的"可变帧率能力";7.0 把系统侧的 LTPO 适配做厚了,让"用对这套接口"更值钱。这也是为什么本文如实标注每个接口的起始版本,而不把它包装成 7.0 专属新特性。

二、三条入口:动画帧率 / UI 绘制帧率 / 自绘制帧率

官方把可变帧率能力归成三类适用场景(可变帧率简介):

  • 动画绘制帧率 :属性动画 / 显式动画,用 expectedFrameRateRange 参数;
  • UI 绘制帧率 :用 displaySync 申请一个独立绘制帧率;
  • 自绘制内容帧率:XComponent / NativeVsync 在 Native 侧申请独立帧率(游戏等)。

三条线对应三种内容形态,别混用:普通 ArkUI 组件上的动画走第一条;你想自己控制某块 UI 的刷新节奏走第二条;游戏或 Canvas 自绘制走第三条。V哥把四类内容映射到帧率档位,做成一张自用表:

内容类型 官方场景举例 推荐 expected/min/max V哥的建议
高动态 应用启停、窗口转场、拖拽窗口 90~120Hz expected 120、min 90 兜底,纯全屏非持续动效才用
稳定滚动 视频弹幕、小说翻页、消息滑动 60Hz expected 60、min 0,闲时让系统自由降频
微动效 转盘、加载转圈、滚动条消失 15~30Hz expected 30、max 60,小区域别浪费算力
跟随源 插画动效、表情包、导航主界面 跟随内容源 expected 0,交给系统/内容源决定

这张表的"V哥的建议"列是V哥踩坑后的取舍:很多团队一上来全用 120,结果微动效也满帧跑。分档的核心就一句------帧率该由内容决定,交给系统调度

三、手把手:displaySync.create() + on('frame') 自绘制帧率

displaySync 来自 @kit.ArkGraphics2D,是"自绘制内容"那条线的主力。流程就三步:建实例 → 设期望帧率范围 → 注册 frame 回调 → start() 启动。

V哥封装了一个按内容类型分档的调度器,避免到处散落魔法数字,也把"切档复用实例"和"销毁回收"这两件容易忘的事一起管起来:

typescript 复制代码
// LtpoFrameScheduler.ets ------ 按内容类型分档的帧率调度封装
import { displaySync } from '@kit.ArkGraphics2D';

// 内容类型:高动态 / 稳定滚动 / 微动效 / 跟随源
export enum ContentTier {
  HIGH = 'high',
  STEADY = 'steady',
  MICRO = 'micro',
  SOURCE = 'source'
}

// 每类内容的帧率三件套,语义来自 ExpectedFrameRateRange
const RANGE: Record<ContentTier, displaySync.ExpectedFrameRateRange> = {
  [ContentTier.HIGH]:   { expected: 120, min: 90,  max: 120 }, // 全屏非持续动效
  [ContentTier.STEADY]: { expected: 60,  min: 0,   max: 120 }, // 闲时自由降频
  [ContentTier.MICRO]:  { expected: 30,  min: 0,   max: 60  }, // 小区域微动效
  [ContentTier.SOURCE]: { expected: 0,   min: 0,   max: 30  }, // 0 = 跟随应用帧率
};

export class LtpoFrameScheduler {
  private sync: displaySync.DisplaySync | undefined = undefined;

  // 按内容类型开一档帧率,注册每帧回调
  start(tier: ContentTier, onFrame: (info: displaySync.IntervalInfo) => void): void {
    if (this.sync === undefined) {
      this.sync = displaySync.create();
    }
    // 把 expected/min/max 交出去,真实帧率由系统再决策
    this.sync.setExpectedFrameRateRange(RANGE[tier]);
    this.sync.on('frame', onFrame);
    this.sync.start();
  }

  // 切档:同一个实例改范围即可,不必重建
  switchTo(tier: ContentTier): void {
    this.sync?.setExpectedFrameRateRange(RANGE[tier]);
  }

  // 页面销毁时必须停掉,否则帧回调一直挂在 UI 主线程上
  stop(): void {
    if (this.sync) {
      this.sync.stop();
      this.sync = undefined;
    }
  }
}

displaySync.create()on('frame')setExpectedFrameRateRange 均自 API 12 起提供(DisplaySync 文档)。on('frame') 的回调跑在 UI 主线程,里面别塞耗时操作,否则会拖慢渲染。

用起来就是一行分档:

typescript 复制代码
// 一个加载转圈,用 MICRO 档,30Hz 足够顺眼又不烧电
const scheduler = new LtpoFrameScheduler();
scheduler.start(ContentTier.MICRO, (info: displaySync.IntervalInfo) => {
  this.angle = (this.angle + 1) % 360; // 每帧转一度
});
// 页面 aboutToDisappear 时:
// scheduler.stop();

四、ExpectedFrameRateRange 的 min / expected / max 怎么填

三个字段来自官方 ExpectedFrameRateRange 类型定义:

  • expected(最优期望帧率):系统优先按它跑。这是整个结构里最关键的一个字段,填错了最影响观感。
  • min(最小帧率):系统尽量不低于它;设 0 表示允许系统降到最低以省电。
  • max(最大帧率):系统不会超过它;上限受屏幕硬件限制(60Hz 屏 max 封顶 60)。

官方对取值给过一句话提醒:

开发者设置的期望帧率值可能无法完全实现,因为会受到系统能力和屏幕刷新率的限制。(可变帧率简介

所以填法的心法:expected 写"这个内容就该有的帧率",min/max 写"系统可以活动的余地"。具体落到四类内容:高动态 expected 120、min 90,留个下限防掉帧;稳定滚动 expected 60、min 0,停了就让系统闲时降频;微动效 expected 30、max 60,小区域没必要冲高;跟随源 expected 0,把决定权交还系统。

五、反模式:把 expected 锁死 120Hz

这是全文最核心的一句:别把 expected、min、max 全写成 120。

官方最佳实践用一整段"不建议锁定最高帧率运行"来警告这件事,V哥摘关键三点:

不建议将 ExpectedFrameRateRange 中的 expected、min、max 都设置为 120,这会干扰系统可变帧率机制,增加负载,影响整机性能和功耗。(基于 LTPO 的低功耗设计

为什么坏?三点串起来:① expected 是系统优先满足的值,锁 120 系统就真按 120 跑;② 持续 120 帧功耗显著上升,长时间会过热;③ 系统被你钉死在 120,它自己的可变帧率能力等于失效了。

V哥的口诀送给你们:"expected 锁满格,手机烫成铁;留个活动位,系统才体贴。" 锁死 120 不是"体验拉满",是把系统的聪明劲儿按住了。

六、用 Profiler 做功耗对比:只讲方法,不编数字

写到这,你肯定想问"到底能省多少电"。V哥必须说清楚:V哥没在真机跑过具体数值,本文不编掉帧率、不编功耗百分比。 V哥能给你的,是官方给出的对比方法,你照着在真机验。

官方给了两步(基于 LTPO 的低功耗设计 · 功耗测试工具):

  1. 看实时刷新率:设置里搜"开发者" → 打开"显示刷新频率"开关,肉眼看屏幕帧率有没有随内容降下来;
  2. 量整机功耗 :DevEco 的 Profiler → Realtime Monitor,选设备/app/进程,黄线斜率正表示耗电。官方建议让应用跑 30 秒、每 3 秒记一次,取第 6~21 秒的平稳段平均。

方法到位,结论留给你的真机。锁 120 和分档这两版,用上面两步一比,差异自己就出来了------这一步别省,用数据说话比拍脑袋强。

七、期望帧率 ≠ 实际帧率

最后泼盆冷水。你设了 expected 30,不代表屏幕一定 30。真实帧率受两件事卡着:

  • 系统功耗/性能约束:系统觉得现在该降频省电,就会在 min~max 里挑个更合适的值;
  • 屏幕硬件上限:60Hz 屏,你 max 写 120 也只到 60。

所以把帧率当"建议"而不是"命令"。设计交互时,别假设回调一定按你设的节奏来------动画的视觉连续性要靠插值保证,而不是靠死守某个帧率。比如转盘转一圈用角度插值,30Hz 和 60Hz 看着都顺,但功耗差一倍。

上线自检清单

  • LTPO 是什么、为什么 1-120Hz 自适应能省电,团队对齐了吗?
  • 有没有把 expected/min/max 全写成 120 的反模式?有就改掉。
  • 动画、UI 绘制、自绘制三条线,各自的帧率入口选对了吗(animateTo 参数 / displaySync / XComponent)?
  • 内容分档表做了吗?高动态/稳定滚动/微动效/跟随源各填了合适的 expected 吗?
  • min 有没有给系统留降频余地(闲时内容敢不敢设 0)?
  • displaySyncaboutToDisappearstop() 并置空了吗?避免回调泄漏。
  • 切档用 switchTo 复用同一实例,还是每次都 create 新的?
  • 真机上用"显示刷新频率"开关验证过帧率随内容变化了吗?
  • 用 Profiler Realtime Monitor 对比过锁 120 与分档的功耗差异吗?
  • 动画视觉连续性靠插值保证,没假设回调一定按期望帧率来吗?

参考与出处

本文涉及的事实性信息(API 名称、枚举取值、版本号、官方约束)来自以下官方文档;文中的结构、代码示例、决策流程与自检清单为本人整理编写:


最后一句 :LTPO 真正的难点不在 expectedFrameRateRange 那三个数------三个数填完不到十行。难的是承认"帧率该由内容决定":高动态给 120、稳定滚动 60、微动效 30,敢留 min=0 让系统自由降频,页面关掉前记得 stop()。这四件想清楚,丝滑和省电系统自己会平衡;想不清楚,锁死 120 换来的只有一块发烫的机身和一条更陡的功耗曲线。

相关推荐
威哥爱编程1 小时前
HarmonyOS 7 Agent A2A 实战:让日程智能体和打车智能体自己谈成一单
华为·harmonyos·arkts
威哥爱编程1 小时前
HarmonyOS 跨设备互通实战:平板点一下,手机镜头帮你拍照
华为·harmonyos·arkts
贾伟康1 小时前
【HarmonyOS 7新能力|036】分布式数字身份工程封装:把接入逻辑放进可维护的分层结构
harmonyos·arkts·软件架构·隐私保护·数字身份
HwJack201 小时前
【HarmonyOS开发小实践】ArkUI 交互事件与手势:从触摸到组合手势
ui·华为·性能优化·harmonyos
星栖与芯2 小时前
LiteOS-M 切换汇编逐行图解(5):HalPendSV 任务切换的现场搬运(五阶段逐行)
汇编·stm32·嵌入式硬件·harmonyos
马剑威(威哥爱编程)2 小时前
【共创稿事节】HarmonyOS 7 数字身份 DID 实战:TEE 颁发、本人同意、最小化出示
华为·harmonyos
贾伟康2 小时前
【HarmonyOS 7新能力|039】冷启网络预建链工程封装:把接入逻辑放进可维护的分层结构
性能优化·harmonyos·arkts·软件架构·网络优化
星栖与芯12 小时前
LiteOS-M 切换汇编逐行图解(4):中断三件套与 HalTaskSchedule 触发
汇编·stm32·单片机·嵌入式硬件·harmonyos
李游Leo14 小时前
定位功能“偶尔失效“怎么查:HarmonyOS Location Kit 权限、订阅与地理围栏实践
harmonyos