Native 应用开发与常见问题
快速开发 Native 应用
本章基于原“Native Demo开发示例”整理,说明开发者如何基于现有 nativeapp 工程快速创建一个 HelloWorld Native 应用,并完成菜单注册、页面入口注册和编译验证。示例参考源码路径如下:
src/application/wearable/nativeapp
src/application/wearable/nativeapp/nativeui/helloworldpage
src/application/wearable/nativeapp/nativeui/include/helloworldpage
Demo开发背景
穿戴 Native Demo 不是独立安装的HAP包,而是随 nativeapp 组件一起编译进系统镜像。新增 Demo 的核心工作是在已有 Native Ability Framework 中增加一个静态注册的 Slice 入口,并为该 Slice 注册一个或多个 SlicePage 页面。
Native Demo 的典型运行链路如下:
-
编译阶段,
CMakeLists.txt收集nativeui下的.cpp文件。 -
启动阶段,
REGIST_MENU、REGIST_SLICE、REGIST_SLICE_PAGE生成的静态注册对象完成 Slice 和页面注册。 -
应用列表读取
NativeRegisterManager中注册的菜单项,显示应用图标和名称。 -
用户点击菜单后,框架根据 View ID 创建对应 Slice,并通过
SlicePageFactory创建主页面。 -
页面进入
OnStart生命周期后,开发者创建 UI 控件并调用AddViewToPageContainer显示页面。 -
页面退出或销毁时,开发者释放自己创建的控件、定时器和图片资源。
如果注册代码被宏包裹,需要确认编译配置中启用了对应宏。例如 HelloWorld Demo 使用 SDK_DITING_COMMUNITY 控制注册是否生效。
创建开发者自己的应用
开发者可按以下步骤创建自己的应用,以下以 mydemo 为例。
-
创建源码和头文件目录。
-
在
AppViewIDs.h中新增唯一 View ID。 -
创建
MyDemoView.h。 -
创建
MyDemoPresenter.h和MyDemoPresenter.cpp,并注册菜单入口。 -
创建
MyDemoPage.h和MyDemoPage.cpp,并注册主页面。#include "AppViewIDs.h" #include "SlicePageFactory.h" #include "UiConfig.h" #include "mydemo/MyDemoPage.h" namespace OHOS { static constexpr uint16_t MY_DEMO_MAIN_PAGE = 1; #ifdef SDK_DITING_COMMUNITY REGIST_SLICE_PAGE(VIEW_MY_DEMO, MY_DEMO_MAIN_PAGE, MyDemoPage, true); #endif void MyDemoPage::OnStart(void *data) { container_ = new UIViewGroup(); container_->SetPosition(0, 0, HORIZONTAL_RESOLUTION, VERTICAL_RESOLUTION); container_->SetTouchable(true); titleLabel_ = new UILabel(); titleLabel_->SetPosition(80, 160, 240, 40); titleLabel_->SetAlign(TEXT_ALIGNMENT_CENTER, TEXT_ALIGNMENT_CENTER); titleLabel_->SetFont(DEFAULT_VECTOR_FONT_FILENAME, 30); titleLabel_->SetText("My Demo"); container_->Add(titleLabel_); AddViewToPageContainer(container_); } } -
确认新增
.cpp文件位于nativeui目录下。nativeapp/CMakeLists.txt会通过如下规则收集源码:
编译与验证
完成 Demo 后,按工程既有构建流程编译 NativeApp。验证时重点检查以下内容:
-
编译阶段无头文件找不到、View ID 重复、类名不匹配等错误。
-
应用列表中出现
REGIST_MENU注册的菜单名称。 -
点击菜单后进入
REGIST_SLICE_PAGE注册的主页面。 -
页面显示
OnStart中创建的控件。 -
退出页面后再次进入,控件显示正常且无异常日志。
如果 Demo 未出现在应用列表中,优先检查 REGIST_MENU 是否被当前编译宏包含,以及 Presenter.cpp 是否参与编译。
如果点击菜单后页面未显示,优先检查 REGIST_SLICE_PAGE 的 View ID 是否和菜单注册一致,主页面参数是否设置为 true。
FAQ
如何优化页面跳转时间
在页面跳转过程中,图片资源加载往往耗时最多,因此可以通过减少前期图片加载的时间来优化减少页面跳转时间,具体方法如下:
-
在OnStart阶段建议一次性加载第一帧页面显示所需的图片;对于序列帧场景,仅需加载第一帧的图片资源。
-
在OnResume阶段借助PostGraphicEvent方法延迟加载剩余的图片资源。注意,延迟访问是为了防止内存释放后的野指针问题,代码实现可参考下述示例:
涉及屏幕管理的相关接口说明
设置灭屏后的Slice的存活期,灭屏时长超过存活期时,再次亮屏会跳回默认界面,默认界面是VIEW_MAIN_SAMPLE。
表 1 ui_screennotify.h接口说明
接口名 |
说明 |
|---|---|
set_back_to_home_interval |
设置存活期时间,单位:s,默认是5秒。 设置灭屏后Slice的存活期,需要在Slice对应的Presenter的OnStart方法中调用,设置该Slice的灭屏后的存活时长。 |
get_back_to_home_interval |
获取存活期时间,单位:s。 |
设置某个Slice亮屏时间,超过设置时间后,屏幕灭屏。
表 2 power_display_service.h接口说明
接口名 |
说明 |
|---|---|
set_screen_set_keepon_timeout |
设置亮屏时间,单位:ms。 使用示例,如果需要设置某个Slice亮屏时间,可以在Slice对应的Presenter的OnStart方法里面添加如下代码。 const power_display_svr_api_t *display_api = power_display_svr_get_api(); if (display_api->get_screen_state() != SCREEN_ON) { display_api->turn_on_screen(); } //当设置的值是-1的时候,设置屏幕常亮 display_api->set_screen_set_keepon_timeout(-1); //设置屏幕亮屏5秒后灭屏 display_api->set_screen_set_keepon_timeout(5000); //设置立刻灭屏 set_screen_set_keepon_timeout(0); |
如何设置当前页面的按键事件响应机制
当前的MVP架构实现中已经配置了默认按键事件响应类KeyInputListener:
-
在AbilitySlice::OnResume中配置默认事件响应类。
-
在AbilitySlice::OnPause方法中清除事件响应类,保证在页面跳转的过程中不处理按键事件响应。
因此,有两种方法:
-
直接修改默认事件响应类KeyInputListener,根据Slice的不同来处理按键事件。
-
在Presenter::OnResume/OnPause方法或者SlicePage::OnResume/OnPause方法中,重新配置当前页面的自定义事件响应类。
如何自定义Slice的转场动效
自定义Slice的转场动效简单来说就是重写TransitionCallback类,不同的动画类型使用不同的静态注册方法。
以蜂窝转场动效HexagonsTransition为例:
-
根据转场的要求,明确是蜂窝页面以及目标页面是否需要截图。
由于蜂窝界面需要对内部的图标做变换,所以不需要截图;而目标页面是个整体,可以使用截图。
虽然转场使用截图会占用资源,但是性能会好,最终的选择需要结合效果、内存以及性能综合来看。
-
在“TransitionType.h”中添加新的转场类型:TRANSITION_HEXAGONS。
-
重写TransitionCallback类:class HexagonsTransition : public TransitionCallback
OnTransitionStart:动画开始阶段,对控件(Slice的截图控件或者root container)进行预处理。
TransitionAlg:动画进行阶段,对控件做matrix变换或者alpha处理,已达到转场的效果。
OnTransitionEnd: 动画结束阶段,重置状态。
-
静态注册转场类型,保证后续可以直接使用。
-
参数300:转场持续时间,单位:ms。
-
参数false:源界面不需要截图。
-
参数true:目标界面需要截图。
-
如何定制页面的滑动响应
如果当前页面配置支持跟手返回,系统会为当前页面配置跟手返回相应的滑动响应监听。
开发者也可以自定义滑动响应,如果系统默认已配置,则需要重写Presenter或者SlicePage的OnResume方法来覆盖系统默认配置。具体实现可以分为如下两个场景:
-
当前Slice无SlicePage:在Presenter的实现子类OnResume方法中自定义rootContainer_的跟手滑动监听。
view_->GetRootContainer()->SetOnDragListener(this)
-
当前Slice有SlicePage:在SlicePage的实现子类OnResume方法中自定义pageContainer_的跟手滑动监听。
pageContainer_->SetOnDragListener(dragListener)
Native应用开发Checklist
由于部分应用存在生命周期不合理,根容器使用不规范和冗余代码较多等问题,因此总结了以下Checklist,用户可在开发应用时对照检查:
-
检查并确认是否存在非UI线程修改UI控件的问题,常见的场景有:
-
在OSTimer的Callback中直接操作UI,建议使用Animator或者Task替换实现。
-
在MsgCenterNotifyProc中操作直接UI,建议配合PostGraphicEvent来实现,但需谨慎处理因延迟执行而导致的空指针或者野指针问题,可以借助shared_ptr和weak_ptr来解决,类似“如何优化页面跳转时间”章节的处理。
-
-
检查并删除冗余代码:
-
删除自实现应用中关于KeyActListener的配置,因为已在AbilitySlice::OnResume方法中配置,如需定制,可参考“SlicePage跳转方法说明”章节内容。
-
删除自实现应用中关于RootView::RemoveAll的调用,因为已在View::TearDownView方法中统一处理。注意,请谨慎删除非OnStop和销毁阶段的RemoveAll函数调用。
-
删除XXXPresenter冗余的继承关系,即未重写函数或者重写为空方法体的基类。
-
删除XXXView和XXXPresente关于生命周期的日志代码,因为AbilitySlice文件已经添加了通用Log。
-
删除OnStart/OnStop日志
-
删除OnPause/OnResume日志
-
-
删除XXXView和XXXPresenter关于基类方法的冗余调用,因为基类的方法体为空实现。
-
删除基类View<P>::OnStart|View<P>::OnStop冗余调用
-
删除基类Presenter<V>::OnStart|Presenter<V>::OnPause|Presenter<V>::OnResume|Presenter<V>::OnStop冗余调用
-
-
-
检查并确保无SlicePage的Slice应用使用GetRootContainer()->Add方法将页面内的控件添加到RootView控件树中,而不是使用RootView->Add方法。
-
检查并确保SlicePage使用GetSlicePageContainer()->Add方法将页面内的控件添加到RootView控件树中,而不是使用RootView->Add方法。
-
检查并确保OnPause阶段清空Focus,OnResume阶段恢复Focus,从而保证有动画转场过程中Focus时序不乱。
-
删除在页面销毁过程(OnStop或者析构)中的ClearFocus操作,因为AbilitySlice::OnPause方法会统一处理。
-
将RequestFocus操作放到Presenter或者SlicePage子类的OnResume方法中。
-
-
检查并确保View或者SlicePage子类在OnStart阶段已完成当前待显示页面的初始化,从而保证转场动画可以拿到完整内容做动画。
-
检查并确保Presenter或者SlicePage子类在OnResume阶段基于实时数据刷新当前页面的UI、启动页面开场动画、获取焦点、设置输入事件监听等,保证转场动画结束后才执行这些动作。
-
检查并确保View或者SlicePage子类的OnStart实现逻辑不依赖于Presenter子类的OnStart逻辑,因为Presenter::OnStart方法的执行晚于View或者SlicePage子类的OnStart方法执行。