跳转至

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 的典型运行链路如下:

  1. 编译阶段,CMakeLists.txt 收集 nativeui 下的 .cpp 文件。

  2. 启动阶段,REGIST_MENUREGIST_SLICEREGIST_SLICE_PAGE 生成的静态注册对象完成 Slice 和页面注册。

  3. 应用列表读取 NativeRegisterManager 中注册的菜单项,显示应用图标和名称。

  4. 用户点击菜单后,框架根据 View ID 创建对应 Slice,并通过 SlicePageFactory 创建主页面。

  5. 页面进入 OnStart 生命周期后,开发者创建 UI 控件并调用 AddViewToPageContainer 显示页面。

  6. 页面退出或销毁时,开发者释放自己创建的控件、定时器和图片资源。

如果注册代码被宏包裹,需要确认编译配置中启用了对应宏。例如 HelloWorld Demo 使用 SDK_DITING_COMMUNITY 控制注册是否生效。

创建开发者自己的应用

开发者可按以下步骤创建自己的应用,以下以 mydemo 为例。

  1. 创建源码和头文件目录。

    src/application/wearable/nativeapp/nativeui/mydemo
    
    src/application/wearable/nativeapp/nativeui/include/mydemo
    
  2. AppViewIDs.h 中新增唯一 View ID。

    VIEW_MY_DEMO,
    
  3. 创建 MyDemoView.h

    #ifndef MY_DEMO_VIEW_H
    
    #define MY_DEMO_VIEW_H
    
    
    
    #include "View.h"
    
    
    
    namespace OHOS {
    
    class MyDemoPresenter;
    
    class MyDemoView : public View<MyDemoPresenter> {};
    
    }
    
    
    
    #endif
    
  4. 创建 MyDemoPresenter.hMyDemoPresenter.cpp,并注册菜单入口。

    #include "NativeRegisterManager.h"
    
    #include "mydemo/MyDemoView.h"
    
    #include "mydemo/MyDemoPresenter.h"
    
    
    
    namespace OHOS {
    
    #ifdef SDK_DITING_COMMUNITY
    
    REGIST_MENU(VIEW_MY_DEMO, MyDemoView, MyDemoPresenter, EMPTY_ICON, EMPTY_ICON, "MyDemo");
    
    #endif
    
    }
    
  5. 创建 MyDemoPage.hMyDemoPage.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_);
    
    }
    
    }
    
  6. 确认新增 .cpp 文件位于 nativeui 目录下。nativeapp/CMakeLists.txt 会通过如下规则收集源码:

    file(GLOB_RECURSE SOURCES "${NativeApp}/nativeui/*.cpp")
    

编译与验证

完成 Demo 后,按工程既有构建流程编译 NativeApp。验证时重点检查以下内容:

  1. 编译阶段无头文件找不到、View ID 重复、类名不匹配等错误。

  2. 应用列表中出现 REGIST_MENU 注册的菜单名称。

  3. 点击菜单后进入 REGIST_SLICE_PAGE 注册的主页面。

  4. 页面显示 OnStart 中创建的控件。

  5. 退出页面后再次进入,控件显示正常且无异常日志。

如果 Demo 未出现在应用列表中,优先检查 REGIST_MENU 是否被当前编译宏包含,以及 Presenter.cpp 是否参与编译。

如果点击菜单后页面未显示,优先检查 REGIST_SLICE_PAGE 的 View ID 是否和菜单注册一致,主页面参数是否设置为 true

FAQ

如何优化页面跳转时间

在页面跳转过程中,图片资源加载往往耗时最多,因此可以通过减少前期图片加载的时间来优化减少页面跳转时间,具体方法如下:

  1. 在OnStart阶段建议一次性加载第一帧页面显示所需的图片;对于序列帧场景,仅需加载第一帧的图片资源。

  2. 在OnResume阶段借助PostGraphicEvent方法延迟加载剩余的图片资源。注意,延迟访问是为了防止内存释放后的野指针问题,代码实现可参考下述示例:

    {
    
        std::weakptr<bool> wk = isExits_; // isExits_为当前类的私有成员变量,类型为std::shared_ptr<bool>
    
        GraphicService::GetInstance()->PostGraphicEvent([this, wk](){
    
            if (wk.expired()) {
    
               return; //当前类已经销毁
    
            }
    
        });
    
    }
    

涉及屏幕管理的相关接口说明

设置灭屏后的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为例:

  1. 根据转场的要求,明确是蜂窝页面以及目标页面是否需要截图。

    由于蜂窝界面需要对内部的图标做变换,所以不需要截图;而目标页面是个整体,可以使用截图。

    虽然转场使用截图会占用资源,但是性能会好,最终的选择需要结合效果、内存以及性能综合来看。

  2. 在“TransitionType.h”中添加新的转场类型:TRANSITION_HEXAGONS。

  3. 重写TransitionCallback类:class HexagonsTransition : public TransitionCallback

    OnTransitionStart:动画开始阶段,对控件(Slice的截图控件或者root container)进行预处理。

    TransitionAlg:动画进行阶段,对控件做matrix变换或者alpha处理,已达到转场的效果。

    OnTransitionEnd: 动画结束阶段,重置状态。

  4. 静态注册转场类型,保证后续可以直接使用。

    REGIST_TRANSITION(TransitionType::TRANSITION_HEXAGONS, HexagonsTransition, 300, false, true);
    
    • 参数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,用户可在开发应用时对照检查:

  1. 检查并确认是否存在非UI线程修改UI控件的问题,常见的场景有:

    • 在OSTimer的Callback中直接操作UI,建议使用Animator或者Task替换实现。

    • 在MsgCenterNotifyProc中操作直接UI,建议配合PostGraphicEvent来实现,但需谨慎处理因延迟执行而导致的空指针或者野指针问题,可以借助shared_ptr和weak_ptr来解决,类似“如何优化页面跳转时间”章节的处理。

  2. 检查并删除冗余代码:

    1. 删除自实现应用中关于KeyActListener的配置,因为已在AbilitySlice::OnResume方法中配置,如需定制,可参考“SlicePage跳转方法说明”章节内容。

    2. 删除自实现应用中关于RootView::RemoveAll的调用,因为已在View::TearDownView方法中统一处理。注意,请谨慎删除非OnStop和销毁阶段的RemoveAll函数调用。

    3. 删除XXXPresenter冗余的继承关系,即未重写函数或者重写为空方法体的基类。

    4. 删除XXXView和XXXPresente关于生命周期的日志代码,因为AbilitySlice文件已经添加了通用Log。

      1. 删除OnStart/OnStop日志

      2. 删除OnPause/OnResume日志

    5. 删除XXXView和XXXPresenter关于基类方法的冗余调用,因为基类的方法体为空实现。

      1. 删除基类View<P>::OnStart|View<P>::OnStop冗余调用

      2. 删除基类Presenter<V>::OnStart|Presenter<V>::OnPause|Presenter<V>::OnResume|Presenter<V>::OnStop冗余调用

  3. 检查并确保无SlicePage的Slice应用使用GetRootContainer()->Add方法将页面内的控件添加到RootView控件树中,而不是使用RootView->Add方法。

  4. 检查并确保SlicePage使用GetSlicePageContainer()->Add方法将页面内的控件添加到RootView控件树中,而不是使用RootView->Add方法。

  5. 检查并确保OnPause阶段清空Focus,OnResume阶段恢复Focus,从而保证有动画转场过程中Focus时序不乱。

    1. 删除在页面销毁过程(OnStop或者析构)中的ClearFocus操作,因为AbilitySlice::OnPause方法会统一处理。

    2. 将RequestFocus操作放到Presenter或者SlicePage子类的OnResume方法中。

  6. 检查并确保View或者SlicePage子类在OnStart阶段已完成当前待显示页面的初始化,从而保证转场动画可以拿到完整内容做动画。

  7. 检查并确保Presenter或者SlicePage子类在OnResume阶段基于实时数据刷新当前页面的UI、启动页面开场动画、获取焦点、设置输入事件监听等,保证转场动画结束后才执行这些动作。

  8. 检查并确保View或者SlicePage子类的OnStart实现逻辑不依赖于Presenter子类的OnStart逻辑,因为Presenter::OnStart方法的执行晚于View或者SlicePage子类的OnStart方法执行。