对于应用层开发来说重要的是什么呢?当然就是UI框架的梳理。
为什么这么说呢?对于我们的项目来说,基本上就是芯片原厂源代码上进行修修补补,而其中最重要的是什么了,无非就是按键,LED,提示音,ANC,通透等等,而这些洛达已经为我们写好了接口,并且具备了一些基本功能,而我们需要做的就是把客户提供的UI(也就是上面说的按键等等)适配到代码中,就是在这些基础功能中,进行配置。
当你明白整个UI的流程之后,那么这个项目你掌握了70%。
我的个人博客
更多内容,请跳转我的个人博客
下面我会对框架做一个大致介绍,更多的自己理解。
系统架构
如图为UI系统架构图:
分为三个部分组成:
-
Earbuds UI reference design
这一部分就是我们主要的操作的部分
-
APPS
各个应用处理程序,比如battery,Homescreen,RHO,HFP等等
APPs实现了应用层的动作,按特性和功能进行组织,每个APP由一个或者多个activities组成,每个activity接收EVENT_GROUP_UI_SHELL_SYSTEM组的事件跟随管理创建,销毁,恢复,暂停,刷新和结果,
-
Event sender
事件发送器(key事件,BT事件,电池事件)
上面的事件在中间件或者硬件抽象层中注册回调,当回调被执行时,事件被发送APPS
比如按下了按键,那么就会通过发送器发送UI Shell事件到APPs,APPS做最后的处理
-
-
Middleware
-
UI shell
提供接口,允许开发人员创建应用程序不直接调用其他中间件或HAL模块的API
-
Manger
各种中间件
-
-
HAL
以key事件为例的处理流程图如下
Key事件发送器在HAL key中注册回调,它在被调用时向UI shell发送一个事件,接收到key事件发送方发送的事件后,UI shell将其发送给app
到这里我们对框架有了一个基础的认识,起码知道了一个按键事件处理流程,但是都是框架的内容,下面进一步去看看它们的内在。
UI shell设计
Activity
这可以说是理解洛达平台最重要的了,基本上所有的处理都是在各个不同的activity中进行。
activity表示UI shell的有限状态机中的状态,每个activity都有自己的回调函数来处理接收到的事件并决定要做什么
应用程序可以调用函数来启动/停止新活动,创建活动时,将其推入活动堆栈中,这个堆栈是什么呢?就是一个列表,没有什么复杂的。
通过下面的代码就可以看出,创建一个新的activity本质就是调用这个函数,然后添加到列表中。
bool ui_shell_activity_stack_add(ui_shell_activity_internal_t *activity, void *data, size_t data_len)
{
//省略代码
// Add activity in list
if (ret) {
activity->next = (*p_acti);
*p_acti = activity;
}
//省略代码
}
activity的类型
三种不同类型的活动可供使用
-
Per-proc activity
用于预处理的独特背景活动,每个项目只有一个预处理活动
具有最高优先级,将事件发送到UI shell时,其回调函数在所有其他活动之前执行
生命周期:除非断电过程中,否则此活动不会被中断或禁用
-
idle activity
每个应用程序都可以由零个,一个或者多个空闲活动
空闲活动处理公共消息并启动临时活动
生命周期:除非在断电过程中,否则此活动不会被中断或禁用
-
Transient activity
系统运行时由空闲活动或临时活动创建的活动
生命周期:在状态转换期间创建/销毁
临时活动的优先级高于空闲活动
activity的优先级
UI shell中有7中不同的优先级,这些活动的优先次序从高到低依次为:highest;high;middle;low;lowest;idle_top;and idle_background;
-
临时活动使用:highest;high;middle;low;lowest;
例如:用于显示电池状态LED的瞬态活动可能是低优先级,而用于显示搜索期间LED行为的不同瞬态活动可能是最高优先级
-
idle活动使用:idle_top;and idle_background;
通常,使用idle_top会有一个独特的主屏幕空闲活动,其他后台活动使用idle_background
activity堆栈
活动堆栈是一个容器,用于控制UIshell中的有限状态机(活动),一个项目只有一个活动堆栈的活动,并且所有应用程序共享同一个活动堆栈,最高优先级的活动位于堆栈的顶部,最低优先级的活动堆栈的底部
每个创建的活动都会根据其优先级放入活动堆栈中,新创建的活动高于具有相同优先级的其他活动
就像我在上面说的一样,就是一个列表而已
Event message
模块或者应用程序可以先UI shell发送事件,UI shell将事件分派给activity堆栈中已经存在的活动
事件可以分为内部事件(有UI shell预定义)和用户自定义事件(即由开发人员自己定义),内部事件和用户自定义都使用相同消息处理流
处理事件消息
使用循环来处理事件,当一个事件被发送到UI shell里,该事件将以下图的方式在activity堆栈中移动
当UI shell任务启动时,它会检查内部队列中的事件消息,如果队列中有事件消息,UI shell将通过推动堆栈处理该消息,如果队列中没有消息,UI shell任务将等待直到收到新消息,处理消息后,UI shell返回检查内部队列
下面我会分析分派的代码
消息遍历
这里是重点
预处理活动(前面说过,预处理优先级最高),活动堆栈中的临时和空闲活动一次性处理一条消息
事件消息由活动从上到下在活动堆栈中进行处理,当活动处理消息时,将调用活动中定义的回调函数,回调函数的返回值决定事件消息是否必须分派到下一个活动
值为true表示事件不得分派到下一个活动
值为false表示事件将被分派到下一个活动进行处理
bool ui_shell_activity_stack_traverse_stack(uint32_t event_group, uint32_t event_id, void *data, size_t data_len)
{
//注意这个返回值,如果为True,那么就停止循环,不需要传递给被的activity,如果为false就需要继续遍历,将事件分派给别的activity,这是个技巧,后面需要用到
bool ret = false;
ui_shell_activity_internal_t *temp_acti = s_acti_list;
//预处理活动先处理,需要处理就处理,不需要处理就不处理,看项目需要
if (s_pre_proc_acti) {
ret = s_pre_proc_acti->proc_event_func(&s_pre_proc_acti->external_activity, event_group, event_id, data, data_len);
}
// 如果预处理活动处理了,并返回True,那么其他的activity就不需要处理了
if (ret != true) {
//遍历活动堆栈,就是遍历列表
for (temp_acti = s_acti_list; temp_acti != NULL; temp_acti = temp_acti->next) {
UI_SHELL_LOG_MSGID_D("traverse_stack: %x", 1, temp_acti);
//分派给不同的activity
ret = temp_acti->proc_event_func(&temp_acti->external_activity, event_group, event_id, data, data_len);
//如果为true就不需要分派了
if (ret == true) { // true means the message is processed by the activity
break;
}
}
}
UI_SHELL_LOG_MSGID_D("traverse_stack end", 0);
return ret;
}
APIs
- UI shell
-
启动UI shell框架
ui_shell_status_t ui_shell_start(void)
-
停止UI shell框架并销毁在UI shell中使用的任何临时数据
ui_shell_status_t ui_shell_finish(void)
-
设置pre-proc活动的proc_event函数,这个函数应该在UI shell启动之前被调用
ui_shell_status_t ui_shell_set_pre_proc_func(ui_shell_proc_event_func_t proc_event)
-
- Activity
-
开始一个活动,proc_event是用于处理接收事件的回调函数
ui_shell_status_t ui_shell_start_activity(ui_shell_activity_t *self, ui_shell_proc_event_func_t proc_event, ui_shell_activity_priority_t priority, void *extra_data, size_t data_len)
-
销毁一个存在的活动
ui_shell_status_t ui_shell_finish_activity(ui_shell_activity_t *self, ui_shell_activity_t *target_activity)
-
销毁所有暂态活动,idle顶部活动成为活动堆栈中的顶部活动
ui_shell_status_t ui_shell_back_to_idle(ui_shell_activity_t *self)
-
将数据返回给启动当前活动的活动
ui_shell_status_t ui_shell_set_result(ui_shell_activity_t *self, void *data, size_t data_len)
-
请求UI shell向目标活动发送ON_REFRESH事件
ui_shell_status_t ui_shell_refresh_activity(ui_shell_activity_t *self, ui_shell_activity_t *target)
-
- Event
-
发送一个事件到UI shell,UI shell将在延迟之后将其分派给活动
ui_shell_status_t ui_shell_send_event(bool from_isr, ui_shell_event_priority_t priority, uint32_t event_group, uint32_t event_id, void *data, size_t data_len, void (*special_free_extra_func)(void), uint32_t delay_ms)
-
从事件列表中删除所有与事件组合事件id匹配的未处理事件
ui_shell_status_t ui_shell_remove_event(uint32_t event_group, uint32_t event_id)
-
- Allowance
-
允许请求到UI shell,UI shell将它发送给所有活动的活动,活动可以返回True立即允许它,或是使用ui_shell_grant_allowance()在以后允许它
ui_shell_status_t ui_shell_request_allowance(ui_shell_activity_t *self, uint32_t request_id)
-
如果一个活动在接收到EVENT_ID_SHELL_SYSTEM_ON_GET_ALLOWN不允许请求,它可以在稍后允许请求时调用该函数
ui_shell_status_t ui_shell_grant_allowance(ui_shell_activity_t *self, uint32_t request_id)
-
结语
到这里UI shell框架的分析就结束了,相信我们对洛达平台有了一个新的认识,尤其是对activity的模式有了一个深入的了解,相信开发起来会得心应手。