作者:姜怀修
1 简介
本文基于OpenHarmony 2.0
为基础,讲解Graphic子系统。Graphic子系统主要包括UI组件、布局、动画、字体、输入事件、窗口管理、渲染绘制等模块,构建基于轻量OS应用框架满足硬件资源较小的物联网设备或者构建基于标准OS的应用框架满足富设备(如平板和轻智能机等)的OpenHarmony系统应用开发。
1.1 OpenHarmony 架构图

2 基础知识
2.1 标准系统的图形子系统
提供了图形接口能力和窗口管理接口能力,
支持应用程序框架子系统和ACE等子系统使用。支持所有运行标准系统的设备使用。
其主要的结构如下图所示:

Wayland Client Adapter
是OpenHarmony
新增的窗口系统的Client端,通过AAFWK
的ability
为应用和UI
框架提供Window
、Surface
渲染和Vsync
帧同步信号。
Wayland Server
使用开源的三方库提供窗口管理、合成和输入事件的管理。Wayland详细介绍
-
Compositor:合成器,负责合成各个图层;
-
Renderer:合成器的后端渲染模块;
-
Shell:提供多窗口能力;
-
Vsync:垂直同步信号管理接口,负责管理所有垂直同步信号注册和响应;
-
Input Manger:多模输入模块,负责接收事件输入;
-
Wayland protocols:Wayland 进程间通信协议。
2.2 图形子系统相关代码目录
foundation/graphic/standard/
├── frameworks # 框架代码目录
│ ├── bootanimation # 开机动画目录
│ ├── surface # Surface代码
│ ├── vsync # Vsync代码
│ └── wm # WindowManager代码
├── interfaces # 对外接口存放目录
│ ├── innerkits # native接口存放目录
│ └── kits # js/napi接口存放目录
└── utils # 小部件存放目录
ace_ace_engine # JS UI框架
aafwk_standard #元能力子系统
third_party_flutter #依赖的三方库
flutter third_party_wayland-ivi-extension #依赖的三方库
wayland third_party_wayland_standard #依赖的三方库
wayland third_party_wayland-protocols_standard #依赖的三方库
wayland third_party_weston #wayland的参考实现weston
2.3 图形子系统重要类全景

3 图形子系统环境初始化
从graphic.rc
可以看出,启动了wms_service
和bootanimation
,这两个服务是显示启动。
foundation\graphic\standard\graphic.rc
上面这两个服务的启动是分别进入了各自的入口函数main
来完成的。
wms_service
的启动,通过startServer
来完成,同时垂直同步信号管理Vsync
初始化也在main
完成,通过VsyncModule::GetInstance()-\>Start()
方法注册。
3.1 wms_service初始化
从StartServer
可以看出WindowManagerServer
的实例通过AddSystemAbility
接口注册到SystemAbilityManager
,初始化完成。
3.2 Vsync初始化
VsyncModuleImpl
继承自VsyncModule
,VsyncModule::GetInstance()->Start()
,实际调用VsyncModuleImpl::Start()
,从InitSA()
的调用关系可以看出,最终将VsyncManager
的实例注册到SystemAbilityManager
。
我们再来看下VsyncMainThread
,调用WaitNextVBlank
获取到一个时间间隔,即VBlank
。
VBlank的定义:
在将光信号转换为电信号的扫描过程中,扫描总是从图像的左上角开始,水平向前行进,同时扫描点也以较慢的速率向下移动。当扫描点到达图像右侧边缘时,扫描点快速返回左侧,重新开始在第1行的起点下面进行第2行扫描,行与行之间的返回过程称为水平消隐。一幅完整的图像扫描信号,由水平消隐间隔分开的行信号序列构成,称为一帧。扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,这一时间间隔,叫做垂直消隐,也称场消隐(VBlank)。
vsyncThreadRunning_
值为true
,DRM
每隔timestamp
发出VSYNC
信号,触发对UI
进行渲染,至此Vsync
初始化完成。
3.3 bootanimation启动过程
bootanimation
的启动,主要是解析bootanimation.raw
文件,然后使用OpenHarmony
处理线程间通信的机制EventHandler
来执行Main函数。
我们先看bootanimation.raw
的解析,再看Main
函数的执行,是如何创建window
之后画出来的。
-
bootanimation.raw的解析
对raw
文件的读取,分成若干帧数据,每次读取一块数据,后面的Draw
会取出这块数据copy
到SurfaceBuffer
的虚拟地址用来显示,后面会说到。
-
window的创建并显示
void Main()
{
const int32_t width = WindowManager::GetInstance()->GetMaxWidth();
const int32_t height = WindowManager::GetInstance()->GetMaxHeight();
WindowConfig config = {
.width = width,
.height = height,
.pos_x = 0,
.pos_y = 0,
.format = PIXEL_FMT_RGBA_8888,
.type = WINDOW_TYPE_NORMAL,
};
Static std::unique_ptr<Window> window = WindowManager::GetInstance()->CreateWindow(&config);
if (window == nullptr) {
LOG("window is nullptr");
} else {
window->Move(0, 0);
window->ReSize(width, height);
window->SwitchTop();
auto onWindowCreate = [](uint32_t pid) {
constexpr uint32_t stringLength = 32;
char filename[stringLength];
(void)snprintf_s(filename,
sizeof(filename), sizeof(filename) - 1, "/proc/%d/cmdline", pid);
auto fp = fopen(filename, "r");
if (fp == nullptr) {
return;
}
char cmdline[stringLength] = {};
fread(cmdline, sizeof(char), stringLength, fp);
fclose(fp);
if (strcmp(cmdline, "com.ohos.systemui") == 0 ||
strcmp(cmdline, "com.ohos.launcher") == 0) {
LOG("exiting");
exit(0);
}
};
window->RegistOnWindowCreateCb(onWindowCreate);
DoubleSync(0, window.get());
}
PostTask([]() { LOG(""); exit(0); }, PRETIME + POSTTIME);
}
void DoubleSync(int64_t time, void *data)
{
static int32_t count = 0;
if (count % 2 == 0) {
Draw(reinterpret_cast<Window *>(data));
}
count++;
RequestSync(DoubleSync, data);
}
void Draw(Window *window)
{
sptr<Surface> surface = window->GetSurface();
if (surface == nullptr) {
LOG("surface is nullptr");
return;
}
do {
sptr<SurfaceBuffer> buffer;
int32_t releaseFence;
BufferRequestConfig config;
window->GetRequestConfig(config);
SurfaceError ret = surface->RequestBuffer(buffer, releaseFence, config);
if (ret == SURFACE_ERROR_NO_BUFFER) {
break;
}
if (ret) {
LOG("RequestBuffer failed: %{public}s", SurfaceErrorStr(ret).c_str());
break;
}
if (buffer == nullptr) {
break;
}
static uint32_t count = 0;
auto addr = static_cast<uint8_t *>(buffer->GetVirAddr());
while (true) {
int32_t drawRet = DoDraw(addr, buffer->GetWidth(), buffer->GetHeight(),count);
if (drawRet && count == 0) {
exit(1);
}
if (drawRet) {
count--;
continue;
}
break;
}
BufferFlushConfig flushConfig = {
.damage = {
.w = buffer->GetWidth(),
.h = buffer->GetHeight(),
},
};
ret = surface->FlushBuffer(buffer, -1, flushConfig);
LOG("Sync %{public}d %{public}s", count, SurfaceErrorStr(ret).c_str());
fflush(stdout);
count++;
} while (false);
}
int32_t DoDraw(uint8_t *addr, uint32_t width, uint32_t height, uint32_t count)
{
constexpr uint32_t stride = 4;
int32_t addrSize = width * height * stride;
static auto frame = std::make_unique<uint8_t[]>(addrSize);
static uint32_t last = -1;
int64_t start = GetNowTime();
uint8_t *data = nullptr;
uint32_t length;
uint32_t offset;
if (RawParser::GetInstance()->GetData(count, data, offset, length)) {
return -1;
}
if (last != count && length > 0) {
memcpy_s(frame.get() + offset, addrSize - offset, data, length);
}
memcpy_s(addr, addrSize, frame.get(), addrSize);
last = count;
LOG("GetData time: %{public}lld, data: %{public}p, length: %{public}d",
GetNowTime() - start, data, length);
return 0;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
4 总结
以上内容首先对OpenHarmony
系统启动时图形子系统的环境初始化,然后对开机动画的显示过程做了源码分析。后续内容会持续发布。另外,我们根据开机动画的解析方式,写了一个工具可以生成bootanimation.raw
,即定制开机动画,见附件。
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
流弊,膜拜大佬~
写的真不错,收藏了
能把问题分析地如此透彻的作者一定很帅吧~~~
马上上手更换一个开机动画,走你
好文,排版优美~