Hello World 与工程解析
快速跑通 Hello World
本章参考《Hello World 应用开发入门指南》,用于在修改 Native Slice 代码前先跑通仓内已提供的 helloworldpage 示例。完成本章后,应能确认源码路径、编译命令、应用入口和页面显示链路均正常。
编译 Hello World
完成一站式 CLI 环境配置后执行完整打包命令:
构建成功后,使用一站式 CLI 烧写固件并打开 UART2 串口监视器。以下为 Windows USB DFU 示例;将 COM3 替换为实际日志串口,其他平台和串口烧写参数参见一站式 CLI 开发环境使用指南。
fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --chip 3322 -d --timeout 180
fbb monitor --port COM3 --baud 750000
-c 会执行清理动作,适合首次验证或新增 .cpp 后排除 CMake 缓存影响。若仅做日常增量编译,可按工程实际编译脚本选择对应目标;本文档以可烧录固件验证为准。
编译通过后,应生成可用于设备烧录的固件产物。若命令在依赖检查阶段失败,先按工程编译环境要求补齐 Python 模块、交叉编译工具链和平台构建依赖,再重新执行。
运行验收
烧录固件并启动设备后,按以下项目验收:
- 应用列表中出现 HelloWorld。
- 点击 HelloWorld 后进入 Native 页面。
- 页面中央显示 Hello World 文本。
- 串口日志可看到
HelloWorldPage OnStart,日志级别为WEARABLE_LOGD。
若以上结果均符合,说明 NativeAbility、菜单注册、SlicePage 注册和页面渲染的基础链路已跑通,可以继续阅读下一章的代码走读。
跑通失败排查
| 现象 | 排查点 |
|---|---|
编译未包含新增 .cpp |
确认源码放在 src/application/wearable/nativeapp/nativeui/ 下,并重新执行带 -c 的打包命令。 |
| 应用列表没有 HelloWorld | 检查 HelloWorldPresenter.cpp 中 REGIST_MENU 是否受 SDK_DITING_COMMUNITY 宏控制且当前目标已打开该宏。 |
| 点击后页面为空 | 检查 HelloWorldPage::OnStart 是否调用 AddViewToPageContainer(container_)。 |
| 页面进入后异常退出 | 检查动态创建的控件是否判空,析构中是否按控件树顺序释放资源。 |
工程代码的文件结构和代码走读
本章围绕 HelloWorld Native Demo 的工程文件、注册链路、页面实现和进阶参考进行代码走读。HelloWorld 示例本身较简单,开发者可以结合 nativeui 目录下其他 Native 应用继续扩展多页面、页面跳转、共享片段和生命周期处理。
HelloWorld Demo目录
HelloWorld Demo 是最小可用 Native Slice Demo,目录结构如下:
nativeui/
├── helloworldpage/
│ ├── HelloWorldPage.cpp
│ └── HelloWorldPresenter.cpp
└── include/
└── helloworldpage/
├── HelloWorldPage.h
├── HelloWorldPresenter.h
└── HelloWorldView.h
各文件职责如下:
| 文件 | 职责 |
|---|---|
HelloWorldView.h |
定义 View 类型,用于 Slice 注册和 Presenter 绑定。 |
HelloWorldPresenter.h |
定义 Presenter 类型,可承载业务逻辑。 |
HelloWorldPresenter.cpp |
使用 REGIST_MENU 注册应用菜单入口。 |
HelloWorldPage.h |
定义 SlicePage 页面类和页面持有的 UI 控件。 |
HelloWorldPage.cpp |
使用 REGIST_SLICE_PAGE 注册页面,并在 OnStart 中创建页面内容。 |
Demo注册流程
定义应用View ID
在 nativelauncher/include/AppViewIDs.h 中定义应用 ID:
新增 Demo 时应新增唯一的 VIEW_XXX,并放在 VIEW_MAX_INTER_ARRY_APP 之前。菜单注册、Slice 注册和页面注册必须使用同一个 View ID,否则点击菜单后无法进入预期页面。
注册菜单入口
HelloWorldPresenter.cpp 中使用 REGIST_MENU 注册应用列表入口:
#include "NativeRegisterManager.h"
#include "helloworldpage/HelloWorldView.h"
#include "helloworldpage/HelloWorldPresenter.h"
namespace OHOS {
#ifdef SDK_DITING_COMMUNITY
REGIST_MENU(VIEW_HELLOWORLD, HelloWorldView, HelloWorldPresenter, EMPTY_ICON, EMPTY_ICON, "HelloWorld");
#endif
}
REGIST_MENU 的参数依次表示 View ID、View 类型、Presenter 类型、普通图标、六边形图标和应用名称。最小 Demo 可以先使用 EMPTY_ICON,后续再替换为自定义资源。
如果某个 Slice 只作为内部跳转目标,不需要出现在应用列表中,可使用 REGIST_SLICE(id, V, P) 只注册 Slice,不注册菜单。switch_slice_demo 中只有入口 Slice 通过 REGIST_MENU 展示在应用列表,其余 Slice 通过内部跳转进入。
注册SlicePage页面
HelloWorldPage.cpp 中使用 REGIST_SLICE_PAGE 注册页面:
static constexpr uint16_t HELLOWORLD_PAGE = 1;
#ifdef SDK_DITING_COMMUNITY
REGIST_SLICE_PAGE(VIEW_HELLOWORLD, HELLOWORLD_PAGE, HelloWorldPage, true);
#endif
参数说明如下:
| 参数 | 说明 |
|---|---|
VIEW_HELLOWORLD |
归属的 Slice ID,需要和 REGIST_MENU 保持一致。 |
HELLOWORLD_PAGE |
页面 ID。同一个 Slice 下多个页面需要使用不同 ID。 |
HelloWorldPage |
页面类型。 |
true |
是否为主页面。应用入口页应设置为 true。 |
Demo页面实现
HelloWorldPage 继承自 SlicePage<HelloWorldPresenter>,在 OnStart 中创建根容器和文本控件:
void HelloWorldPage::OnStart(void *data)
{
container_ = new UIViewGroup();
container_->SetPosition(0, 0, HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION);
container_->SetDraggable(true);
container_->SetTouchable(true);
helloWorldLabel_ = new UILabel();
helloWorldLabel_->SetPosition(121, 161, 193, 40);
helloWorldLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER);
helloWorldLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30);
helloWorldLabel_->SetText("Hello World");
container_->Add(helloWorldLabel_);
AddViewToPageContainer(container_);
}
页面实现要点如下:
-
使用
UIViewGroup作为根容器,尺寸优先使用HORIZONTAL_RESOLUTION和VERTICAL_RESOLUTION。 -
子控件通过
SetPosition设置位置和尺寸。 -
文本控件通过
SetFont、SetAlign、SetText设置显示效果。 -
子控件通过
container_->Add()加入根容器。 -
根容器通过
AddViewToPageContainer()加入 SlicePage 页面容器。
页面退出时需要释放动态创建的控件:
HelloWorldPage::~HelloWorldPage()
{
if (container_ != nullptr) {
container_->RemoveAll();
delete container_;
container_ = nullptr;
}
if (helloWorldLabel_ != nullptr) {
delete helloWorldLabel_;
helloWorldLabel_ = nullptr;
}
}
如果页面使用图片缓存、定时器或业务资源,也需要在析构函数或 OnStop 中释放,避免重复进入页面后产生内存泄漏或野指针。
进阶能力参考
HelloWorld 只体现最小页面。开发实际 Demo 时,可继续参考 nativeui 下已有应用扩展能力:
| 参考目录 | 可学习能力 |
|---|---|
nativeui/switch_slice_demo |
多 Slice 注册、多页面注册、页面切换、返回栈、点击事件和 UIFragment 复用。 |
nativeui/alarm |
点击事件、拖拽事件、弹窗 Slice、定时器、数字时钟、业务 Model 和 Presenter 事件分发。 |
nativeui/applist |
UIList、AbstractAdapter、列表项复用、应用列表点击进入应用。 |
nativeui/settings |
多级设置页面、Model 驱动列表、页面内跳转、蓝牙设备列表等复杂业务状态管理。 |
nativeui/activity |
图片缓存、样式设置、动态图表、数据变化后刷新控件。 |
nativeui/timers、nativeui/stopwatch |
计时、倒计时、周期刷新、页面暂停和恢复处理。 |
常用能力和接口如下:
| 能力 | 关键接口或类 | 说明 |
|---|---|---|
| 点击事件 | UIView::OnClickListener、SetOnClickListener |
控件需设置 SetTouchable(true) 后绑定监听。 |
| 拖拽事件 | UIView::OnDragListener、SetOnDragListener |
适合页面滑动、弹窗拖拽、返回手势等场景。 |
| 页面内跳转 | SwitchPageInSlice、BackToPrevSlicePage |
用于同一个 Slice 内多个 SlicePage 间跳转。 |
| Slice间跳转 | SwitchSlice、SwitchSliceWithCache、BackToPreSlice |
用于不同 Native 应用之间切换。 |
| 列表 | UIList、AbstractAdapter、GetView |
适合动态数据列表,列表项应支持复用。 |
| 图片 | UIImageView、ImageCacheManager、SetSrc |
大图或频繁使用图片建议走缓存管理。 |
| 异步刷新 | GraphicTimer、PostGraphicEvent |
定时器回调中操作 UI 时,应投递到图形事件队列。 |
| 分层 | Page、Presenter、Model |
页面负责 UI,Presenter 处理交互,Model 保存业务数据。 |