智能客服
你问我答,随时在线为你解决问题
在HarmonyOS中,Buffer是承载自绘制内容循环渲染的主要载体,有别于Vsync统一执行的思想,自绘制内容的生产不依赖于系统事件,而是由三方主导控制。Buffer在渲染过程中通过BufferQueue来进行轮转,采用了生产者与消费者的设计思想,应用将作为生产者将生产好的渲染内容准备好后,从BufferQueue中获取并Flush Buffer,而RS则作为消费者可以通过AcquireBuffer接口获取Buffer,让该Buffer作为自绘制Surface中的一帧在屏幕上显示,并将使用后的Buffer调用ReleaseBuffer释放进入轮转池。相关的流转逻辑可以参考下图:

开发者可以通过Profiler的Trace工具分析Buffer的流转过程,可按照下图所示的顺序将相关进程置顶排列。其中OS_IPC中包含了render_service的跨进程通信信息,开发者可参考下图虚线所示,根据binder找到对应的生产者进程,详细的Trace点含义如下:

在定位问题时,开发者可以按照以下步骤来确定Buffer使用是否无误:



开发者可使用HiSmartPerf工具,在“整机测试-RS树可视化展示”处展开RS树,通过id查找的方式找到此BufferQueue对应的rs节点位置,定位到导致空跑问题的自绘制内容。如下图,开发者在TabIndex为0的home页面抓取Trace发现了Buffer空跑问题,通过RsDebug找到问题node id后通过id搜索,在切换至yellow页面时,发现是此处的web buffer预渲染导致的空跑问题。
能够造成空跑问题的Buffer通常并不会在屏幕上显示,所以往往并不会在当前页面被发现,开发者在使用该工具时可以结合页面逻辑,上下滚动、左右切换,以及根据问题复现的步骤依次回溯路径页面来寻找有问题的RS node。

Web的预渲染是一种广泛使用的性能优化手段,让Web组件在挂载之前完成渲染可以有效减少Web初次加载时的时延表现,但处理不当也同样可能引起Buffer类空跑问题。在Web类应用中,对于预加载的离线Web节点,开发者需要确保这些后台的Web处于冻结状态,不会产生持续性的冗余Buffer影响正常场景的功能。下图展示了一个在非Web首页通过Tab预加载两个Web页面的Trace,分析Buffer的Release情况,发现Trace中RSUniRenderThre的帧率表现高于实际的显示帧率,表明空跑问题存在。进一步观察发现,RSUni中有两处ReleaseBuffer,来自两个不同的queueId,由此可以推断该页面可能存在两个离线的Web组件正在后台渲染,带来冗余负载。

开发者可以通过HiSmartPerf的RS树工具定位Buffer位置,除此之外,推荐开发者充分利用Web DevTools工具,参考使用DevTools工具调试前端页面,开发者可以通过端口投射的方式展示当前页面中所有的Web页面加载情况。如下图所示,端口投射成功后“1”处填写投射端口,“2”处将显示所有创建成功的web对象,点击其中的两个网页可查看其当前活跃状态,如“3”“4”则分别对应了处于冻结状态和活跃状态的页面,点击inspect观察表现情况。这种活跃/冻结状态可以调用WebController.onActive/onInactive来控制,原则上,并非在屏幕显示范围内的Web不应处于活跃状态,避免带来Buffer空跑问题。

当前系统对Web组件接入了可见性回调来方便开发者规避大部分Web空跑问题,该接口可以保证当Web挂载到ArkUI树上之后,跟随Web组件的可见性来控制Controller,当组件不可见时设置Web为冻结,可见时设置为活跃。但可见性接口对于离线组件不会返回结果,依赖三方开发者进行控制。
开发者可通过WebviewController的onActive()和onInactive()来控制Web的活跃与冻结,冻结态的Web处于后台不会生产Buffer,可保证功耗收益。当开发者使用离线Web组件或主动调用了preloaditems()等方法时,建议在Web容器中添加onFirstMeaningfulPaint()回调,确保Web离线加载时,在完成第一次页面渲染之后进入冻结状态,既能保证进入预渲染Web时能够快速打开第一帧内容,也能保证其后台不产生冗余Buffer。
- @Component
- struct MyWebComponent {
- private src: string = ""
- private controller1: webview.WebviewController = new webview.WebviewController();
- shouldDraw: boolean = true;
- build() {
- Web({ src: this.src, controller: this.controller1 })
- .onPageBegin(() => {
- console.log(this.src, "webpage is onActive")
- this.controller1.onActive();
- })
-
- .onFirstMeaningfulPaint(() => {
- if (!this.shouldDraw) {
- return;
- }
- console.log(this.src, "webpage is onInactive")
- this.controller1.onInactive()
- this.shouldDraw = false
- })
- }
- }
Tab是一种较为特殊的加载结构,Tab默认的切换动效会将路径中的TabContent进行构建,此时位于路径TabContent中的Web组件,会由于隐式动效的特性无法响应可见性回调。对于使用了Tab+Web的开发者,推荐在接入onFirstMeaningfulPaint()首帧冻结的基础上,主动调用preloadItems()方法对Tabs进行预加载。若不进行预加载,需确保onPageBegin()中,不将controller设置为活跃状态。

上图展示了一个位于可滑动列表中的Video组件从屏幕外滑动至屏幕内的应用场景。其中“1”处的信息代表页面滑动的时机,“2”处的信息表明,视频的硬解码进程一直持续着产生。观察“3”“4”两处的差异可以发现,当Video组件位于屏幕外时,由于Buffer的持续生产造成了RSUni持续ReleaseBuffer,引发空跑问题。原则上,开发者所使用的自绘制图层需要对其ArkUI载体(如Video、XComponent等)添加充分的事件监听,确保其不再需要显示时,从Buffer生产的源头上停止。
- @Component
- export struct MyVideoComponent {
- @State videoSrc: Resource | string = $r('app.media.test_video');
- private controller: VideoController = new VideoController();
-
-
- build() {
- Column() {
- Video({
- src: this.videoSrc,
- controller: this.controller,
-
- })
- .width(300)
- .height(200)
- .onPrepared(() => {
- this.controller.start();
- })
- // 确保视频组件完全可见时播放,完全不可见时停止
- .onVisibleAreaChange([0.0], (isExpanding: boolean, currentRatio: number) => {
- if (isExpanding && currentRatio >= 1.0) {
- console.info('Component is completely visiable.');
- if (this.controller) {
- this.controller.start();
- }
- }
- if (!isExpanding && currentRatio <= 0.0) {
- console.info('Component is completely invisible.');
- if (this.controller) {
- this.controller.pause();
- }
- }
- })
- }
- }
- }
如上代码所示,开发者可利用可见性监听,判断当前呈现自绘制内容的ArkUI组件是否位于显示范围,通过控制Video.controller,控制视频在可见时播放,不可见时停止。

对于使用了自绘制图层的应用页面而言,开发者还需要考虑该自绘制图层与当前页面中已有的图层如DisplayNode之间的交叠关系。理想情况下,自绘制图层完成生产后,如果无需与其他图层产生更多叠加计算时,无需交由RSUniRender线程重新处理,可直接交由RSHardware显示,这种显示方式可称之为“直通”。反之,如果开发者对承载自绘制组件的容器设定了一些图形绘制效果,例如模糊、透明、提亮等,那么RSUniRender需要将已经渲染好的Buffer,与作为背景的DisplayNode进行交叠计算。在这种情况下,自绘制图层不得不在RSUniRender中提前释放Buffer,将自绘制图层的内容由GPU进行重绘,并重新作为DisplayNode的一部分,再交由RSHardware进行显示,这种现象可称之为“GPU重绘”问题。
GPU重绘问题对功耗的影响巨大,且多数情况下,开发者所设置的背景色、透明度等属性实际带来的显示效果在自绘制图层的覆盖下,效果并不明显。由于自绘制图层的生产本身也会消耗较多CPU和GPU的计算资源,持续性的GPU重绘将会占据更高的计算资源,引起明显发热。特别地,对于Web、XComponent等容器,不建议开发者主动设置背景色与透明度。
以下是一些常见的“GPU重绘”问题的根因。

开发者可以搜索“DrawImage”判断有无GPU重绘问题产生,如上图,给一个纯净的适配播放图层设置了一个透明度,进而引发GPU重绘问题发生,开发者可根据以下几个Trace点定位问题:
在上图所示的案例中,观察发现render_service里既没有ProcessCommandUni、也没有Animate的有效信息,表明此时DisplayNode并无实际刷新任务执行。但由于GPU重绘问题的存在,DisplayNode也会全程参与Buffer轮转过程,并产生图层计算功耗。

如上图,开发者可以使用HiSmartPerf的RS树工具查看可疑节点,在OtherModifier位置可以查看到来自pid进程58867,为该节点添加了一个0.4的Alpha透明度,结合该信息可进一步定位到对应的代码片段,如下图代码,注释掉透明度效果后问题不复现。
- @Component
- export struct MyVideoComponent_opacity {
- @State videoSrc: Resource | string = $r('app.media.test_video');
- private controller: VideoController = new VideoController();
-
-
- build() {
- Column() {
- Video({
- src: this.videoSrc,
- controller: this.controller,
-
- })
- .width(300)
- .height(200)
- // .opacity(0.4) 注释掉此段代码,可规避GPU重绘
- .onPrepared(() => {
- this.controller.start();
- })
- }
- }
- }
修改好的Trace表现如下图所示,在直通情况下,自绘制图层在图中“1”处获取Buffer,“2”处释放Buffer,无需交由RSUniRender进行重绘计算,也没有DisplayNode的刷新显示,达到预期的低功耗效果。

以下效果会导致自绘制图层GPU重绘,非必要显示效果,不建议开发者接入: