你有没有想过手机屏幕的刷新率是 60Hz 甚至 120Hz但你的游戏或者动画可能不需要那么高的帧率比如一个贪吃蛇游戏10 帧就够了一个实时天气动画30 帧就很流畅了。如果一直以最高帧率运行那就是白白浪费电。HarmonyOS 提供了displaySync这个模块让你可以自己控制 UI 自绘制内容的帧率做到该快的时候快该慢的时候慢。导入模块import{displaySync}fromkit.ArkGraphics2D;第一步创建 DisplaySync 对象一切从创建开始letbackDisplaySync:displaySync.DisplaySyncdisplaySync.create();调用displaySync.create()会返回一个DisplaySync实例。这个实例就是你控制帧率的核心工具后面所有的操作都通过它来完成。第二步设置期望的帧率范围创建好对象后你需要告诉系统我想要什么帧率。这通过setExpectedFrameRateRange方法来设置letrange:ExpectedFrameRateRange{expected:10,min:0,max:120};// 设置DisplaySync期望的帧率backDisplaySync?.setExpectedFrameRateRange(range)这里传了一个ExpectedFrameRateRange对象包含三个字段expected期望帧率这里设为 10表示你希望每秒跑 10 帧。min最小帧率设为 0 表示没有下限实际上系统会有最低限制。max最大帧率设为 120 表示允许系统在需要时提高到 120 帧。为什么要设一个范围而不是一个固定值呢因为系统需要根据设备状态比如电量、温度来动态调整。你设的expected是目标但实际帧率会在min和max之间浮动。打个比方你在做一个游戏的主菜单界面。菜单本身不需要高帧率设expected: 10就够了。但菜单里可能有一个动画 logo你希望它播放时能流畅一点所以把max设高一点。当动画播放完系统会自动降回低帧率省电。帧率设置的一些经验值静态内容表格、列表10~15 fps普通动画过渡、切换30~60 fps游戏主循环60~120 fps第三步注册帧回调帧率设好了接下来你需要在每一帧做一些事情比如更新游戏状态、绘制画面。这通过on(frame)来注册回调letcallback(frameInfo:displaySync.IntervalInfo){console.info(DisplaySync,TimeStamp:frameInfo.timestamp TargetTimeStamp: frameInfo.targetTimestamp);}// 注册订阅函数backDisplaySync?.on(frame,callback)回调函数会收到一个IntervalInfo对象包含两个时间戳timestamp当前帧到达的时间单位是纳秒。targetTimestamp下一帧预期到达的时间单位也是纳秒。这两个时间戳非常重要。用targetTimestamp - timestamp你就能算出两帧之间的间隔然后用这个间隔来计算游戏对象的移动距离、动画的插值进度等。比如你的游戏角色每秒移动 300 像素那在回调里可以这样算letdeltaTime(frameInfo.targetTimestamp-frameInfo.timestamp)/1_000_000_000;// 转成秒letmoveDistance300*deltaTime;// 这一帧应该移动的距离第四步启动回调注册好回调后还需要调用start()来真正启动letrange:ExpectedFrameRateRange{expected:10,min:0,max:120};backDisplaySync?.setExpectedFrameRateRange(range)letcallback(frameInfo:displaySync.IntervalInfo){console.info(DisplaySync,TimeStamp:frameInfo.timestamp TargetTimeStamp: frameInfo.targetTimestamp);}backDisplaySync?.on(frame,callback)// 开始每帧回调backDisplaySync?.start()从调用start()开始系统就会按照你设定的帧率范围周期性地调用你的回调函数。注意一个坑start()会把DisplaySync关联到当前的 UI 实例和窗口。如果你在非 UI 页面中、或者在一些异步回调里调用start()可能无法正确关联到 UI 上下文导致回调不执行。如果你遇到这种情况可以用UIContext.runScopedTask来确保在正确的 UI 上下文中执行import{displaySync}fromkit.ArkGraphics2D;import{UIContext}fromkit.ArkUI;// xxx.etsEntryComponentstruct Index{// 创建DisplaySync实例backDisplaySync:displaySync.DisplaySyncdisplaySync.create();aboutToAppear(){// 获取UIContext实例letuiContext:UIContextthis.getUIContext();// 在当前UI上下文中执行DisplaySync的start接口uiContext?.runScopedTask((){this.backDisplaySync?.start();})}build(){// ...}}这样就能保证start()在正确的 UI 上下文中执行了。第五步停止回调当你不再需要帧回调时比如游戏暂停、页面退出记得调用stop()来停止letrange:ExpectedFrameRateRange{expected:10,min:0,max:120};backDisplaySync?.setExpectedFrameRateRange(range)letcallback(frameInfo:displaySync.IntervalInfo){console.info(DisplaySync,TimeStamp:frameInfo.timestamp TargetTimeStamp: frameInfo.targetTimestamp);}backDisplaySync?.on(frame,callback)backDisplaySync?.start()// ...// 停止每帧回调backDisplaySync?.stop()stop()之后回调函数就不会再被调用了。如果你之后需要重新启动再调用start()即可。取消订阅如果你不想用stop()暂停而是想彻底移除某个回调函数可以用off(frame)letcallback(frameInfo:displaySync.IntervalInfo){console.info(DisplaySync,TimeStamp:frameInfo.timestamp TargetTimeStamp: frameInfo.targetTimestamp);}backDisplaySync?.on(frame,callback)// 取消订阅函数backDisplaySync?.off(frame,callback)如果你调用off时不传 callback 参数会取消所有已注册的回调函数。实际场景做一个简单的游戏循环下面的流程图展示了 DisplaySync 的完整使用流程否是是否创建 DisplaySync 对象设置期望帧率范围注册帧回调函数调用 start 启动回调系统按帧率调用回调在回调中更新游戏状态是否需要停止?调用 stop 停止回调是否需要彻底移除?调用 off 取消订阅可随时重新 start下面的流程图展示了帧率范围的动态调整逻辑静态内容普通动画高速游戏设置expected/min/max帧率系统根据设备状态调整当前场景类型帧率趋向min值省电帧率保持expected值帧率提升到max值回调间隔较长回调间隔适中回调间隔较短用时间戳计算deltaTime根据deltaTime更新游戏逻辑把上面的 API 串起来一个典型的游戏循环是这样的import{displaySync}fromkit.ArkGraphics2D;EntryComponentstruct GamePage{backDisplaySync:displaySync.DisplaySyncdisplaySync.create();playerX:number100;aboutToAppear(){// 设置游戏帧率为 60fpsletrange:displaySync.ExpectedFrameRateRange{expected:60,min:30,max:120};this.backDisplaySync?.setExpectedFrameRateRange(range);// 注册游戏循环this.backDisplaySync?.on(frame,(frameInfo:displaySync.IntervalInfo){// 计算时间差letdeltaTime(frameInfo.targetTimestamp-frameInfo.timestamp)/1_000_000_000;// 更新玩家位置this.playerX200*deltaTime;});// 启动游戏循环this.backDisplaySync?.start();}aboutToDisappear(){// 页面退出时停止this.backDisplaySync?.stop();}build(){// 渲染界面...}}小结displaySync模块的核心就是三个动作设帧率setExpectedFrameRateRange、注册回调on(frame)、启动/停止start/stop。它的价值在于让你能精细控制 UI 自绘制内容的帧率在流畅度和功耗之间找到最佳平衡点。做游戏、做动画、做实时图表只要是自绘制的场景都值得用它来优化性能。
鸿蒙开发-想控制游戏帧率?DisplaySync可变帧率详解
发布时间:2026/5/31 7:13:26
你有没有想过手机屏幕的刷新率是 60Hz 甚至 120Hz但你的游戏或者动画可能不需要那么高的帧率比如一个贪吃蛇游戏10 帧就够了一个实时天气动画30 帧就很流畅了。如果一直以最高帧率运行那就是白白浪费电。HarmonyOS 提供了displaySync这个模块让你可以自己控制 UI 自绘制内容的帧率做到该快的时候快该慢的时候慢。导入模块import{displaySync}fromkit.ArkGraphics2D;第一步创建 DisplaySync 对象一切从创建开始letbackDisplaySync:displaySync.DisplaySyncdisplaySync.create();调用displaySync.create()会返回一个DisplaySync实例。这个实例就是你控制帧率的核心工具后面所有的操作都通过它来完成。第二步设置期望的帧率范围创建好对象后你需要告诉系统我想要什么帧率。这通过setExpectedFrameRateRange方法来设置letrange:ExpectedFrameRateRange{expected:10,min:0,max:120};// 设置DisplaySync期望的帧率backDisplaySync?.setExpectedFrameRateRange(range)这里传了一个ExpectedFrameRateRange对象包含三个字段expected期望帧率这里设为 10表示你希望每秒跑 10 帧。min最小帧率设为 0 表示没有下限实际上系统会有最低限制。max最大帧率设为 120 表示允许系统在需要时提高到 120 帧。为什么要设一个范围而不是一个固定值呢因为系统需要根据设备状态比如电量、温度来动态调整。你设的expected是目标但实际帧率会在min和max之间浮动。打个比方你在做一个游戏的主菜单界面。菜单本身不需要高帧率设expected: 10就够了。但菜单里可能有一个动画 logo你希望它播放时能流畅一点所以把max设高一点。当动画播放完系统会自动降回低帧率省电。帧率设置的一些经验值静态内容表格、列表10~15 fps普通动画过渡、切换30~60 fps游戏主循环60~120 fps第三步注册帧回调帧率设好了接下来你需要在每一帧做一些事情比如更新游戏状态、绘制画面。这通过on(frame)来注册回调letcallback(frameInfo:displaySync.IntervalInfo){console.info(DisplaySync,TimeStamp:frameInfo.timestamp TargetTimeStamp: frameInfo.targetTimestamp);}// 注册订阅函数backDisplaySync?.on(frame,callback)回调函数会收到一个IntervalInfo对象包含两个时间戳timestamp当前帧到达的时间单位是纳秒。targetTimestamp下一帧预期到达的时间单位也是纳秒。这两个时间戳非常重要。用targetTimestamp - timestamp你就能算出两帧之间的间隔然后用这个间隔来计算游戏对象的移动距离、动画的插值进度等。比如你的游戏角色每秒移动 300 像素那在回调里可以这样算letdeltaTime(frameInfo.targetTimestamp-frameInfo.timestamp)/1_000_000_000;// 转成秒letmoveDistance300*deltaTime;// 这一帧应该移动的距离第四步启动回调注册好回调后还需要调用start()来真正启动letrange:ExpectedFrameRateRange{expected:10,min:0,max:120};backDisplaySync?.setExpectedFrameRateRange(range)letcallback(frameInfo:displaySync.IntervalInfo){console.info(DisplaySync,TimeStamp:frameInfo.timestamp TargetTimeStamp: frameInfo.targetTimestamp);}backDisplaySync?.on(frame,callback)// 开始每帧回调backDisplaySync?.start()从调用start()开始系统就会按照你设定的帧率范围周期性地调用你的回调函数。注意一个坑start()会把DisplaySync关联到当前的 UI 实例和窗口。如果你在非 UI 页面中、或者在一些异步回调里调用start()可能无法正确关联到 UI 上下文导致回调不执行。如果你遇到这种情况可以用UIContext.runScopedTask来确保在正确的 UI 上下文中执行import{displaySync}fromkit.ArkGraphics2D;import{UIContext}fromkit.ArkUI;// xxx.etsEntryComponentstruct Index{// 创建DisplaySync实例backDisplaySync:displaySync.DisplaySyncdisplaySync.create();aboutToAppear(){// 获取UIContext实例letuiContext:UIContextthis.getUIContext();// 在当前UI上下文中执行DisplaySync的start接口uiContext?.runScopedTask((){this.backDisplaySync?.start();})}build(){// ...}}这样就能保证start()在正确的 UI 上下文中执行了。第五步停止回调当你不再需要帧回调时比如游戏暂停、页面退出记得调用stop()来停止letrange:ExpectedFrameRateRange{expected:10,min:0,max:120};backDisplaySync?.setExpectedFrameRateRange(range)letcallback(frameInfo:displaySync.IntervalInfo){console.info(DisplaySync,TimeStamp:frameInfo.timestamp TargetTimeStamp: frameInfo.targetTimestamp);}backDisplaySync?.on(frame,callback)backDisplaySync?.start()// ...// 停止每帧回调backDisplaySync?.stop()stop()之后回调函数就不会再被调用了。如果你之后需要重新启动再调用start()即可。取消订阅如果你不想用stop()暂停而是想彻底移除某个回调函数可以用off(frame)letcallback(frameInfo:displaySync.IntervalInfo){console.info(DisplaySync,TimeStamp:frameInfo.timestamp TargetTimeStamp: frameInfo.targetTimestamp);}backDisplaySync?.on(frame,callback)// 取消订阅函数backDisplaySync?.off(frame,callback)如果你调用off时不传 callback 参数会取消所有已注册的回调函数。实际场景做一个简单的游戏循环下面的流程图展示了 DisplaySync 的完整使用流程否是是否创建 DisplaySync 对象设置期望帧率范围注册帧回调函数调用 start 启动回调系统按帧率调用回调在回调中更新游戏状态是否需要停止?调用 stop 停止回调是否需要彻底移除?调用 off 取消订阅可随时重新 start下面的流程图展示了帧率范围的动态调整逻辑静态内容普通动画高速游戏设置expected/min/max帧率系统根据设备状态调整当前场景类型帧率趋向min值省电帧率保持expected值帧率提升到max值回调间隔较长回调间隔适中回调间隔较短用时间戳计算deltaTime根据deltaTime更新游戏逻辑把上面的 API 串起来一个典型的游戏循环是这样的import{displaySync}fromkit.ArkGraphics2D;EntryComponentstruct GamePage{backDisplaySync:displaySync.DisplaySyncdisplaySync.create();playerX:number100;aboutToAppear(){// 设置游戏帧率为 60fpsletrange:displaySync.ExpectedFrameRateRange{expected:60,min:30,max:120};this.backDisplaySync?.setExpectedFrameRateRange(range);// 注册游戏循环this.backDisplaySync?.on(frame,(frameInfo:displaySync.IntervalInfo){// 计算时间差letdeltaTime(frameInfo.targetTimestamp-frameInfo.timestamp)/1_000_000_000;// 更新玩家位置this.playerX200*deltaTime;});// 启动游戏循环this.backDisplaySync?.start();}aboutToDisappear(){// 页面退出时停止this.backDisplaySync?.stop();}build(){// 渲染界面...}}小结displaySync模块的核心就是三个动作设帧率setExpectedFrameRateRange、注册回调on(frame)、启动/停止start/stop。它的价值在于让你能精细控制 UI 自绘制内容的帧率在流畅度和功耗之间找到最佳平衡点。做游戏、做动画、做实时图表只要是自绘制的场景都值得用它来优化性能。