动态应用

FAQ1 动态应用如何添加开机初始化

调用初始化注册宏

动态应用如果存在必须在gui_thread_entry运行前完成的初始化逻辑,可参照reader_bookshelf.creader_init的调用方式处理。该方式主要用于开机初始化,例如提前完成开机动画资源配置、图片解码器初始化等准备工作。

static void reader_init(void)
{
    app_pm_anim_set_resource((anim_img_t *)pm_anim_fn, sizeof(pm_anim_fn) / sizeof(anim_img_t));
    epbub_decoder_img_init();
    LOG_I("%s", __func__);
}

/* It must be initialized before the gui_thread_entry runs. */
#ifdef BSP_USING_PC_SIMULATOR
    INIT_PRE_APP_EXPORT(reader_init);
#else
    DLMODULE_INIT_DEF(reader_init);
#endif
  • PC模拟器场景:使用INIT_PRE_APP_EXPORT(reader_init),随主工程开机初始化流程执行。

  • 板级动态模块场景:使用DLMODULE_INIT_DEF(reader_init),在动态模块加载初始化阶段执行。


FAQ2 动态应用如何判断是否会被编译装入

配置readme.ini依赖规则

Solution通过Butterfli工具编译动态应用,工具会根据当前板级工程HCPU的.config配置判断是否装入动态应用:

  • 动态应用根目录存在readme.ini时,Butterfli会读取其中定义的分辨率和依赖宏,与.config中的配置进行匹配,匹配成功才装入。

  • 动态应用根目录不存在readme.ini时,默认认为该动态应用与当前配置兼容,直接装入。

readme.ini中常用配置项如下:

配置项

说明

RESOLUTION_SET

定义支持的分辨率,RES_NUM=0表示支持所有分辨率。

MACRO_DEPEND

通用依赖宏,板级和模拟器共享,例如APP_DLMODULE_APP_USED

BOARD_MACRO_DEPEND

板级专用依赖宏,例如硬件相关宏USING_EZIPA_DEC

SIMU_MACRO_DEPEND

模拟器专用依赖宏,无依赖时设置MACRO_NUM=0

更完整说明可参考:动态应用指南


FAQ3 动态应用的数据如何保存

使用动态应用NVM接口

动态应用的数据应通过动态应用NVM接口读写,数据会与应用绑定;删除应用时,框架会自动清理关联数据。

size_t app_nvm_read(const char* key_name, const void* data, size_t length);
rt_err_t app_nvm_write(const char* key_name, const void* data, size_t length);
rt_err_t app_nvm_del(const char* key_name);

注意: key_name必须和动态应用的ID保持一致,包括动态appwfaod,否则可能导致数据无法按预期读写或清理。


FAQ4 动态应用的注册ID、目录名和资源名有什么要求

保持ID和资源规则一致

动态应用需要保证注册ID、动态应用目录名、_MODULE_NAME_宏定义保持一致。动态应用在模拟器调试、内置编译以及文件系统分目录访问资源时都会依赖该名称。

#define _MODULE_NAME_ "dyn_plane"
#include "app_module.h"

APPLICATION_REGISTER(app_get_strid(key_plane, "Plane"), NULL, "dyn_plane", sizeof(plane_play_t))

常见规则如下:

  • 动态应用注册ID必须和动态应用目录、_MODULE_NAME_一致。

  • 动态appaodwf缩略图名称必须固定为tn.png

  • 动态应用多语言接口app_get_strapp_get_str_from_id只会访问动态应用目录下resource/lang/multi_language_table.xlsx生成的词条。


FAQ5 动态应用无法显示如何排查

按日志和文件完整性排查

动态应用无法显示时,可按照以下步骤排查,原始说明可参考:动态应用指南-无法显示问题

  1. 搜索日志中关键字module,查看是否存在can't find ... in kernel symbol table的记录。

    • 若存在,说明有接口未在主代码中导出,需在app_rtm_export.c中对应声明导出。

    • 例如日志提示动态应用找不到custom_ring_play

      can't find custom_ring_play in kernel symbol table
      

      则需要在主代码的solution/framework/gui_fwk/dyn_fwk/export/app_rtm_export.c中导出该接口。若已有对应头文件,先包含头文件,再增加RTM_EXPORT

      #include "ring.h"
      RTM_EXPORT(custom_ring_play);
      

      如果没有合适的头文件,也可以先声明函数原型,再导出:

      void custom_ring_play(const char *file_path);
      RTM_EXPORT(custom_ring_play);
      

      修改后需要重新编译主工程,使该符号进入主代码的动态模块符号表。

  2. 若未找到can't find相关日志,则需检查:

    • 应用文件是否完整,installer目录是否包含xxx_tn.binxxx.soxxx_res.soxxx.desc

    • 文件存放目录是否正确,例如应放在root分区,却误放到/dyn分区等。


FAQ6 动态应用发生死机如何分析

加载动态应用符号表分析调用栈

动态应用发生死机时,由于动态加载代码的执行地址经过重定位,使用Trace32恢复现场时需要额外加载动态应用符号表,才能看到完整调用栈。原始说明可参考:动态应用指南-死机分析

处理流程如下:

  1. 获取模块地址(两种方式)

    • 可从串口日志中搜索open module关键字,最后一个关键字后的地址就是模块地址。

    • 固件中也会保存最后打开的模块地址,例如cur_app_modulecur_wf_modulecur_aod_module

  2. 获取入口地址。

    • 将模块地址转换为struct rt_dlmodule *类型,读取其中的entry_addr

      • ((struct rt_dlmodule *) cur_app_module)->entry_addr

      • ((struct rt_dlmodule *) cur_wf_module)->entry_addr

      • ((struct rt_dlmodule *) cur_aod_module)->entry_addr

      如这里取出的地址是:0x6036dc38

  3. 加载符号表。

    • Butterfli编译完成后,会在文件系统路径中生成zip_output_symbolbak目录,其中包含xxx.so.nostrip符号表文件。

    • 使用Trace32命令按入口地址加载,例如:D.Load sport.so.nostrip 0x6036dc38 /nocode /noclear

加载符号表后,即可根据完整调用栈定位动态应用中触发异常的具体函数和代码位置。