Activity 生命周期管理

当前实现更新

以下说明以当前代码实现为准:

  • Activity 必须在 egui_activity_init(self, core) 时直接绑定目标 core

  • activity.root_view 会在 init 阶段一起绑定到同一个 core

  • egui_activity_start(self, prev_activity) 要求 selfself.root_view 已经属于目标 core

  • 当前实现不会在启动时自动改写 self->coreroot_view 的归属关系

bind_core 相关旧接口已经删除,多屏场景下应在构造 Activity 时就传入目标屏幕的 core

Activity 是 EmbeddedGUI 中管理全屏页面的核心组件,借鉴了 Android Activity 的设计理念。每个 Activity 拥有独立的视图树和完整的生命周期,通过栈式管理实现页面间的前进与回退。

生命周期

Activity 的生命周期由 6 个回调函数组成,按顺序依次触发:

onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy

各状态含义:

状态

说明

onCreate

Activity 创建,初始化控件和布局,将视图添加到根视图

onStart

Activity 可见,root_view 设为 visible

onResume

Activity 获得焦点,允许处理触摸事件

onPause

Activity 失去焦点,禁止处理触摸事件

onStop

Activity 不可见,root_view 设为 hidden

onDestroy

Activity 销毁,从视图树和 Activity 栈中移除

状态枚举定义在 egui_activity.h 中:

enum
{
    EGUI_ACTIVITY_STATE_NONE = 0,
    EGUI_ACTIVITY_STATE_CREATE,
    EGUI_ACTIVITY_STATE_START,
    EGUI_ACTIVITY_STATE_RESUME,
    EGUI_ACTIVITY_STATE_PAUSE,
    EGUI_ACTIVITY_STATE_STOP,
    EGUI_ACTIVITY_STATE_DESTROY
};

Activity 结构体

struct egui_activity
{
    egui_dnode_t node;              // Activity 栈中的链表节点
    uint8_t state;                  // 当前状态
    uint8_t is_need_finish;         // 是否需要销毁

    egui_core_t *core;              // Activity 所属的 core

    egui_view_root_group_t root_view; // Activity 的根视图容器

    const egui_activity_api_t *api; // 虚函数表
};

虚函数表 egui_activity_api_t 定义了 6 个生命周期回调:

struct egui_activity_api
{
    void (*on_create)(egui_activity_t *self);
    void (*on_start)(egui_activity_t *self);
    void (*on_resume)(egui_activity_t *self);
    void (*on_pause)(egui_activity_t *self);
    void (*on_stop)(egui_activity_t *self);
    void (*on_destroy)(egui_activity_t *self);
};

Activity 栈管理

EmbeddedGUI 使用双向链表维护 Activity 栈。核心 API 如下:

// 启动新 Activity(prev_activity 为前一个 Activity,可为 NULL)
void egui_activity_start(egui_activity_t *self, egui_activity_t *prev_activity);

// 结束 Activity(触发 onPause -> onStop -> onDestroy)
void egui_activity_finish(egui_activity_t *self);

// 检查 Activity 是否正处于切换流程中
int egui_activity_check_in_process(egui_activity_t *self);

// 通过子视图查找所属 Activity
egui_activity_t *egui_view_get_activity(egui_view_t *view);

启动流程:调用 egui_activity_start() 时,框架会先 pause 当前 Activity,然后对新 Activity 依次调用 onCreate -> onStart -> onResume

结束流程:调用 egui_activity_finish() 时,框架标记 is_need_finish,在 onStop 中自动触发 onDestroy,同时恢复前一个 Activity。

切换动画配置

Activity 切换支持 4 个动画槽位,分别控制启动和结束时新旧 Activity 的过渡效果:

// 设置启动动画:当前 Activity 的进入动画 + 前一个 Activity 的退出动画
void egui_activity_set_start_anim(
    egui_activity_t *self,
    egui_animation_t *open_anim,   // 当前 Activity 从右侧滑入
    egui_animation_t *close_anim   // 前一个 Activity 向左侧滑出
);

// 设置结束动画:前一个 Activity 的恢复动画 + 当前 Activity 的退出动画
void egui_activity_set_finish_anim(
    egui_activity_t *self,
    egui_animation_t *open_anim,   // 前一个 Activity 从左侧滑入
    egui_animation_t *close_anim   // 当前 Activity 向右侧滑出
);

典型的水平滑动切换配置:

// 启动时:新页面从右侧滑入
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_start_open_param,
    EGUI_CONFIG_SCREEN_WIDTH, 0, 0, 0);  // from_x, to_x, from_y, to_y
egui_animation_translate_t anim_start_open;

// 启动时:旧页面向左侧滑出
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_start_close_param,
    0, -EGUI_CONFIG_SCREEN_WIDTH, 0, 0);
egui_animation_translate_t anim_start_close;

// 结束时:前一个页面从左侧滑入
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_finish_open_param,
    -EGUI_CONFIG_SCREEN_WIDTH, 0, 0, 0);
egui_animation_translate_t anim_finish_open;

// 结束时:当前页面向右侧滑出
EGUI_ANIMATION_TRANSLATE_PARAMS_INIT(anim_finish_close_param,
    0, EGUI_CONFIG_SCREEN_WIDTH, 0, 0);
egui_animation_translate_t anim_finish_close;

如果需要垂直滑动切换,将 x 参数改为 0,y 参数改为 EGUI_CONFIG_SCREEN_HEIGHT 即可。

自定义 Activity

自定义 Activity 需要:

  1. 定义继承结构体(包含 egui_activity_t base 和自定义字段)

  2. 重写需要的生命周期回调

  3. 构建自定义的虚函数表

  4. 在 init 函数中调用父类 init 并替换虚函数表

以 HelloActivity 中的 egui_activity_test_t 为例:

// 1. 定义结构体
struct egui_activity_test
{
    egui_activity_t base;           // 继承基类

    int index;
    char label_str[20];

    egui_view_linearlayout_t layout_1;
    egui_view_label_t label_1;
    egui_view_button_t button_1;    // "Next" 按钮
    egui_view_button_t button_2;    // "Finish" 按钮
    egui_view_button_t button_3;    // "Dialog" 按钮
};

// 2. 重写 on_create:初始化控件并添加到 Activity
void egui_activity_test_on_create(egui_activity_t *self)
{
    egui_activity_test_t *local = (egui_activity_test_t *)self;
    egui_activity_on_create(self);  // 调用父类

    // 初始化布局和控件...
    egui_view_linearlayout_init((egui_view_t *)&local->layout_1, egui_activity_get_core(self));
    // ... 设置按钮回调 ...
    egui_activity_add_view(self, (egui_view_t *)&local->layout_1);
}

// 3. 构建虚函数表(只替换需要修改的回调)
static const egui_activity_api_t egui_activity_test_t_api_table = {
    .on_create  = egui_activity_test_on_create,  // 自定义
    .on_start   = egui_activity_on_start,        // 使用默认
    .on_resume  = egui_activity_on_resume,
    .on_pause   = egui_activity_on_pause,
    .on_stop    = egui_activity_on_stop,
    .on_destroy = egui_activity_test_on_destroy,  // 自定义
};

// 4. 初始化函数
void egui_activity_test_init(egui_activity_t *self, egui_core_t *core)
{
    egui_activity_init(self, core);       // 调用父类 init
    self->api = &egui_activity_test_t_api_table;  // 替换虚函数表
}

资源管理

Activity 应用的资源通过 app_resource_config.json 配置,包括字体和图片。详细说明参见资源管理章节。

HelloActivity 完整流程

example/HelloActivity/uicode_disp0.c 展示了完整的 Activity 管理流程:

void uicode_disp0_init(egui_core_t *core)
{
    EGUI_UNUSED(core);

    // 1. 初始化 Dialog 动画(底部上滑)
    egui_animation_translate_init(EGUI_ANIM_OF(&anim_dialog_start));
    egui_animation_translate_params_set(&anim_dialog_start, &anim_dialog_start_param);
    egui_animation_duration_set(EGUI_ANIM_OF(&anim_dialog_start), 500);
    // ... 初始化其他动画 ...

    // 2. 初始化 Dialog,并把动画绑定到 Dialog 对象本身
    egui_dialog_test_init((egui_dialog_t *)&dialog, core);
    egui_dialog_set_anim(
        (egui_dialog_t *)&dialog,
        EGUI_ANIM_OF(&anim_dialog_start),
        EGUI_ANIM_OF(&anim_dialog_finish)
    );

    // 3. 启动第一个 Activity
    // uicode_start_next_activity() 内部会对待启动 Activity 调用:
    // egui_activity_set_start_anim(...)
    // egui_activity_set_finish_anim(...)
    // egui_activity_start(...)
    uicode_start_next_activity(NULL);

    // 4. 初始化 Toast
    egui_toast_std_init((egui_toast_t *)&toast, core);
    egui_toast_set_as_default((egui_toast_t *)&toast);
}

Activity 的内存分配支持两种模式:

  • 静态分配:预分配固定数量的 Activity 数组,适合资源受限场景

  • 动态分配:通过 egui_api_malloc 动态创建,需在 onDestroy 中释放

// 静态分配模式
#define ACTIVITY_ARRAY_SIZE 3
static egui_activity_test_t activity_test_array[ACTIVITY_ARRAY_SIZE];

egui_activity_test_t *uicode_get_empty_activity(void)
{
    if (activity_index >= ACTIVITY_ARRAY_SIZE)
        return NULL;
    return &activity_test_array[activity_index++];
}

相关文件

  • src/app/egui_activity.h - Activity 基类定义

  • src/app/egui_activity.c - Activity 基类实现

  • src/core/egui_core.h - Activity 栈管理 API

  • example/HelloActivity/ - 完整示例

Toast Helper

Activity 也提供了基于对象的 Toast 快捷接口:

void egui_activity_show_toast_info(egui_activity_t *self, const char *text);
void egui_activity_show_toast_info_with_duration(egui_activity_t *self, const char *text, uint16_t duration);

典型用法:

static void button_next_click_cb(egui_view_t *self)
{
    egui_activity_t *activity = egui_view_get_activity(self);

    if (activity != NULL)
    {
        egui_activity_show_toast_info(activity, "Open next page");
    }
}

Core 解析说明

当前实现里,egui_activity_start(self, prev_activity) 只使用 self 自身已经绑定的 core。 如果 selfself.root_view 不属于传入的目标 core,启动流程会直接返回,不会做自动补绑,也不依赖全局 active core。

egui_activity_show_toast_info(...) / egui_activity_show_toast_info_with_duration(...) 这类对象式 helper, 则会通过 activity 自身绑定的 core 去查找默认 Toast。 因此只要 Activity 在初始化时已经带上目标屏幕的 core, 后续显示 Toast 时就不需要再额外传 core

多屏场景下更应该遵守这个约束:创建对象时就明确绑定到目标屏幕的 core,而不是指望启动阶段推断或修正归属关系。