跳转至

UIKit 图形服务与调试

屏幕旋转

中间buffer方案

原理为将内容绘制中间buffer上,再将中间buffer旋转到fb并送显fb。修改方式如下:

  1. 打开文件“/src/middleware/services/gui/hal/common/include/graphic_hardware_config_wearable.h”将宏ROTATE_METHOD的值修改为0。

    须知: 在宏ROTATE_METHOD的值为0时,宏HW_ROTATE_ANGLE的值不能修改为0,否则将无内容显示。

  2. 将宏HW_ROTATE_ANGLE修改为需要旋转的角度(1:顺时针90度,2:顺时针180度,3:顺时针270度)。

预旋转方案

原理为将绘制的内容加上旋转变换绘制到fb上,送显fb。修改方式如下:

  1. 打开文件“/src/middleware/services/gui/hal/common/include/graphic_hardware_config_wearable.h”将宏ROTATE_METHOD的值修改为1。
  2. 将宏HW_ROTATE_ANGLE修改为需要旋转的角度(0不旋转,1表示顺时针90度,2顺时针180度,3顺时针270度)。

DPU旋转方案

原理为直接绘制到DPU上,再由DPU旋转送显fb。修改方式如下:

  1. 打开文件“/src/middleware/services/gui/hal/common/include/graphic_hardware_config_wearable.h”将宏ROTATE_METHOD的值修改为2。
  2. 将宏HW_ROTATE_ANGLE修改为需要旋转的角度(0:不旋转,1:顺时针90度,2:顺时针180度,3:顺时针270度)。

Graphic Service

整体方案

图 1 Graphic service总体方案

Graphic-service总体方案

Graphic service总体上分为两部分:

  • 初始化graphic。
  • 创建两个任务线程,分别完成主任务执行,及vblank(显示器扫描线完成最后一行后,需要重返左上角的过程,也叫VBI(Vertical Blank Interval)、AOD(Always On Display,常亮显示)、Screen等硬件信息监测。

通过InitGraphicService()调用,完成对gralloc、display HAL、input、TaskManager等的初始化,准备graphic执行环境。同时会启动两个线程。

  • main task:只做一件事,执行event queue中的任务,若队列空则睡眠等待信号量。
  • vsync thread:监测screen、AOD信息,若是灭屏或低功耗模式,图形将睡眠等待信号量;否则获取vblank信号,并以此为周期将TaskHandler推送到main task执行。

所有外部模块对图形的调用,均需通过PostGraphicEvent()接口推送到event queue,等待vblank周期执行。

对外接口

表 1 graphic service对外API

函数 说明
void PostGraphicEvent(const OHOS::GraphicEvent& event) 向图形service任务队列推送任务,任务将在后续的Vsync周期被执行。
注意:仅推送涉及UI属性变更或刷新的任务,切勿推送无关且耗时的处理,这会阻塞UI,导致显示卡顿。
void NotifyScreenOn() 设置screen flag为1(on)。
void NotifyScreenOff() 设置screen flag为0(off)。

说明: 若是C语言代码,通过graphic_service_wrapper中对应的接口调用。

表情包支持

方案介绍

不同于文字使用矢量字库绘制,表情包使用图片进行绘制,支持自定义表情包,表情包自定义流程如下:

  1. 制作表情包压缩数据(请参考“图片资源打包”)。
  2. 在头文件ui_emoji_table.cpp中定义unicode码点和表情包图片的映射关系。
  3. 导入表情包压缩数据并使用。

制作表情包数据库和表情包映射表

制作表情包数据库和表情包映射表流程如下:

  1. 将使用的表情包用Debugkits以package的方式进行压缩,并将名字保存为emoji.bin。

    将使用的表情包用Debugkits以package的方式进行压缩,并将名字保存为emoji.bin

  2. 将表情包与码点的映射关系放到头文件“/src/middleware/services/gui/uikit/proprietary/src/ui/frameworks/common/ui_emoji_table.cpp”中去,同时通过变量“EMOJI_PATH”配置emoji.bin文件的位置。

    制作表情包数据库和表情包映射表之二

  3. 将emoji.bin放入配置的路径中去。

绘制原理

普通的码点(unicode)首先通过查找字库,获取矢量数据进行绘制。对于找不到的码点会先在自定义的表情包数据库中寻找,如果找到,会提取表情包的图片数据,并进行绘制;如果在表情包映射表中也找不到,将会在配置的多语言字库中查找码点数据。

DFX

内存占用

  • dumpmem

    打印UIKit占用内存,包含以下内存:

    • current malloc/new memory:

      通过UIKit UIMalloc或new分配的内存块数量。

    • current gralloc allocate memory:

      通过UIKit GrallocEngine分配的内存,包含图片、矢量、视频Buffer等内存。

    注意,如果当前dumpmem数值在慢慢变大,一般说明出现内存泄漏,请参考“内存泄漏定位”;如果当前dumpmem数值在慢慢减小,说明出现内存重复释放,需要自行定位。

  • dumpimg

    打印UIKit中图片资源缓存信息,以及相关控件所持有的图片资源信息。

说明: 在串口工具中发送指令,指令格式为:‘AT+GUI=UIKIT_DFX,’+具体命令,后续不再赘述。

帧率

  • printfps

    • -h:打印帧率说明。
    • duration n:n秒,持续打印帧率n秒[n次]。

    仅输入printfps,则只打印一次帧率。

  • showfps

    • -h:显示帧率说明。
    • -p:指定显示起始位置。参数格式:x,y。

    仅输入showfps,则默认显示在屏幕中间。

    其坐标位置仅在第一次输入showfps时有效,后续再调用均无效。

  • hidefps

    隐藏帧率显示。

帧率DFX示例如下:

/* 打印帧率 */
AT+GUI=UIKIT_DFX,printfps,10
/* 显示帧率 */
AT+GUI=UIKIT_DFX,showfps,-p,100,100
/* 隐藏帧率 */
AT+GUI=UIKIT_DFX,hidefps

截图

  • dump_view_id

    串口输入此命令,将当前RootView下所有设置过viewId的子节点的viewId和坐标信息输出在串口中:[viewId]: x, y, w, h。

  • screencap

    • -h:截图指南,无参。
    • -v:控件截图,截取指定控件的数据。参数格式:viewId。
    • -c:区域截图,截取指定区域的数据。参数格式:x,y,w,h。参数非负,且不能超过屏幕大小。
    • -f:输出文件,截取数据保存至该输出文件。

    说明:

    • -f需配置为最后一个选项。如果-f前无其他选项,则判定为全屏截图,是从FB捞取的图片数据。
    • -v是重新绘制后截取的图片数据。-c是从FB中捞取的图片数据。
    • FB捞取的图片数据的压缩模式是COMPRESS_MODE_HFBC_2X,重绘截取的图片数据的压缩模式是COMPRESS_MODE_HFBC_3X。
    • 截取的压缩图片需要解压才能查看,解压缩流程请参考《DebugKits工具使用指南》中的“图形压缩对比”章节
    • 视频截图仅能通过-v,需要先打印控件树获取视频控件ID,再配置ID通过-v截取视频图片数据。
    • 文件保存的数据格式为RGB888。
    • 文件像素宽度为16倍数对齐,即真实宽度为:

    文件像素宽度为16倍数对齐,即真实宽度为

    • 串口回显:

      screencap result: 1.(表示执行成功)。

      screencap result: 0.(表示执行失败)。

截图DFX示例如下:

/* 区域截图 */
AT+GUI=UIKIT_DFX,screencap,-c,0 0 100 100,-f,/bin/vs/sd0p0/rect.bin
/* 组件截图 */
dump_view_id
AT+GUI=UIKIT_DFX,screencap,-v,mainclickactivity,-f,/bin/vs/sd0p0/view.bin
/* 全屏截图 */
AT+GUI=UIKIT_DFX,screencap,-f,/bin/vs/sd0p0/screen.bin

模拟事件

模拟旋转表冠事件

  • inject_rotate n
    • 参数n:旋转刻度(有符号整形类型)。
    • 示例:

      AT+GUI=UIKIT_DFX,inject_rotate,10

模拟按键事件

  • inject_key keyId state
    • 参数keyId:键值(字符串类型),合法值[power,func]。
    • 参数state:按键状态(无符号整形类型),合法值[0, 1, 2]。0代表松开,1代表按下,2代表长按。
    • 示例:

      AT+GUI=UIKIT_DFX,inject_key,power,1

模拟点击事件

  • inject_click x y
    • 参数x:X坐标(无符号整形类型)。
    • 参数y:Y坐标(无符号整形类型)。
    • 示例:

      AT+GUI=UIKIT_DFX,inject_click,100,200

模拟拖动事件

  • inject_drag startX startY endX endY duration
    • 参数startX:起始X坐标(无符号整形类型)。
    • 参数startY:起始Y坐标(无符号整形类型)。
    • 参数endX:结束X坐标(无符号整形类型)。
    • 参数endY:结束Y坐标(无符号整形类型)。
    • 参数duration:拖动时长ms(无符号整形类型)。
    • 示例:

      AT+GUI=UIKIT_DFX,inject_drag,200,400,200,100,30

模拟长按事件

  • inject_lp x y
    • 参数x:X坐标(无符号整形类型)。
    • 参数y:Y坐标(无符号整形类型)。
    • 示例:

      AT+GUI=UIKIT_DFX,inject_lp,100,30

时延统计

  • showtime param

    参数param:要统计时延的类型,可取值point、tp、render、close。

    • point:打印UI一秒内尝试查询tp点的次数及相关信息。
    • tp:实时打印tp报点。
    • render:打印绘制耗时。
    • close:关闭时延统计。
    • 示例:

      AT+GUI=UIKIT_DFX,showtime,render

  • showtime point

  • showtime tp
  • showtime render count flush

    • 含义:渲染过程中每一帧软硬件耗时、task耗时和event耗时。
    • 示例

      • 示例1:

        AT+GUI=UIKIT_DFX,showtime,render,30,0

        30:打印接下来30帧软硬件绘制指令。

        0:第一帧不强制刷新。

      • 示例2:

        AT+GUI=UIKIT_DFX,showtime,render,30,1

        30:打印接下来30帧软硬件绘制指令。

        1:第一帧强制刷新。

    打印结果(截取部分):

    Total: E 11, T 50, F 10
    
    E 0: 0, 81
    
    E 1: 13, 16330
    T 0: 17, 422
    T 1: 7, 36
    T 2: 6, 103
    T 3: 5, 17
    T 4: 5, 15699
    isNativeRunning 1, freq 60
    Cmd count: 9 time: 15037, {0 0 465 465}
    0. hw_rect: 575, 187, {0, 0, 465, 465}
    1. hw_blit: 766, 252, 1
    2. hw_path: 1187, 857, 11
    3. hw_blit: 1171, 239, 1
    4. hw_blit: 716, 422, 2
    5. hw_blit: 1063, 771, 6
    6. hw_wait: 29, 95
    7. hw_flush: 48, 7122, {0, 0, 465, 465}, s
    8. hw_submit: 47, 65, 0
    

    表 1 showtime render打印结果说明

    格式

    含义

    备注

    Total: E 11, T 50, F 10

    • E 11:本次图形线程记录共11个event;
    • T 50:本次图形线程记录共50个task;
    • F 10:本次图形线程成功送显10帧。

    图形线程执行流程:while循环处理event队列,event分为内部/外部两种类型,内部event进一步拆分为多个task(最后三个task固定为输入/动效/渲染送显)。

    E 0: 0, 81

    • E 0:第0号event;
    • 0:距离上个event结束的间隔时间(首个event为0μs);
    • 81:本event耗时81μs。

    该event无task,属于外部event。

    E 1: 13, 16330

    T 0: 17, 422

    T 1: 7, 36

    T 2: 6, 103

    T 3: 5, 17

    T 4: 5, 15699

    isNativeRunning 1, freq 60

    Cmd count: 9 time: 15037, {0 0 465 465}

    0. hw_rect: 575, 187, {0, 0, 465, 465}

    ...

    • E 1: 13, 16330

      13:距离上个event结束13μs后开始;

      16330:本event总耗时16330μs。

    • T 0: 17, 422

      T 0:属于event 1的第0号task;

      17:距离上个task开始间隔17μs(首个task表示event开始到task开始的间隔);

      422:本task耗时422μs。

    • T 4: 5, 15699

      5:距离上个task间隔5μs;

      15699:送显task总耗时15699μs。送显task可进一步拆解为指令级耗时(见下面内容)。

    -

    T 4: 5, 15699

    isNativeRunning 1, freq 60

    Cmd count: 9 time: 15037, {0 0 465 465}

    0. hw_rect: 575, 187, {0, 0, 465, 465}

    1. hw_blit: 766, 252, 1

    2. hw_path: 1187, 857, 11

    3. hw_blit: 1171, 239, 1

    4. hw_blit: 716, 422, 2

    5. hw_blit: 1063, 771, 6

    6. hw_wait: 29, 95

    7. hw_flush: 48, 7122, {0, 0, 465, 465}, s

    8. hw_submit: 47, 65, 0

    • isNativeRunning 1, freq 60”:

      isNativeRunning 1:表示本次送显是在图形线程送显,如果为0表示在非图形线程送显(例如JS线程);

      freq 60:当前送显最高帧率为60(实际帧率小于等于60)。

    • Cmd count: 9 time: 15037, {0 0 465 465}:

      Cmd count: 9:本次送显包含9条指令;

      time: 15037:送显统计开始到结束总共耗时15037μs;

      {0 0 465 465}:送显区域,格式{left, top, right, down}。

    • 0. hw_rect: 575, 187, {0, 0, 465, 465}:

      0. hw_rect:绘制指令hw_rect的序号为0,其中hw_rect表示绘制一个矩形,详细解释见下面内容。

      575:上一次指令结束到本次指令开始之间的间隔为575μs,此处为第一次绘制,表示绘制统计开始到第一次指令之间的时间间隔;

      187:本次指令提交耗时为187μs;

      {0, 0, 465, 465}:矩形绘制区域,格式{left, top, right, down}。

    指令格式规范:

    <序号>. <sw/hw>_<指令类型>:<间隔时间>,<执行耗时>,<附加信息>[, <属性标记>]

    字段说明:

    • sw/hw前缀
      • sw:软件实现的指令(如 sw_rect表示软件绘制矩形);
      • hw:硬件加速的指令(如hw_rect表示硬件绘制矩形)。
    • 附加信息(根据指令类型变化):
      • 默认携带绘制区域(格式:{left, top, right, bottom});
      • 特殊指令携带:

        hw_blit:图片搬移次数(整数);

        hw_path:矢量路径数量(整数);

        hw_flush:送显缓冲区类型(s=SRAM/p=PSRAM);

        hw_submit:提交方式(0:异步;1:同步)。

    • 属性标记(可选):
      • fill:填充区域;
      • stroke:描边区域;
      • both:同时包含填充和描边;
      • s/p:标识使用的显存类型。

    示例说明:"3. hw_rect: 100,200,{0,0,480,800},fill"

    表示:第3条指令,硬件绘制矩形,距上一条指令间隔100μs,执行耗时200μs,绘制区域为(0,0)-(480,800),包含填充属性。

    sw_tran

    软件绘制图片变换,携带信息为区域。

    -

    sw_blit

    软件图片搬移,携带信息为区域。

    -

    hw_blit

    硬件图片搬移,携带信息为搬移图片数量。

    -

    hw_path

    硬件矢量绘制,携带信息为矢量路径数量。

    由于一次绘制中,矢量绘制的次数会很多,因此将矢量绘制的信息压缩。例如,“hw_path: 1187, 857, 11”,表示总间隔时间为1187μs,硬件总提交指令耗时为857μs,总共11条指令。

    普遍场景下,文字、地图上的路径都是由矢量绘制的。

    hw_clip

    硬件矢量路径截图,携带信息为区域。

    -

    sw_bezier

    软件贝塞尔曲线,不携带信息。

    -

    sw_rect

    软件矩形绘制,携带信息为区域。

    -

    hw_rect

    硬件矩形绘制,携带信息为区域,此外有可能额外包含路径的属性:fill、stroke、both。

    -

    hw_blur

    硬件高斯模糊绘制,携带信息为区域。

    -

    sw_letter

    软件绘制文字,携带信息为文字区域。

    -

    sw_arc

    软件绘制圆弧,携带信息为区域。

    -

    sw_line

    软件绘制直线,携带信息为区域。

    -

    hw_submit

    硬件指令提交,携带信息为异步和同步,0:异步提交;1:同步提交。

    -

    hw_flush

    硬件送显,携带信息为送显区域,显存类型包括sram和psram,s:sram;p:psram。

    -

    hw_wait

    硬件等待上一次提交指令绘制完成,无携带信息。

    -

    hw_arc_rect

    硬件圆角矩形绘制,携带信息为区域,此外有可能额外包含路径的属性:fill、stroke、both。

    -

    hw_line

    硬件绘制直线,携带信息为区域,此外有可能额外包含路径的属性:fill、stroke、both。

    -

    hw_arc

    硬件绘制圆弧,携带信息为区域,此外有可能额外包含路径的属性:fill、stroke、both。

    -

    hw_bezier

    硬件贝塞尔曲线,携带信息为区域,此外有可能额外包含路径的属性:fill、stroke、both。

    -

    hw_ellipse

    硬件绘制椭圆,携带信息为区域,此外有可能额外包含路径的属性:fill、stroke、both。

    -

    hw_light

    硬件绘制光照动效,携带信息为区域。

    -

    hw_3d_cylinder

    硬件绘制圆柱动效,携带信息为区域。

    -

    hw_3d_mesh

    硬件绘制方格动效,携带信息为区域。

    -

    hw_3d_sphere

    硬件绘制球体效果,携带信息为区域。

    -

    hw_rain

    硬件绘制雨滴效果,携带信息为区域。

    -

控件树打印

  • dump_root_view

    以json格式打印当前整个控件树,打印信息会输出到屏幕上,同时会将信息保存到“/user/res/view_tree.json”。

    • 示例:

      AT+GUI=UIKIT_DFX,dump_root_view

内存泄漏定位

  • leak_panic cnt size

    该条指令需要结合代码分析,在存在内存泄漏的代码区间(例如某个类的构造和析构之间)两端分别调用如下代码:

    MemCheck::GetInstance()->EnableLeakCheck(true);  // 检测开始
    // some code
    MemCheck::GetInstance()->EnableLeakCheck(false); // 检测结束
    

    结果会输出这个区间内没有释放的内存,包含信息为“申请次序+大小”,多次运行这段代码,内存泄漏会在某次固定存在,在下一次运行这段代码前发出指令“AT+GUI=UIKIT_DFX,leak_panic,cnt,size”。其中cnt表示第几次,size表示大小,该指令会在第cnt次申请内存时主动崩溃,并输出调用栈信息,通过调用栈可以定位内存泄漏位置。

    • 示例:

      AT+GUI=UIKIT_DFX,leak_panic,16,24

Native状态打印

  • dumpstate

    打印Native UI的运行状态。打印的信息包含:

    • NativeRunning:应用运行状态,用于区分Native和JS的状态。1代表Native正在运行中,0代表JS正在运行中。
    • IsScreenOn:屏幕量灭屏状态。1代表处于亮屏状态,0代表处于灭屏状态。
    • GraphicTaskTriggeredCount:已触发的Native任务个数。
    • LowPower:UI低功耗相关信息。
    • 示例:

      AT+GUI=UIKIT_DFX,dumpstate

术语表

A

     

AOD

Always On Display

常亮显示

        

D

     

DFX

Design for eXcellence

产品非功能性属性设计

        

E

     

eMMC

Embedded Multimedia Controller

嵌入式多媒体控制器

        

G

     

GUI

Graphical User Interface

图形用户界面

        

H

     

HAL

Hardware Abstraction Layer

硬件抽象层

HSV

Hue, Saturation, Value

色度、彩度、亮度

        

I

     

ICU

     

IoT

Internet of Things

物联网

        

L

     

LTR

Left To Right

从左向右

        

O

     

OS

Operating System

操作系统

        

P

     

POI

     

PSRAM

Pseudostatic Random Access Memory

假静态随机存储器

        

R

     

RTL

Right To Left

从右向左

        

S

     

SVG

Scalable Vector Graphics

可缩放矢量图形

        

U

     

UI

User Interface

用户界面

        

V

     

VBI

Vertical Blanking Interval

垂直消隐区

        

X

     

XML

Extensible Markup Language

可扩展标记语言

注意事项

  • UIKit 的组件树具有所有权关系。页面创建的容器、标签、图片和资源应在页面退出或析构时按创建的反向顺序释放;图片资源必须配对调用加载和卸载接口。
  • 使用 UIViewGroup::Add() 后,子视图由容器参与显示和事件分发;仅创建组件而未调用 AddViewToPageContainer() 不会显示页面内容。
  • 动态修改文本、位置、可见性或样式后,按控件行为调用 Invalidate() 或对应组件刷新接口,避免依赖下一帧偶然重绘。
  • 新建应用必须使用未占用的 VIEW_* 标识和页 ID。重复注册会造成菜单入口异常、页面跳转错乱或链接阶段的重复符号错误。
  • HelloWorld 的菜单和页面注册受 SDK_DITING_COMMUNITY 条件保护。移植到其他目标时,应先确认对应目标启用了 UIKit、OpenHarmony Native 框架和所需应用组件。
  • 组件详细说明中的资源、表盘、旋转、Graphic Service 和 DFX 特性可能依赖特定硬件、资源包或产品配置;使用前先核对各章节的前置条件和 SDK 源码中的编译条件。

常见编译错误

现象 原因与处理
找不到 components/ui_label.hSlicePage.h 将页面放入现有 /src/application/wearable/nativeapp/nativeuinativeui/include 结构,复用 nativelauncher 现有的 UIKit 头文件配置;不要在外部组件中猜测私有头文件路径。
固件中没有新增菜单 检查 REGIST_MENU 是否参与编译、VIEW_* 是否已加入 AppViewIDs.h,以及是否被 SDK_DITING_COMMUNITY 或其他条件编译屏蔽。
进入页面后黑屏或没有控件 检查根容器位置和尺寸、container->Add()AddViewToPageContainer()、字体与图片资源加载是否全部成功。
页面切换后内存增长 检查容器是否 RemoveAll()、通过 new 创建的控件是否释放、LoadAllInMultiRes() 是否有对应的 UnloadAllInMultiRes()。可参考本文 DFX 章节中的内存泄漏定位方法。
修改页面源码后未生效 nativelauncher 通过递归规则收集 nativeui/*.cpp;新增文件或编译配置变化后,按 CLI 指南重新配置并构建,避免复用过期构建目录。