文档管理中心
我的
您当前正在浏览新版开发者文档中心,目录分类和层级有所调整。点击左侧当前文档分类名称前的“☰”图标,可切换文档分类。 了解新版目录
问题处理与运维开发与测试问题应用质量技术质量性能ArkUI页面滑动卡顿丢帧问题分析

ArkUI页面滑动卡顿丢帧问题分析

问题现象

页面滑动过程卡顿,不流畅。

背景知识

  • 渲染流程:在HarmonyOS中,图形系统采用统一渲染模式,遵循典型流水线模式。下图为90Hz刷新率的渲染流程,Vsync信号周期为90Hz为11.1ms,每个Vsync信号到来时,应用侧会处理消费者的屏幕点击等输入事件,生成界面描述数据结构,提交给Render Service,Render Service协调GPU等资源处理,最终将图像送到屏幕上显示。

    在上述渲染流程中,如果本次Vsync信号到来时,应用侧或者Render Service侧相关流程的执行时间超过11.1ms,未在下一次Vsync信号到来时执行完本次的渲染流程,会造成两次Vsync信号到来时仅执行一次渲染流程,出现丢帧。如果存在多次丢帧的情况,用户会感知到卡顿的现象。

  • Frame分析:DevEco Profiler提供的Frame场景分析能力,可以录制卡顿过程中的关键数据进行分析,从而识别出导致卡顿丢帧的原因。

    常见Trace关键字如下:

    展开

    关键字

    说明

    H:FlushDirtyNodeUpdate

    刷新标脏的组件,当状态变量变化时,比如宽度和高度,组件需要重新布局刷新

    H:CustomNodeUpdate 组件名

    组件刷新,当状态变量变化时触发

    H:CreateTaskMeasure[组件名][self:组件ID][parent:父组件ID]

    创建组件的测量任务,确定组件的宽、高

    H:CreateTaskLayout[组件名][self:组件ID][parent:父组件ID]

    创建组件的布局任务,确定组件的位置

    H:Create[组件名][self:组件id]

    组件创建

    H:SendCommands

    发送指令,通知图形侧进行渲染。下方H:MarshRSTransactionData表示提交渲染数据给渲染服务

    H:HandleOnAreaChangeEvent

    处理组件区域变化事件,组件的大小、位置发生时触发。

    H:HandleVisibleAreaChangeEvent

    处理可见区域变化事件,组件可见面积(即组件在屏幕显示区的面积,只计算父组件内的面积,超出父组件部分不会计算)与组件自身面积的比值与设置的阈值接近时触发。

    H:LazyForEach predict

    LazyForEach预处理

    H:List predict

    List预处理

    H:Builder:BuildLazyItem

    构建LazyItem

    H:CustomNode:BuildItem[组件名][self:组件ID][parent:父组件ID]

    构建自定义组件

    H:ExecuteJS

    运行ArkTS业务逻辑

    H:ViewPU.viewPropertyHasChanged 组件名 状态变量名 N

    状态变量更新,N表示该状态变量更新后影响的组件数量。该Trace关键字需要运行hdc shell param set persist.ace.debug.enabled 1命令然后重启应用才能生效

    H:JSAnimation

    执行显示动画

    H:Napi complete

    执行Napi接口函数的回调函数

    binder transaction

    同步binder调用

    H:DispatchDisplaySync

    帧回调函数执行

    H:aboutToBeDeleted

    组件析构时执行,在未使用复用机制时,FlushDirtyNodeUpdate和LazyForEach predict下会析构组件,导致刷新时组件重复创建

  • 应用在进行图片解码操作时,需要申请对应内存,当PixelMap较大且使用共享内存时,RS主线程将经历较长的纹理上传时间,导致卡顿现象。图形侧提供了DMA内存零拷贝功能,可在绘制图片时避免纹理上传时间消耗。在hilog日志中搜索CreatePixelMapExtended,通过memoryType的值可以确认PixelMap创建时使用的内存类型,值为2表示共享内存,值为4表示DMA内存。

问题定位

ArkUI页面滑动卡顿丢帧问题的定位思路如下:

使用DevEco Profiler Frame工具录制滑动卡顿丢帧过程,相关步骤可以参考创建深度分析任务并进行录制。如果已有待分析的Trace文件,可以参考会话区中数据导入的步骤,将文件导入DevEco Profiler中查看分析。

  1. 识别滑动过程:在DevEco Profiler上方的搜索框中输入H:APP_LIST_FLING搜索。

    如下图为搜索Trace关键字H:APP_LIST_FLING的示例,其中H:APP_LIST_FLING为示例应用(包名为com.example.myapplication,Trace中简写为e.myapplication)的滑动泳道,泳道中有颜色部分(图中为紫色)为页面滑动过程。点击H:APP_LIST_FLING泳道右侧星型按钮将泳道置顶,方便问题分析。

  2. 识别卡顿丢帧位置。

    找到Frame泳道后点击左侧箭头展开,查看子泳道是否存在数据。

    • Frame泳道中的子泳道存在数据:

      根据H:APP_LIST_FLING泳道中的Trace点框选滑动过程的数据,点击Display泳道确认滑动过程的屏幕刷新率,如下图中可看到屏幕刷新率为120Hz左右(Avg Hz为119),则Vsync信号周期为8.3ms,如果应用侧或Render Service侧未在8.3ms内执行完渲染流程,会引起丢帧。

      通过App Frame(如e.myapplication的下方)和RS Frame(render_service下方)标签的泳道中可分别确认应用、Render Service卡顿丢帧的位置,出现卡顿丢帧(未在8.3ms执行完渲染流程)的地方显示为红色,正常完成渲染(在8.3ms内执行完渲染流程)的帧显示为绿色。

      如下图可看到应用(e.myapplication)存在几处卡顿丢帧,点击红色部分,然后点击"Details"区域"Corresponding Slice"下方左侧的箭头跳转到应用主线程泳道,结合App Frame卡顿丢帧处(红色部分)以及应用主线程的Trace信息,可以分析应用卡顿丢帧的原因。

    • Frame泳道中的子泳道无数据(比如打开.sys文件时会出现)时,可以找到render_service泳道展开,通过其子泳道H:PreferredFrameRate确认屏幕刷新率,框选滑动过程Present Fence(图形上屏信号)泳道来确认滑动帧率,如下图可看到屏幕刷新率为120Hz,滑动过程FPS为88.9,远少于120,存在滑动卡顿丢帧的现象。

      在滑动范围中,Present Fence泳道上缺少Trace点H:Waiting for Present Fence的部分(下图方框中空白的部分),很可能是存在丢帧问题,需要结合应用侧、Render Service侧主线程的Trace信息确认。

  3. 确认卡顿丢帧模块。
    • Frame泳道中的子泳道存在数据:可根据滑动范围框选Frame泳道,查看应用和render_service泳道相应的FPS值以及颜色来确认卡顿丢帧模块。
      • 应用进程卡顿丢帧:

        如下图为应用侧卡顿丢帧的示例(屏幕刷新率为120Hz),可看到应用泳道(e.myapplication)存在卡顿帧(红色),而render_service泳道无卡顿帧(绿色)。应用泳道的FPS值为87.6,远少于120,应用侧存在卡顿丢帧,而render_service泳道的FPS值少于120是应用侧发生丢帧,提交绘制相关数据较晚导致。

      • Render Service进程卡顿丢帧:

        如下图为Render Service卡顿丢帧的示例(屏幕刷新率为120Hz),可看到应用泳道无卡顿帧(绿色),而render_service泳道存在卡顿帧(红色)。应用泳道的FPS值为122.0,而render_service泳道的FPS值为11.9,远少于120,Render Service进程存在卡顿丢帧。

    • Frame泳道中的子泳道无数据:可根据应用主线程和render_service线程的Trace信息的宽度、密集特征来大致判断发生卡顿丢帧的模块。
      • 应用进程卡顿丢帧:

        如下图为应用侧卡顿丢帧的示例,可看到应用泳道中卡顿丢帧区域(方框部分)的Trace信息较宽且相连到一块,而右侧未丢帧处的Trace点较短且均匀分布。

      • Render Service进程卡顿丢帧:

        如下图为Render Service卡顿丢帧的示例,可看到应用泳道中的Trace信息较短且均匀分布,而render_service泳道的Trace信息较宽且相连到一块。

  4. 分析卡顿丢帧原因。
  • 应用主进程卡顿丢帧分析:
    分析应用主线程卡顿丢帧问题,首先在hilog日志中搜索has changed during render关键字,如果日志中有大量State variable 'xxx' has changed during render日志打印,这是由于组件渲染绘制时有更新状态变量,一直触发节点刷新,该情况会导致滑动卡顿丢帧问题。
    收起
    自动换行
    深色代码主题
    复制
    1. 11889-11889 C03947/com.exam...AceStateMgmt com.examp...lication E [(100000:100000:scope)] FIX THIS APPLICATION ERROR: @Component 'XXXXXX'[11248]: State variable 'XXXXXX' has changed during render! It's illegal to change @Component state while build (initial render or re-render) is on-going. Application error!
    2. 11889-11889 C03947/com.exam...AceStateMgmt com.examp...lication E [(100000:100000:scope)] FIX THIS APPLICATION ERROR: @Component 'XXXXXX'[11248]: State variable 'XXXXXX' has changed during render! It's illegal to change @Component state while build (initial render or re-render) is on-going. Application error!
    3. 11889-11889 C03947/com.exam...AceStateMgmt com.examp...lication E [(100000:100000:scope)] FIX THIS APPLICATION ERROR: @Component 'XXXXXX'[11248]: State variable 'XXXXXX' has changed during render! It's illegal to change @Component state while build (initial render or re-render) is on-going. Application error!
    4. 11889-11889 C03947/com.exam...AceStateMgmt com.examp...lication E [(100000:100000:scope)] FIX THIS APPLICATION ERROR: @Component 'XXXXXX'[11248]: State variable 'XXXXXX' has changed during render! It's illegal to change @Component state while build (initial render or re-render) is on-going. Application error!

    如没有上述大量日志打印,继续分析Frame工具抓取的Trace信息。

    屏幕刷新率为120Hz下,如果应用在8.3ms内未执行完渲染流程,会引起卡顿丢帧。未执行完渲染流程的情况有:
    • 单帧耗时长:Vsync信号到来时,应用处理输入事件、执行渲染流程耗时多。下图为单帧耗时长的示例(屏幕刷新率为120Hz),可看到H:ReceiveVsync(接收Vsync信号处理的Trace点)整体耗时为13.4ms,超过了8.3ms。

      接收Vsync信号处理耗时主要包含渲染流程(Trace点为H:OnVsyncEvent)耗时以及预加载(Trace点为H:OnIdle)耗时,因此针对单帧耗时长问题,含有如下情况:
      • 渲染流程(Trace点为H:OnVsyncEvent)耗时多。

        渲染流程耗时多,可分析H:OnVsyncEvent下方哪部分Trace点较宽来确认问题原因,可能的情况如下表所示:
        展开

        Trace关键字

        含义

        耗时多的原因

        H:DispatchDisplaySync

        帧回调函数执行

        帧回调函数执行耗时业务逻辑

        H:DispatchTouchEvent

        点击事件处理

        点击事件处理时执行耗时逻辑

        H:FlushDirtyNodeUpdate

        标脏组件刷新

        多个状态变量更新,大量组件刷新

        H:UITaskScheduler::FlushTask

        刷新UI界面

        页面组件复杂,测量、布局耗时多

        H:HandleOnAreaChangeEvent

        执行OnAreaChange回调函数

        回调函数中执行耗时业务逻辑

        H:HandleVisibleAreaChangeEvent

        执行OnVisibleChange回调函数

        回调函数中执行耗时业务逻辑

      • 预加载(Trace点为H:OnIdle)耗时多。

        预加载耗时多,可分析H:OnIdle下方哪部分Trace点较宽来确认问题原因,可能的情况如下表所示:
        展开

        Trace关键字

        含义

        耗时多的原因

        H:LazyForEach predict

        LazyForEach预处理

        未采用组件复用、懒加载条目构建时间长

        H:List predict

        列表预加载

        组件复杂,测量、布局耗时多

        H:Preload FlowItem

        瀑布流(WaterFlow)组件的预加载

        组件复杂,测量、布局耗时多

    • 帧间耗时长:应用执行业务逻辑或调用系统接口等耗时多,导致无法在Vsync信号到来时处理输入事件、执行渲染流程。下图帧间耗时长的示例(屏幕刷新率为120Hz),可看到在两个H:ReceiveVsync(接收Vsync信号处理的Trace点)之间存在耗时为25.1ms的Trace点(H:ExecuteJS),超过8.3ms,应用执行业务代码耗时较多,导致丢帧。

      除了业务代码耗时多,可能的情况还有执行同步binder调用(Trace点为binder transaction)耗时多、Napi接口函数的回调函数执行耗时多(Trace点为H:Napi complete)等,可以查看ArkTS Callstack和Callstack泳道的调用栈来确认相关代码,定位耗时函数。

    • Render Service渲染进程卡顿丢帧分析:

      Render Service渲染进程卡顿丢帧,可查看render_service进程的RSUniRenderThread泳道是否存在单帧耗时长的情况。

      如下图可看到RSUniRenderThread泳道存在单帧耗时长(201.3ms)的情况,同时存在Trace点H:onCreateTexture,在绘制时创建8191*7176大小的纹理。

      在hilog日志中搜索CreatePixelMapExtended查看应用是否有创建像素图,如下日志示例中可以看到创建10800*7176的高分辨率图片,memoryType值为2表示采用共享内存,而不是DMA内存,导致Render Service渲染进程卡顿丢帧。

      收起
      自动换行
      深色代码主题
      复制
      1. 3551-27643 C02B61/com.exa...n/ImageSource com.examp...lication I CreatePixelMapExtended success, imageId:1769788704094294, desiredSize: (0, 0), imageSize: (10800, 7176), desiredHdr: 1, hdrType : 1, memoryType : 2, cost 427949 us

分析结论

ArkUI页面滑动卡顿丢帧的原因有:

  • 在组件渲染绘制时更新状态变量,一直触发节点刷新。
  • 回调函数、点击事件处理函数中执行耗时业务逻辑。
  • 多个状态变量更新,大量组件刷新。
  • 页面组件复杂,在测量、布局时耗时较多。
  • 未采用组件复用,频繁创建和销毁对象。
  • binder调用过多、耗时多。
  • 创建高分辨率图片时未采用DMA内存,在绘制图片时进行纹理上传。

修改建议