MTK内存管理篇
MTK 手机用的操作系统是 nucleus, 这是一个RTOS(实时操作系统),这个RTOS本身是不带内存管理功能,所以MTK自己写的内存管理。(nucleus在系统初始化完毕时,会调用Application_Initialize,参数就是可使用内存的起始地址)
大体上来分,MTK内存可以分为3种:
- control buffer
平常使用的OslMalloc就是这个内存。
这个内存内部实现是按块来划分的(pool),具体的配置可custom_config.c 文件里的custom_config_ctrl_buff_info()里看到里面的size 指定了这个块的大小,no_of_buff 指定了有多少个这样的块。
按块来管理内存,可以有效地控制内存的碎片,管理也相对简单,可能会造成内存的一些浪费对于手机这种需要长时间运行不重起的设备来说,还是很有必要的。
MTK 默认最大块的大小为 2048byte,也就是2k。这就是说用OslMalloc 分配内存默认最大能分配到2k,这个可以看custom_config_ctrl_buff_info()配置可以看到。可以通过修改里面的配置来改变这个值,不过一般不这么做,因为MTK提供了其他的内存管理方式
- system buffer
system buffer 平时我们用不到,听名字也是系统使用的。
主要是提供 run-time usage,是一块 semi-static memory(什么意思?)比如 block of task, task stack ,control block of control buffer ,buffer pool等等在 custom_config.c里面配置 ,主要有两个宏 GLOBAL_MEM_SIZE 和 GLOBAL_DEBUG_MEM_SIZE两个 static 数组 static kal_uint32 System_Mem_Pool[GLOBAL_MEM_SIZE/sizeof(kal_uint32)]; 和static kal_uint32 Debug_Mem_Pool[GLOBAL_DEBUG_MEM_SIZE/sizeof(kal_uint32)];
为了满足时间要求,也就是要求快速分配,系统内存又分为 internal system memory 和 system memory前者link 到 internal SRAM ,后者link 到 external SRAM
- app buffer
app的内存是使用通过MTK 提供的一种ADM(application dynamic memory)机制来实现,ADM 主要的功能是通过管理一个数组来实现内存的分配。
ADM 也是通过内存块(pool)来实现的,具体无法看到其代码。app通过这个adm这个机制,可以更加灵活的使用内存,比如分配大内存(大于2k)等等
主要函数
创建 adm
KAL_ADM_ID kal_adm_create(void *mem_addr, kal_uint32 size, kal_uint32 *subpool_size, kal_bool islogging);
删除函数
kal_status kal_adm_delete(KAL_ADM_ID adm_id);
分配函数
extern void *kal_adm_internal_alloc(KAL_ADM_ID adm_id, kal_uint32 size, char *filename, kal_uint32 line);
#define kal_adm_alloc(adm_id, size) kal_adm_internal_alloc(adm_id, size, __FILE__, __LINE__)
释放函数
extern void kal_adm_free(KAL_ADM_ID adm_id, void *mem_addr);
在 MTK 内存管理简单总结 中,大体说了MTK的三种内存分配方式,对于第三种,也就是app buffer,是比较丰富的一种。
在 MTK 平台中也有许多具体的实现。在代码里搜索一下 kal_adm_create 就可以发现有许多地方使用了。
看一个比较典型的使用:
在文件app_mem.c里,有两个memory pool,一个是用于应用之间共享内存,另一个是用于屏幕内存。
第一种内存,主要是用于各种应用之间共享内存(以下简称ASM),这样可以节省内存,MTK实现了一种机制,可以在多个应用之间共享内存,当当前应用想获得的共享内存不足时,MTK会通知后台应用释放相应的内存。这套机制在AppMemMgr.c里面实现。
先看一下初始化该内存次池函数
void applib_mem_ap_init(void (*stop_finish_callback_by_MMI)(kal_uint32 app_id, kal_uint32 string_id, kal_bool result))
这个函数带有一个参数,这个参数是一个函数指针,该回调函数有3个参数,app_id,(应用id),string_id 和 result。
这个回调函数比较特别,是当一个后台应用 被 要求释放内存,释放完毕后调用的。
为什么要搞这么一个函数,因为一些应用比较复杂,释放内存的同时需要关闭一些资源,而这些动作是异步的,等这些异步发的操作多完成时,调用一些函数,告诉ASM,内存释放完毕。
具体实现:通过 调用 kal_adm_create 来创建一个内存池,然后保存了一些回调函数,没有什么特别的地方,内存池的大小 是 APPLIB_MEM_AP_POOL_SIZE 来确定的,可以通过修改 app_asm_pool_union 来修改内存池的大小。
应用分配内存
void *applib_mem_ap_alloc(kal_uint32 app_id, kal_uint32 mem_size)
应用通过上面的函数来获得ASM的内存,参数一 app_id,是当前分配内存的id,这个id需要自己定义,并且注册(下文说明),
参数二是实际需要分配的内存大小。
具体实现:先mem_size 进行了处理,让其四字节对齐。然后通过 kal_adm_alloc 获得内存,不过这个内存加上了一个头结构和尾结构,(头和尾都加入了特殊字符,再释放时进行检查,这个可以判断内存是否越界)。然后把这个内存插入到list的头部。
昨天说到了内存的分配。下面看一下内存释放
主要进行了3步:
static void applib_mem_ap_free_int(void *mem_ptr)
{
/*—————————————————————-*/
/* Local Variables
/*—————————————————————-*/
applib_mem_header_struct *header, *prev_node, *remove_node;
applib_mem_footer_struct *footer;
/*—————————————————————-*/
/* Code Body
/*—————————————————————-*/
if (g_applib_mem_cntx.app_pool_id) /* Normal mode */
{
ASSERT(mem_ptr && APPLIB_MEM_ALIGNED_4(mem_ptr));
header = ((applib_mem_header_struct*) mem_ptr) – 1;
footer = (applib_mem_footer_struct*) (((char*)mem_ptr) + header->chunk_size);
ASSERT(APPLIB_MEM_COMP_PATTERN(header->guard_pattern, APPLIB_MEM_HEADER_PATTERN1) &&
APPLIB_MEM_COMP_PATTERN(footer->guard_pattern, APPLIB_MEM_FOOTER_PATTERN1));
/*
* Remove the block from linked list
*
* It is not a fast algorithm, we can improve it by using double linked list,
* but we choose simpler design because
* 1. Typically total allocation count is small
* 2. We don’t want to increase space overheads
* 3. We don’t want to access KAL ADM internal data structure
*/
prev_node = &g_applib_mem_cntx.app_head;
ASSERT(prev_node->next);
for (remove_node = prev_node->next;
remove_node;
prev_node = remove_node, remove_node = prev_node->next)
{
if (remove_node == header)
{
break;
}
}
ASSERT(remove_node);
prev_node->next = remove_node->next;
/* Set guard pattern */
APPLIB_MEM_SET_PATTERN(header->guard_pattern, APPLIB_MEM_HEADER_PATTERN2);
APPLIB_MEM_SET_PATTERN(footer->guard_pattern, APPLIB_MEM_FOOTER_PATTERN2);
/* Release the block */
#ifdef APPLIB_MEM_USE_ADM
kal_adm_free(g_applib_mem_cntx.app_pool_id, header);
#else
free(header);
#endif
ASSERT(g_applib_mem_cntx.app_alloc_count > 0);
g_applib_mem_cntx.app_alloc_count–;
}
else /* Full pool mode */
{
ASSERT(mem_ptr == g_applib_mem_ap_pool && g_applib_mem_cntx.app_alloc_count == 1);
g_applib_mem_cntx.app_alloc_count = 0;
g_applib_mem_cntx.app_id_of_full_pool = APPLIB_MEM_AP_ID_DUMMY; /* 0 */
#ifdef APPLIB_MEM_USE_ADM
g_applib_mem_cntx.app_pool_id = kal_adm_create(
g_applib_mem_ap_pool,
APPLIB_MEM_AP_POOL_SIZE,
(kal_uint32*) g_applib_mem_pool_chunk_size,
KAL_FALSE);
#else /* APPLIB_MEM_USE_ADM */
g_applib_mem_cntx.app_pool_id = APPLIB_DUMMY_POOL_ID;
#endif /* APPLIB_MEM_USE_ADM */
}
}
- 取得内存的头部和尾部,(调试版本可以判断内存是否越界)
- 从链表中删除这个节点
- 调用 kal_adm_free 释放内存
在MTK 内存管理简单总结 2 提到调用 applib_mem_ap_alloc 分配内存是需要一个应用id,这个id是需要自己增加,
而且在调用这个函数之前必须 调用 applib_mem_ap_register 注册这个id。需要注意的是最后一个参数,是一个回调函数,这个回调函数是在共享内存不够使用时,ASM会调用这个函数,告诉应用需要释放共享内存,供其它应用使用。增加 id 在 app_mem.h 的 applib_mem_ap_id_enum 里面,只要添加一个id就可以。
同样 屏幕内存也是通过ADM来管理,屏幕内存 是用来 创建 layer 用的,在MTK的某个版本开始,创建layer的内存是有要求的,
需要applib_mem_screen_alloc 分配的内存。
在 MTK 内存管理简单总结 4 提到,当内存不足时,可以调用 mmi_frm_appmem_prompt_to_release_mem 来显示后台应用,让用户去选择关闭后台应用释放内存,下面看一下具体流程:当选者某一后台应用,选择stop 按键时,会触发 mmi_frm_appmem_prompt_screen_stop_highlighted 函数 (触发过程跟MTK的UI事件机制想关,以后分析)。
mmi_frm_appmem_prompt_screen_stop_highlighted
该函数没有做什么,只是通过选择list 选项 来获得应该被stop的后台应用id。接着进入到确认界面 ( mmi_frm_appmem_entry_stop_confirm_screen ), 当用户选择yes后,进入到mmi_frm_appmem_stop_confirm_screen_yes 函数,这个函数执行真正的释放内存动作
static void mmi_frm_appmem_stop_confirm_screen_yes(void)
{
g_mmi_frm_appmem_prompt.stop_state = MMI_FRM_APPMEM_STOP_STATE_WAITING;
//调用相应应用注册的函数,通知应用却释放内存
//这个就是为什么应用要用共享内存前,需要调用 applib_mem_ap_register 来注册的原因
applib_mem_ap_stop_app_by_MMI_task(g_mmi_frm_appmem_prompt.app_id_confirm_to_stop);
MMI_TRACE(MMI_FW_TRC_G1_FRM, MMI_FRM_ASM_STOP_CONFIRM_YES,
g_mmi_frm_appmem_prompt.app_id_confirm_to_stop,
g_mmi_frm_appmem_prompt.stop_state);
//判断释放是否完成,如果没有完成,那么显示正在处理界面
//这里不直接认为回调完应用的就直接认为内存释放了,是因为
//应用stop的过程有可能是一个异步操作,这一点需要特别注意
if (g_mmi_frm_appmem_prompt.stop_state != MMI_FRM_APPMEM_STOP_STATE_FINISHED)
{
/* mmi_frm_appmem_stop_finished_handler() was not invoked inside
applib_mem_ap_stop_app_by_MMI_task(), we display progressing
screen to wait for mmi_frm_appmem_stop_finished_handler() */
mmi_frm_appmem_entry_progressing_screen();
}
}
注意点:
相应的app 在释放完毕内存后,一定要applib_mem_ap_notify_stop_finished 来通知 ASM内存管理器,内存释放成功或者失败。
该函数最后会调用 mmi_frm_appmem_stop_finished_handler 来通知 内存管理器,内存释放结果。
这套机制,回调函数调来调去,一开始让人有点晕,不过这么设计主要是考虑应用的异步性,有些应用停止是一些异步动作,需要等到异步动作结束才能知道最后结果。
MTK MMI event总结
在MTK MMI 里面有各种event,最常见的跟交互相关的 按键 event,触摸屏 event。跟各种具体事件比如电话event,短信event,电量event,信号量event,timer event等等。
MTK 都有相应的处理方式,跟交互相关的按键(KeyBrd.c)和触摸(TouchScreen.c) 各有一套自己的机子,timer event 已经在 MTK timer 小结 3 介绍过,还有一套机制就是处理各种其他事件,它主要分为普通事件interrupt event,interrupt event 主要用于需要弹出的对话框的event。
常用事件机制。做过MTK开发的童鞋应该知道要接受 MTK L4 层的消息,用一个注册函数 SetProtocolEventHandler,注册一个event 的 处理函数。比如短信,电话,电池电量,信号量等等都是通过 这个函数来注册消息。当L4 层,处理完封装事件后,就会把这个event 发送到MMI task 里来(消息发送可以看 MTK task 小结 5 ),然后MMI task 通过这个 event 机制,找到相应的处理函数,进行处理。实现这种机制,也是考虑到零活性和扩张性。如果都在MMI task 里面,用 switch case 来处理,那就很疯狂了,长度不说,写个应用,定义个消息,都要去改MMI task,所以这个机制实现虽然比较简单,但是还是很有必要的。
昨天也说到,这个机制的 event 主要有两种,普通event 和 intrrupt event(中断事件),这些主要是一些需要中断当前应用的事件(主要是看那些弹出框),比如电话,有些应用需要提前处理这个消息,还要处理 这个事件处理完毕后的处理。还是看代码吧
在MMI task 最后,会调用函数 ProtocolEventHandler,这个函数是就是找相应的event 的相应处理函数(平台不一样,可能实际的函数名字有些区别,但是流程基本上一样的,我这里被 #define 到 mmi_frm_execute_current_protocol_handler)。
// MsgStruct 是 具体事件的 消息体
void mmi_frm_execute_current_protocol_handler(U16 eventID, void *MsgStruct, int mod_src, void *peerBuf)
{
U16 count = 0;
PsExtPeerFuncPtr currFuncPtr = NULL;
U8 interrup_result = MMI_FALSE; /* False not handle interrupt, True will handle */
MMI_BOOL query_result = MMI_FALSE, execute_result = MMI_FALSE;
interrupt_event_hdlr int_func = NULL, post_int_func = NULL;
mmi_frm_int_event_type current_frm_int_event;
{
// 遍历 protocolEventHandler 查找 是否有event 注册了回调函数
// 虽然这个方法感觉比较笨,就一个数组,实际上对速度没有什么影响
for (count = 0; count < maxProtocolEvent; count++)
{
if (protocolEventHandler[count].eventID == eventID)
{
//找到 处理函数
currFuncPtr = (PsExtPeerFuncPtr) protocolEventHandler[count].entryFuncPtr;
// 这个地方时,一个优化
// 处理的事情就是 把刚才现在处理的 event id 往前移动一个单位
// 这是考虑到 程序的局部性原理, 这个优化还是很有必要的
if (count > 0)
{
protocolEventHandler[count].eventID = protocolEventHandler[count – 1].eventID;
protocolEventHandler[count].entryFuncPtr = protocolEventHandler[count – 1].entryFuncPtr;
protocolEventHandler[count – 1].eventID = eventID;
protocolEventHandler[count – 1].entryFuncPtr = (PsFuncPtr) currFuncPtr;
}
break;
}
}
}
// 这个就是查找是否是中断事件
current_frm_int_event = mmi_frm_interrupt_event_converter(eventID, MsgStruct);
if (current_frm_int_event > 0)
{
// 查看改 中断事件是否有 注册 提前处理函数
query_result = mmi_frm_query_interrupt_event_information(current_frm_int_event, &int_func, &post_int_func);
}
// 如果有 调用该函数
if (query_result && int_func)
{
/* New interruption mechanism */
execute_result = (*int_func)(current_frm_int_event);
}
//根据 event 相应的回调函数 以及 前面处理的结果,来觉得是否处理该事件
// 要注意的是,如果如果 interrput 提前处理函数 返回 true,那么 这里就不会执行
if ((currFuncPtr) && (!interrup_result) && (!execute_result))
{
(*currFuncPtr) (MsgStruct, mod_src, peerBuf);
}
else
{
MMI_TRACE(MMI_FW_TRC_G1_FRM, MMI_FRM_INFO_EVENT_EXECURPTO_NO_HDLR, eventID);
}
// interrupt event 最后处理函数
if (query_result && post_int_func)
{
execute_result = (*post_int_func)(current_frm_int_event);
}
}
看完这个,其实觉得挺简单的,对吧。就是一个数组,数组里关联了 event id 和 对应的处理函数。
还有就是增加了一种 intrrupt event。可以注册这种event 的 pre_handler and post_handler。这样如果需要,可以提前做处理或者不响应该事件。
普通 event 的执行过程。中间还有几个函数没有介绍,这里简单介绍一下
// 这个函数就是简单把 L4c 的interrupt消息,转换成 MMI 层interrupt的消息
static mmi_frm_int_event_type mmi_frm_interrupt_event_converter(U16 event_id, void *msg)
{
mmi_frm_int_event_type frm_interrupt_event = 0;
switch (event_id)
{
// GPIO 消息
case PRT_EQ_GPIO_DETECT_IND:
{
mmi_eq_gpio_detect_ind_struct *gpio_detect_ind = (mmi_eq_gpio_detect_ind_struct *)msg;
switch (gpio_detect_ind->gpio_device)
{
// 翻盖关闭
case EXT_DEV_CLAM_CLOSE:
{
frm_interrupt_event = MMI_FRM_INT_CLAM_CLOSE;
break;
}
// 翻盖打开
case EXT_DEV_CLAM_OPEN:
{
frm_interrupt_event = MMI_FRM_INT_CLAM_OPEN;
break;
}
// 耳机插入 拔出
case EXT_DEV_EARPHONE:
{
if (gpio_detect_ind->on_off == 1)
{
frm_interrupt_event = MMI_FRM_INT_EARPHONE_PLUG_IN;
}
else
{
frm_interrupt_event = MMI_FRM_INT_EARPHONE_PLUG_OUT;
}
break;
}
}
break;
}
// 充电信息,
case PRT_BATTERY_STATUS_IND:
{
mmi_eq_battery_status_ind_struct *battery_status_ind = (mmi_eq_battery_status_ind_struct*)msg;
frm_interrupt_event = mmi_frm_get_frm_int_event_macro(
battery_status_ind->battery_status,
mmi_frm_int_event_battery_table,
sizeof(mmi_frm_int_event_battery_table) / sizeof(mmi_frm_int_event_battery_table[0]));
/* For low battery indication */
if (battery_status_ind->battery_status == PMIC_VBAT_STATUS)
{
if (battery_status_ind->battery_voltage == BATTERY_LOW_WARNING)
{
frm_interrupt_event = MMI_FRM_INT_BATTERY_LOW_WARNING;
}
else if (battery_status_ind->battery_voltage == BATTERY_LOW_TX_PROHIBIT)
{
frm_interrupt_event = MMI_FRM_INT_BATTERY_LOW_TX_PROHIBIT;
}
}
break;
}
case MSG_ID_TIMER_EXPIRY:
case MSG_ID_MMI_EQ_POWER_ON_IND:
case MSG_ID_MMI_EQ_KEYPAD_DETECT_IND:
{
break;
}
default:
{
//其他 interrupt event,主要是弹出框相关,短信报告等等
frm_interrupt_event = mmi_frm_get_frm_int_event_macro(
event_id,
mmi_frm_int_event_convert_table,
sizeof(mmi_frm_int_event_convert_table) / sizeof(mmi_frm_int_event_convert_table[0]));
break;
}
}
return frm_interrupt_event;
}
这个函数就是把 L4C 的消息转换到 MMI 的消息,然后通过 mmi_frm_query_interrupt_event_information 这个函数去查找,是否有相应的注册。这个函数比较简单,就是数组里去比较。
还有一个常用的函数是 SetProtocolEventHandler,也就是注册一个消息的地回调函数。
具体也没有什么可说的,无非就是循环查找是否有这个 event。
1,如果有,看func 是否为空,为空表示去掉这个event 的注册,不为空,就直接修改 这个event 的 func
2,没有,就增加一个event。
这里注意,由一个小小的优化,就是判断,event 数组的最后是否为无效的 event,如果无效,那么减少总注册event 的个数。这个样可以减少 循环查询的次数。
今天就饥饿着总结MMI event,今天打算说说key event。MTK 的 key envent 管理其实跟 MTK MMI event 小结 2 说的protocol event 机制也差不了多少,简单来说,就是app 通过注册某一个key 的回调函数,当这个key事件产生是,该机制就去找到相应的回调函数,进行回调。
先说说key 的 event 有哪些:
1 key down 按键按下 KEY_EVENT_DOWN
2 key up 按键弹起 KEY_EVENT_UP
3 key long press 按键长按 KEY_LONG_PRESS。 默认好像是长按1.5 记不清楚了,可以自己设置,函数Kbd_SetLongPressTime,不过除非必不得已,不要去随便修改,可能会引起其他应用出问题,要修改,也要 先获得当前值,退出应用时设置回原来的值。
4 key repeat 按键按下一直不放,发送该消息 KEY_REPEAT。 举个简单例子,pc 上当输入时,如果长按 a 键,那么回不停的增加a,这个就是repeat动作。这个的时间好像是0.5s,也就是按住键 0.5 s 之后,每隔0.5s,就会发送一个repeat 消息。设置函数Kbd_SetRepeatTime
还有两个不常用的 KEY_HALF_PRESS_DOWN和KEY_HALF_PRESS_UP,也就是说的 2step 可以。
先讲一下按键的处理流程。
当 按键下按键之后,MMI task 会收到 MSG_ID_MMI_EQ_KEYPAD_DETECT_IND这个消息,在系统初始化的时候,已经注册了这个消息的处理函数 mmi_frm_key_handle。也就是说 mmi_frm_key_handle 是处理按键过程的函数。这个函数 mmi_frm_key_handle 收到这个消息后,回去一个 通过 该消息带的函数,去取当前发生的按键事件(实际这个函数就是一个按键缓存队列里去第一个,用一个按键缓存队列,可以防止按键大量的丢失,可以起到同步驱动 和MMI层,驱动产生按键事件很快而MMI有可能比较慢,可以简单的理解为生产者和消费者的关系,跑题了)。 取到当前按键后,看当前是否可以进行操作,比如是否有触摸笔按下等等。
昨天说了一下key event 的基本情况,今天直接从代码开始吧
先看执行key event 函数 mmi_frm_key_handle
void mmi_frm_key_handle(void *paraBuff)
{
kbd_data k;
U32 msg_count;
mmi_eq_keypad_detect_ind_struct *p;
// 判断参数是否为空
// 在 初始化时,系统就注册了 MSG_ID_MMI_EQ_KEYPAD_DETECT_IND 的回调函数为 mmi_frm_key_handle
// 当收到这个消息是,表示有按键事件要处理,
// 这个paraBuff 是 keyTask 传过来的参数,里面的内容是获取按键信息的函数指针
if (paraBuff != NULL)
{
drv_get_key_func new_key_ptr;
p = (mmi_eq_keypad_detect_ind_struct*) paraBuff;
new_key_ptr = (drv_get_key_func) (p->func);
//判断是否是新的函数指针,如果是的话,需要进行一些清理工作
if (new_key_ptr != keypad_ptr)
{
// 新的函数指针,则进行清理,并且判断是否有按键需要处理
if (!mmi_kbd_process_keyptr_change((void *)new_key_ptr))
{
return;
}
//清理 按键 事件缓存buffer
ClearKeyEvents();
keypad_ptr = new_key_ptr;
}
}
// 通过 while 1 来 不停的从 按键缓存里获取按键信息
// 当然这里不会无限死循环,后面会进行相应处理
while (1)
{
//1)判读是否是挂起,2)是否key需要处理
if ((g_mmi_suspend_key_flag == MMI_FALSE) && keypad_ptr &&((*(keypad_ptr))(&k) == MMI_TRUE))
{
if (k.Keydata[0] != KEY_INVALID)
{
#if defined(__MMI_TOUCH_SCREEN__) || defined(__MMI_HANDWRITING_PAD__)
kal_bool is_pen_enabled, is_pen_down;
// 获得触摸屏状态
mmi_pen_get_state(&is_pen_enabled, &is_pen_down);
#endif /* defined(__MMI_TOUCH_SCREEN__) || defined(__MMI_HANDWRITING_PAD__) */
#if defined(__MMI_TOUCH_SCREEN__) || defined(__MMI_HANDWRITING_PAD__)
//如果触摸屏按下,那么保存状态
if (is_pen_down && (k.Keyevent == WM_KEYPRESS))
{
U16 KeyMapIndex;
// 不允许 按键处理
is_allow_key_action = MMI_FALSE;
// 把驱动的按键和MMI 按键进行转换
KeyMapIndex = mmi_frm_get_idx_from_device_key_code(k.Keydata[0]);
if (nKeyPadStatus[KeyMapIndex] == KEY_EVENT_UP)
{
KEYBRD_MESSAGE KeyBrdMsg;
KeyBrdMsg.nKeyCode = nKeyPadMap[KeyMapIndex].nMMIKeyCode;
if (mmi_frm_is_2step_keyCode(KeyBrdMsg.nKeyCode))
{
nKeyPadStatus[KeyMapIndex] = KEY_HALF_PRESS_DOWN;
pressKey = HALF_DOWN_STATUS;
key_is_pressing_count++;
}
else
{
nKeyPadStatus[KeyMapIndex] = KEY_EVENT_DOWN;
pressKey = FULL_DOWN_STATUS;
key_is_pressing_count++;
}
}
}
else
#endif /* defined(__MMI_TOUCH_SCREEN__) || defined(__MMI_HANDWRITING_PAD__) */
//如果按键是弹起,那么还原原来按键状态,也就是弹起状态
//
if ((k.Keyevent == WM_KEYRELEASE) && (is_allow_key_action == MMI_FALSE))
{
#if defined(__MMI_TOUCH_SCREEN__) || defined(__MMI_HANDWRITING_PAD__)
/* Reset allow key flag and Update key status even if pen is down*/
U16 KeyMapIndex;
KeyMapIndex = mmi_frm_get_idx_from_device_key_code(k.Keydata[0]);
if ((nKeyPadStatus[KeyMapIndex] == KEY_EVENT_DOWN)
|| (nKeyPadStatus[KeyMapIndex] == KEY_HALF_PRESS_DOWN)
|| (nKeyPadStatus[KeyMapIndex] == KEY_LONG_PRESS)
|| (nKeyPadStatus[KeyMapIndex] == KEY_REPEAT))
{
nKeyPadStatus[KeyMapIndex] = KEY_EVENT_UP;
key_is_pressing_count–;
}
prevKeyMapIndex = prevKeyMapIndex;
#endif /* defined(__MMI_TOUCH_SCREEN__) || defined(__MMI_HANDWRITING_PAD__) */
is_allow_key_action = MMI_TRUE;
}
// 按键是否可以处理
if (is_allow_key_action == MMI_TRUE)
{
if (((k.Keyevent == WM_KEYPRESS) && (mmi_kbd_get_key_is_pressing_count() == 0))
|| k.Keyevent == DRV_WM_KEYLONGPRESS || k.Keyevent == DRV_WM_KEYREPEATED || k.Keyevent == DRV_WM_KEYFULLPRESS)
{
#if defined(__MMI_TOUCH_SCREEN__) || defined(__MMI_HANDWRITING_PAD__)
//关闭触摸屏
mmi_pen_disable();
#endif
}
//真正处理按键事件
mmi_frm_convert_process_key_event(k.Keyevent, k.Keydata[0]);
if ((k.Keyevent == WM_KEYRELEASE) && (mmi_kbd_get_key_is_pressing_count() == 0))
{
#if defined(__MMI_TOUCH_SCREEN__) || defined(__MMI_HANDWRITING_PAD__)
//重新打开触摸屏
mmi_pen_enable();
#endif
}
}
}
// 获得MMI task 消息队列消息个数
msg_get_ext_queue_info(mmi_ext_qid, &msg_count);
//如果外部有消息或者内部消息,则跳出该循环
if (msg_count > 0 || OslNumOfCircularQMsgs() > 0)
{
//表示还有key需要处理,在MMI task 里会直接调用这个函数进行再处理
g_keypad_flag = MMI_TRUE;
break;
}
}
else
{
// 没有key消息要处理
g_keypad_flag = MMI_FALSE;
break;
}
} /* while(1) */
MMI_TRACE(MMI_FW_TRC_G1_FRM, MMI_FRM_KEY_HDLR_END);
}
1) 这里key 用while (1)来处理,不停的从key 消息buffer里取出按键信息,然后处理,这么做可以防止task中不停的发送消息,可以理解为共享内存。同样当有其他消息来时,需要跳出来处理其他消息,处理完毕后还需要再来处理。
2)这里跟触摸屏的事件进行了冲突处理,也就是按键和触摸屏不能同时工作,这两者也没有优先级。
3)实际真正处理按键 是在 mmi_frm_convert_process_key_event里面,下次在分析
在 MTK MMI event 小结 5 中,提到了event 处理函数 mmi_frm_key_handle,这个函数主要作用是判断是否需要处理按键,从按键缓存里面持续的读取按键信息,然后调用 mmi_frm_convert_process_key_event 进行处理。这个函数没有什么可说的,最多是在屏幕旋转的情况下,把 导航键 转换一下,接着它调用了 ProcessKeyEvent, 这个函数主要是对于一些状态的处理,防止key down 和up 不成对,出现混乱。
void ProcessKeyEvent(U32 MsgType, U16 DeviceKeyCode)
{
MMI_BOOL isKeyPaired;
U16 KeyMapIndex;
/*—————————————————————-*/
/* Code Body */
/*—————————————————————-*/
// 按键影射,把驱动的按键码,转换成MMI 的 按键消息
KeyMapIndex = mmi_frm_get_idx_from_device_key_code(DeviceKeyCode);
if (KeyMapIndex >= MAX_KEYS)
{
return;
}
// 处理各种按键事件,没有什么可以多说的,
// 主体结构都一样,
// 1 判断状态是否正常
// 2 正常 则调用 KeyEventHandler 处理, 否则忽略该事件
if (MsgType == WM_KEYPRESS)
{
// 这里处理 多案件同时按下的情况。这里需要硬件支持
if ((KeyMapIndex != prevKeyMapIndex) && (g_kbd_concurrent_key_mode == CONCURRENT_KEY_MODE_1_KEY))
{
isKeyPaired = (nKeyPadStatus[prevKeyMapIndex] == KEY_EVENT_UP);
prevKeyMapIndex = KeyMapIndex;
}
//判断案件状态是否正常,防止不匹配
if (nKeyPadStatus[KeyMapIndex] == KEY_EVENT_UP)
{
KEYBRD_MESSAGE KeyBrdMsg;
KeyBrdMsg.nKeyCode = nKeyPadMap[KeyMapIndex].nMMIKeyCode;
if (mmi_frm_is_2step_keyCode(KeyBrdMsg.nKeyCode))
{
nKeyPadStatus[KeyMapIndex] = KEY_HALF_PRESS_DOWN;
key_is_pressing_count++;
KeyBrdMsg.nMsgType = KEY_HALF_PRESS_DOWN;
}
else
{
nKeyPadStatus[KeyMapIndex] = KEY_EVENT_DOWN; /* same with KEY_FULL_PRESS_DOWN */
key_is_pressing_count++;
KeyBrdMsg.nMsgType = KEY_EVENT_DOWN;
}
// 处理按键事件
KeyEventHandler((KEYBRD_MESSAGE*) & KeyBrdMsg);
}
else
{
/* Ignore the event */
}
}
else if (MsgType == WM_KEYRELEASE)
{
if ((nKeyPadStatus[KeyMapIndex] == KEY_EVENT_DOWN)
|| (nKeyPadStatus[KeyMapIndex] == KEY_LONG_PRESS)
|| (nKeyPadStatus[KeyMapIndex] == KEY_REPEAT) || (nKeyPadStatus[KeyMapIndex] == KEY_HALF_PRESS_DOWN))
{
KEYBRD_MESSAGE KeyBrdMsg;
nKeyPadStatus[KeyMapIndex] = KEY_EVENT_UP;
key_is_pressing_count–;
KeyBrdMsg.nMsgType = KEY_EVENT_UP;
KeyBrdMsg.nKeyCode = nKeyPadMap[KeyMapIndex].nMMIKeyCode;
KeyEventHandler((KEYBRD_MESSAGE*) & KeyBrdMsg);
}
else
{
/* Ignore the event */
}
}
/* ++Robin, modified by Max Chen */
else if (MsgType == DRV_WM_KEYLONGPRESS)
{
if (nKeyPadStatus[KeyMapIndex] == KEY_EVENT_DOWN)
{
KEYBRD_MESSAGE KeyBrdMsg;
nKeyPadStatus[KeyMapIndex] = KEY_LONG_PRESS;
KeyBrdMsg.nMsgType = KEY_LONG_PRESS;
KeyBrdMsg.nKeyCode = nKeyPadMap[KeyMapIndex].nMMIKeyCode;
KeyEventHandler((KEYBRD_MESSAGE*) & KeyBrdMsg);
}
else
{
/* Ignore the event */
}
}
else if (MsgType == DRV_WM_KEYREPEATED)
{
if ((nKeyPadStatus[KeyMapIndex] == KEY_LONG_PRESS) || (nKeyPadStatus[KeyMapIndex] == KEY_REPEAT))
{
KEYBRD_MESSAGE KeyBrdMsg;
nKeyPadStatus[KeyMapIndex] = KEY_REPEAT;
KeyBrdMsg.nMsgType = KEY_REPEAT;
KeyBrdMsg.nKeyCode = nKeyPadMap[KeyMapIndex].nMMIKeyCode;
KeyEventHandler((KEYBRD_MESSAGE*) & KeyBrdMsg);
}
else
{
/* Ignore the event */
}
}
else if (MsgType == DRV_WM_KEYFULLPRESS)
{
/*
* Only in two-stage key will have KEY_FULL_PRESS_DOWN, and it followed after KEY_HALF_PRESS_DOWN
*/
if (nKeyPadStatus[KeyMapIndex] == KEY_HALF_PRESS_DOWN)
{
KEYBRD_MESSAGE KeyBrdMsg;
nKeyPadStatus[KeyMapIndex] = KEY_EVENT_DOWN;
KeyBrdMsg.nMsgType = KEY_EVENT_DOWN;
KeyBrdMsg.nKeyCode = nKeyPadMap[KeyMapIndex].nMMIKeyCode;
KeyEventHandler((struct KEYBRD_MESSAGE*)&KeyBrdMsg);
}
else
{
/* Ignore the event */
}
}
else if ((MsgType == DRV_WM_ENABLE_TWOKEY_DETECTION) || (MsgType == DRV_WM_ENABLE_THREEKEY_DETECTION) ||
(MsgType == DRV_WM_DISABLE_MULTIKEY_DETECTION))
{
/* Ignore the event */
}
else
{
MMI_TRACE(MMI_FW_TRC_G6_FRM_DETAIL, MMI_FRM_ERROR_PROC_KEYEVENT_HDLR);
MMI_ASSERT(0);
}
}
KeyEventHandler 函数主要判断是否要真的处理该事件,可以看成是一个按键事件的拦截,比如应用切换过程中,需要一个切换动画,而这个动画工程中,需要处理忽略这些按键。就需要特殊的处理。
static void KeyEventHandler(KEYBRD_MESSAGE *eventKey)
{
MMI_BOOL is_hdlr_enabled = MMI_TRUE;
// 主要处理一些特殊相应:屏幕背光,屏幕锁定,按键声音
mmi_kbd_app_key_hdlr(eventKey);
// 判断是否有前置处理函数
if (g_mmi_frm_cntx.kbd_pre_func)
{
is_hdlr_enabled = g_mmi_frm_cntx.kbd_pre_func(eventKey);
}
// 根据前置处理函数结果,判断是否要处理该 key event
if (is_hdlr_enabled)
{
//处理 案件事件
ExecuteCurrKeyHandler((S16) eventKey->nKeyCode, (S16) eventKey->nMsgType);
}
// 是否有后置处理函数,可以进行一些监视
if (g_mmi_frm_cntx.kbd_post_func)
{
g_mmi_frm_cntx.kbd_post_func(eventKey);
}
}
接下来是 ExecuteCurrKeyHandler 这个函数就是根据 按键事件,获得处理函数,进行处理。
void ExecuteCurrKeyHandler(S16 keyCode, S16 keyType)
{
FuncPtr currFuncPtr = NULL;
// 重新设定 键盘锁和屏保timer
mmi_idle_restart_keypad_lock_timer();
mmi_idle_restart_screensaver_timer();
frm_p->currKeyCode = keyCode;
frm_p->currKeyType = keyType;
// 对电话状态下,挂电话键的特殊处理.
if (frm_p->currKeyType == KEY_EVENT_DOWN &&
isInCall() && !GetWapCallPresent() &&
IsBitReset(g_mmi_frm_cntx.end_key_flag, frm_p->currKeyType) )
{
RegisterEndKeyHandlerInCall();
}
// 获得按键处理函数
currFuncPtr = currKeyFuncPtrs[keyCode][keyType];
// 导航的选择键,默认和左功能键的效果一样。
if (keyCode == KEY_ENTER && currFuncPtr == NULL)
{
if (currKeyFuncPtrs[KEY_ENTER][KEY_EVENT_UP] == NULL &&
currKeyFuncPtrs[KEY_ENTER][KEY_EVENT_DOWN] == NULL &&
currKeyFuncPtrs[KEY_ENTER][KEY_HALF_PRESS_DOWN] == NULL &&
currKeyFuncPtrs[KEY_ENTER][KEY_REPEAT] == NULL && currKeyFuncPtrs[KEY_ENTER][KEY_LONG_PRESS] == NULL)
{
currFuncPtr = currKeyFuncPtrs[KEY_LSK][keyType];
}
}
// 处理按键 消息
if (currFuncPtr)
{
(*currFuncPtr)();
}
}
// 重置状态
if (keyType == KEY_EVENT_UP)
{
frm_p->currKeyCode = KEY_INVALID;
frm_p->currKeyType = MAX_KEY_TYPE;
}
}
从整个按键事件的处理流程来看,也没有什么特殊的地方,就是收到消息后,从按键buffer里取出按键事件,然后处理。如果有有别的事件 要处理,那么就break出来,等到处理完这个消息后,在 MMI task 调用 mmi_frm_key_handle 继续处理剩下的按键事件。
这里无非多了很多的判断,是否要进行按键处理,MTK的代码函数名字上虽然看不出什么东西,但是一层一层函数,每一层函数的功能还是比较独立的,所以也不是很难看懂。
到这,key event 的处理基本上就算完成了。其他几个常用的函数注册key 处理函数了。了解了按键event的处理过程之后(其实也是比较简单的),注册函数就很好了解了,就是把相应key 和 event 的数组里放入一个回调函数的指针。比如
void SetKeyHandler(FuncPtr funcPtr, U16 keyCode, U16 keyType) 抛开其他语句,就是一句简单的实现currKeyFuncPtrs[keyCode][keyType] = funcPtr; 还有可以设置一组按键的处理函数,
void SetGroupKeyHandler(FuncPtr funcPtr, PU16 keyCodes, U8 len, U16 keyType),这个很好理解,keyCodes 就是一个按键数组,len是这个数组的长度。实现也很简单,就是一个for循环,把这些keyCodes的回调函数都写成 funcPtr。这里就不再说了(其实也没有什么可以说得了)
其他还有:
void mmi_frm_kbd_reg_pre_key_hdlr(PsKeyProcessCBPtr func) ; //注册按键预处理函数,MTK MMI event 小结 6 解释过
void mmi_frm_kbd_set_tone_state(mmi_frm_kbd_tone_state_enum state); // 设置按键音。需要说明一点,在开发一个应用程序的时候,有些手机状态一般先保存一下进入程序前的状态,在自己的app 退出后还原状态。比如这个按键音,背光长亮等等
接下来简单说一下触摸屏事件,其实触摸屏的实现跟key event 几乎一样。也是通过pen event 缓存队列来获得pen 事件。来看几个代码.
先看一下初始化函数:mmi_pen_init
void mmi_pen_init(void)
{
g_pen_initialized = KAL_TRUE;
// 看到这两个函数指针,是不是感觉很熟悉?对,就是跟key 一样,前置处理和后置处理函数
// 这个看多了,不用细看代码就能猜到了。
g_pen_cntx.pre_pen_callback_fp = NULL;
g_pen_cntx.post_pen_callback_fp = NULL;
// 设置 手写输入法的区域,手写输入法在 触摸屏这里需要特殊处理。
mmi_pen_stop_capture_strokes();
// 设置触摸屏采样时间间隔,参数一是在普通状态下,采样时间间隔,参数二是手写输入法情况下采样时间间隔
mmi_pen_config_sampling_period(MMI_PEN_SAMPLING_PERIOD_1, MMI_PEN_SAMPLING_PERIOD_2);
// 设置超时时间间隔
mmi_pen_config_timeout_period(MMI_PEN_LONGTAP_TIME, MMI_PEN_REPEAT_TIME, MMI_PEN_STROKE_LONGTAP_TIME);
// 设置move 的间距,也就是两个点之间多少个pixel 算move 事件。
mmi_pen_config_move_offset(
MMI_PEN_SKIP_MOVE_OFFSET,
MMI_PEN_SKIP_STROKE_MOVE_OFFSET,
MMI_PEN_SKIP_LONGTAP_OFFSET,
MMI_PEN_SKIP_STROKE_LONGTAP_OFFSET);
// 设置drv 的回调函数,也就是当产生事件时,会回调这个函数
touch_panel_cb_registration(mmi_pen_touch_panel_sendilm, NULL);
// 设置MMI 层消息 MSG_ID_TP_EVENT_IND 的处理函数
SetProtocolEventHandler(mmi_pen_touch_panel_event_ind, MSG_ID_TP_EVENT_IND);
}
在驱动drv 这一层,触摸屏有一个task– MOD_TP_TASK,在处理 事件,MOD_TP_TASK 会回调用 touch_panel_cb_registration 注册的函数,有pen event 事件产生了,这个去pen event 缓存里去,这里注册的是:mmi_pen_touch_panel_sendilm 函数,这个函数就是简单的向MMI task 发送一个消息 MSG_ID_TP_EVENT_IND,让MMI task 来处理。这里这么做的原因也很简单,drv 的task 优先级是很高的,要处理的东西也很多,所以希望回调函数能很快处理完这个事情(发送一个消息很快),这里可以想一下 pc 上的中断,这两者差不多。当然中断还有更多其他原因,比如内核态和用户态等等,需要回调函数尽可能快处理,发送消息到消息队列(扯远了。。。)。
还有一点需要说明的是,现在触摸屏的手机越来越普遍,那么该善触摸屏的体验是非常重要的,可惜的是MTK 的又不是多点触摸,处理起来很费劲。这个时候就需要充分利用好这里的几个设置,采样时间间隔,以及move 事件距离。需要时,让自己app可以捕获尽可能多的点,然后自己进行计算,来判断用户行为。这对改善体验很有帮助。当然这个也是比较耗电的,不需要时,一 定要设置回普通状态。
在 MTK MMI event 小结 7 中,说到,init 时候, 注册了触摸屏消息MSG_ID_TP_EVENT_IND 的处理函数 mmi_pen_touch_panel_event_ind,
static void mmi_pen_touch_panel_event_ind(void *param )
{
// 这个用于控制 只有一个 MSG_ID_TP_EVENT_IND 在 消息队列里
g_pen_is_driver_indication_in_queue = 0;
// 判断是否启动了timer 在定时读取 pen event 缓存
if (!g_pen_polling_timer_started)
{
//真正的处理函数
mmi_pen_poll_hdlr();
}
}
这个函数其实没有做什么真正的处理,只是简单的判断的了一下是否要处理这个消息。这里需要注意的是, 跟按键事件不同的是,这里通过timer来控制处理 pen event 消息缓存里剩余的消息, 按键事件 是在 MMI task 消息循环里,直接调用 按键事件的处理函数。从要实现的效果上没有什么太多的区别。
static void mmi_pen_poll_hdlr(void)
{
TouchPanelEventStruct data;
MMI_BOOL is_stroke_move = MMI_FALSE;
MMI_BOOL has_incoming_event = MMI_FALSE;
MMI_BOOL delay_polling_timer = MMI_FALSE;
MMI_BOOL pen_abort_happen = MMI_FALSE;
U16 interval = 0;
g_pen_polling_timer_started = MMI_FALSE;
ResetBit(g_input_msg_in_queue, MMI_DEVICE_PEN);
// 判断 pen event 是否开启,按键和pen 是冲突的。
if (!g_pen_cntx.is_enabled)
{
return;
}
// 判断是否还有数据要处理
// delay_polling_timer 表示是否启动了timer,来稍后处理这些pen event 事件,开始时初始化为 false
while (!delay_polling_timer && mmi_pen_lookahead_buffer_get_event(&data))
{
mmi_pen_point_struct pos;
// 检测背光情况,是否要开启
mmi_idle_key_event_backlight_check();
// 重置 屏幕保护和键盘锁的时间
mmi_idle_restart_screensaver_timer();
mmi_idle_restart_keypad_lock_timer();
// 获取 x,y。坐标点
pos.x = (S16) data.x_adc;
pos.y = (S16) data.y_adc;
has_incoming_event = MMI_TRUE;
#ifdef __MMI_TOUCH_PANEL_SHORTCUT__
if(!mmi_pen_check_tp_shortcut(&pos, data.event))
#endif /* __MMI_TOUCH_PANEL_SHORTCUT__ */
{
// 判断 是否是 stroke move 事件结束
// is_stroke_move 表示前一个pen event 事件是否是 stroke move
if (is_stroke_move && (data.event != STROKE_MOVE))
{
is_stroke_move = MMI_FALSE;
// 这个函数眼熟吧
if (g_pen_stroke_post_move)
{
g_pen_stroke_post_move();
}
}
// 保存现在状态
g_pen_cntx.pen_current_pos = pos;
g_pen_cntx.pen_event = data.event;
switch (data.event)
{
case PEN_DOWN:
g_pen_cntx.is_pen_down = 1;
g_pen_cntx.is_in_pen_handler = 1;
// 计算最近两次 点击事件的 事件间隔
// 这里这么判断是当时间溢出 short的时候,需要特殊处理
if (g_pen_cntx.previous_pen_down_time > data.time_stamp)
{
interval = 0XFFFF – g_pen_cntx.previous_pen_down_time + data.time_stamp;
}
else
{
interval = data.time_stamp – g_pen_cntx.previous_pen_down_time;
}
//判断两次pen down 的时间是否足够短,间隔距离足够短,从而判断是否是 双击
if ((interval <= MMI_PEN_DOUBLE_CLICK_THRESHOLD) &&
(mmi_pen_get_distance_square(g_pen_cntx.pen_down_pos, pos) < MMI_PEN_SKIP_DOUBLE_CLICK_OFFSET_SQUARE))
{
if (g_pen_event_table[MMI_PEN_EVENT_DOUBLE_CLICK] && g_pen_cntx.is_enabled)
{
// 前置处理函数
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_DOUBLE_CLICK);
// 回调double click 注册函数
(g_pen_event_table[MMI_PEN_EVENT_DOUBLE_CLICK]) (pos);
// 后置处理函数
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_DOUBLE_CLICK);
}
g_pen_cntx.previous_pen_down_time = 0;
}
else
{
g_pen_cntx.previous_pen_down_time = data.time_stamp;
}
g_pen_cntx.pen_down_pos = pos;
if (g_pen_event_table[MMI_PEN_EVENT_DOWN] && g_pen_cntx.is_enabled)
{
// 前置处理函数
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_DOWN);
// 回调pen down注册函数
(g_pen_event_table[MMI_PEN_EVENT_DOWN]) (pos);
// 后置处理函数
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_DOWN);
}
break;
case PEN_UP:
g_pen_cntx.is_pen_down = 0;
if (g_pen_event_table[MMI_PEN_EVENT_UP] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_UP);
(g_pen_event_table[MMI_PEN_EVENT_UP]) (pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_UP);
}
// 判断是否要进行pen event reset
if (g_pen_cntx.reset_stroke_on_pen_up)
{
// 判读是否有 stroke event
if (g_pen_stroke_max_points > 0)
{
// 结束 stroke event,注意,stroke event 会把所有的点push到一个数组里,供注册的程序使用,当然这个数组大小也是程序设定的。这里设置结束标志。
mmi_pen_end_strokes_of_character();
mmi_pen_reset();
mmi_pen_begin_strokes_of_character();
}
else
{
mmi_pen_reset();
}
}
// 一对 down move up 后,即时还有pen事件,通过timer 来取下一组信息
delay_polling_timer = MMI_TRUE;
g_pen_cntx.is_in_pen_handler = 0;
g_pen_cntx.pen_event = TP_UNKNOWN_EVENT;
break;
case PEN_MOVE:
if (g_pen_event_table[MMI_PEN_EVENT_MOVE] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_MOVE);
(g_pen_event_table[MMI_PEN_EVENT_MOVE]) (pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_MOVE);
}
break;
case PEN_LONGTAP:
if (g_pen_event_table[MMI_PEN_EVENT_LONG_TAP] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_LONG_TAP);
(g_pen_event_table[MMI_PEN_EVENT_LONG_TAP]) (pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_LONG_TAP);
}
break;
case PEN_REPEAT:
if (g_pen_event_table[MMI_PEN_EVENT_REPEAT])
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_REPEAT);
(g_pen_event_table[MMI_PEN_EVENT_REPEAT]) (pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_REPEAT);
}
break;
case PEN_ABORT:
pen_abort_happen = MMI_TRUE;
g_pen_drvq_full_abort = MMI_TRUE;
mmi_pen_reset();
break;
case TP_UNKNOWN_EVENT:
MMI_ASSERT(0);
break;
case STROKE_MOVE:
// 这个主要是用于当只有一个手写区域时使用
if (g_pen_cntx.is_pending_stroke_event)
{
// 判断是第一stroke move点是否符合标准(最小距离)
if (mmi_pen_get_distance_square(g_pen_cntx.pen_down_pos, pos) > g_pen_stroke_min_offset_square)
{
g_pen_cntx.is_pending_stroke_event = 0;
g_pen_stroke_min_offset_square = 0;
g_pen_cntx.reset_stroke_on_pen_up = 0;
g_pen_cntx.pen_event = PEN_ABORT;
// 先产生 pen_abort 事件,告诉应用普通的pen 事件被中断,也就是光有pen down 没有pen up事件了
if (g_pen_event_table[MMI_PEN_EVENT_ABORT]&& g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_ABORT);
(g_pen_event_table[MMI_PEN_EVENT_ABORT]) (g_pen_cntx.pen_down_pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_ABORT);
}
g_pen_cntx.pen_event = STROKE_DOWN;
// 把 stroke down 时间事件 保存到数组里
mmi_pen_push_stroke_point(g_pen_cntx.pen_down_pos.x, g_pen_cntx.pen_down_pos.y);
// 发送 stroke down 事件
if (g_pen_stroke_table[MMI_PEN_STROKE_DOWN] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_STROKE_DOWN);
(g_pen_stroke_table[MMI_PEN_STROKE_DOWN]) (g_pen_cntx.pen_down_pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_STROKE_DOWN);
}
g_pen_cntx.pen_event = STROKE_MOVE;
// 预处理
is_stroke_move = MMI_TRUE;
if (g_pen_stroke_pre_move)
{
g_pen_stroke_pre_move();
}
// 发送 stroke move
mmi_pen_push_stroke_point(pos.x, pos.y);
if (g_pen_stroke_table[MMI_PEN_STROKE_MOVE] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_STROKE_MOVE);
(g_pen_stroke_table[MMI_PEN_STROKE_MOVE]) (pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_STROKE_MOVE);
}
}
}
else
{
// 判断在哪个区域里面
if (g_pen_num_stroke_area > 1)
{
mmi_pen_fix_multi_block_pen_position(&pos);
}
// 预处理
if (!is_stroke_move)
{
is_stroke_move = MMI_TRUE;
if (g_pen_stroke_pre_move)
{
g_pen_stroke_pre_move();
}
}
// 保存 stroke move 并发送
mmi_pen_push_stroke_point(pos.x, pos.y);
if (g_pen_stroke_table[MMI_PEN_STROKE_MOVE] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_STROKE_MOVE);
(g_pen_stroke_table[MMI_PEN_STROKE_MOVE]) (pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_STROKE_MOVE);
}
}
break;
case STROKE_DOWN:
g_pen_cntx.is_stroke_down = 1;
g_pen_cntx.is_pen_down = 1;
g_pen_cntx.pen_down_pos = pos;
g_pen_cntx.stroke_down_block_index = mmi_pen_lookup_handwriting_block(pos.x, pos.y);
// 单个区域特殊处理
if (g_pen_stroke_min_offset_square > 0)
{
g_pen_cntx.is_pending_stroke_event = 1;
g_pen_cntx.pen_event = PEN_DOWN;
if (g_pen_event_table[MMI_PEN_EVENT_DOWN] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_DOWN);
(g_pen_event_table[MMI_PEN_EVENT_DOWN]) (g_pen_cntx.pen_down_pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_DOWN);
}
}
else
{
g_pen_cntx.is_pending_stroke_event = 0;
g_pen_cntx.pen_event = STROKE_DOWN;
if (g_pen_stroke_table[MMI_PEN_STROKE_DOWN] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_STROKE_DOWN);
(g_pen_stroke_table[MMI_PEN_STROKE_DOWN]) (pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_STROKE_DOWN);
}
if (g_pen_cntx.is_enabled && g_pen_cntx.is_pen_down && g_pen_stroke_max_points > 0)
{
mmi_pen_push_stroke_point(pos.x, pos.y);
}
}
break;
case STROKE_UP:
MMI_DBG_ASSERT(g_pen_stroke_max_points > 0);
g_pen_cntx.is_stroke_down = 0;
g_pen_cntx.is_pen_down = 0;
if (g_pen_cntx.is_pending_stroke_event)
{
g_pen_cntx.is_pending_stroke_event = 0;
g_pen_cntx.reset_stroke_on_pen_up = 1;
g_pen_cntx.pen_event = PEN_UP;
if (g_pen_event_table[MMI_PEN_EVENT_UP] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_EVENT_UP);
(g_pen_event_table[MMI_PEN_EVENT_UP]) (g_pen_cntx.pen_down_pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_EVENT_UP);
}
delay_polling_timer = MMI_TRUE;
}
else
{
g_pen_cntx.pen_event = STROKE_UP;
if (g_pen_num_stroke_area > 1)
{
mmi_pen_fix_multi_block_pen_position(&pos);
}
mmi_pen_push_stroke_end();
if (g_pen_stroke_table[MMI_PEN_STROKE_UP] && g_pen_cntx.is_enabled)
{
MMI_PEN_EXECUTE_PRE_CALLBACK_FUNC(MMI_PEN_STROKE_UP);
(g_pen_stroke_table[MMI_PEN_STROKE_UP]) (pos);
MMI_PEN_EXECUTE_POST_CALLBACK_FUNC(MMI_PEN_STROKE_UP);
}
}
if (g_pen_cntx.reset_stroke_on_pen_up)
{
if (g_pen_stroke_max_points > 0)
{
mmi_pen_end_strokes_of_character();
mmi_pen_reset();
mmi_pen_begin_strokes_of_character();
}
else
{
mmi_pen_reset();
}
}
/* leave pen handler procedure */
g_pen_cntx.is_in_pen_handler = 0;
g_pen_cntx.pen_event = TP_UNKNOWN_EVENT;
break;
default:
MMI_ASSERT(0);
}
} /* if(!mmi_pen_check_tp_shortcut(&pos, data.event)) */
// 把外部消息放入到内部循环消息队列
mmi_frm_fetch_msg_from_extQ_to_circularQ();
OslCircularDump();
// 如果内部消息积压大于 MMI_PENDING_MSG_THRESHOLD,就跳出循环,进行处理
if (OslNumOfCircularQMsgs() > MMI_PENDING_MSG_THRESHOLD)
{
break;
}
}
if (is_stroke_move)
{
if (g_pen_stroke_post_move)
{
g_pen_stroke_post_move();
}
}
if (has_incoming_event)
{
if (!mmi_shutdown_is_in_power_off_period())
{
mmi_idle_key_event_backlight_check();
}
}
if (g_pen_cntx.is_in_pen_handler == 0)
{
g_pen_cntx.pen_event = TP_UNKNOWN_EVENT;
}
// 启动定时期
if (delay_polling_timer)
{
StartTimer(PEN_POLLING_TIMER, MMI_PEN_DEBOUNCE_POLLING_DELAY, mmi_pen_poll_hdlr);
g_pen_polling_timer_started = MMI_TRUE;
SetBit(g_input_msg_in_queue, MMI_DEVICE_PEN);
}
else if (g_pen_cntx.is_pen_down || pen_abort_happen)
{
StartTimer(PEN_POLLING_TIMER, MMI_PEN_POLLING_PERIOD, mmi_pen_poll_hdlr);
g_pen_polling_timer_started = MMI_TRUE;
SetBit(g_input_msg_in_queue, MMI_DEVICE_PEN);
if(pen_abort_happen)
{
pen_abort_happen = MMI_FALSE;
}
}
}
这里其实也是比较简单:
1: 取pen event 事件
2: 根据时间类型处理事件
3:是否有额外消息要处理,是 退出,否继续 1
这里有点点复杂的是手写输入法,这个事件比较乱,全局变量一堆,分析起来比较头疼。
到这里 pen event 其实也就差不多了,说一下几个常用的函数:
mmi_pen_enable /mmi_pen_disable //打开/关闭 pen 事件
mmi_pen_set_calibration_data / mmi_pen_read_calibration_data //设置和读取校准数据,校准数据总共四个值 x = x*x_scale + x_offset, y = y*y_scale + y_offset
mmi_pen_start_calibration // 开始校准
mmi_pen_start_capture_strokes / mmi_pen_stop_capture_strokes //在设定区域里 开始和关闭 捕捉输入法轨迹(stroke)点。具体可以参见 EntryEmPenTest 里面用法,有机会在分析MTK手写输入法
mmi_pen_config_move_offset / mmi_pen_config_timeout_period / mmi_pen_config_sampling_period //几个配置函数,对于写一个好的触摸屏应用,还是很有必要的,相关介绍看 MTK MMI event 小结 7
mmi_pen_register_move(up down repeat …)_handler //注册pen 事件处理函数。
对于触摸屏校准,做一个简单的说明:
当 调用 void mmi_pen_start_calibration(kal_uint16 num, const mmi_pen_point_struct *points) 函数时,就开始进入触摸屏校准状态,一旦调用这个函数,那么收到点将都是没有校准过的,直到校准完成。num 表示校准的个数,MTK是3个,points 分别就是理想点的位置(可能不好理解,就是屏幕上显示需要点击的3个点)
真正执行校准的是在 函数 touch_excute_cali里面
void touch_excute_cali(kal_int16 x_adc, kal_int16 y_adc)
{
ilm_struct *tp_ilm;
tp_cali_done_struct *local_para;
kal_bool cali_result;
module_type owner=0;
// 校准是否被激活,调用 mmi_pen_start_calibration 后,就为true
if(tp_cali_mode==KAL_FALSE)
return;
// 校准第一个点
if(tp_cali_cnt==0)
{
tp_cali_cnt++;
// 记录获取的adc 点(实际物理点坐标)
cali_point_adc[0].x=x_adc;
cali_point_adc[0].y=y_adc;
}
// 第二个点
else if(tp_cali_cnt==1)
{
tp_cali_cnt++;
cali_point_adc[1].x=x_adc;
cali_point_adc[1].y=y_adc;
}
// 第三个点
else if(tp_cali_cnt==2)
{
cali_point_adc[2].x=x_adc;
cali_point_adc[2].y=y_adc;
tp_cali_mode=KAL_FALSE;
tp_cali_cnt=0;
// 对校准进行计算
cali_result=touch_panel_check_cali();
if(cali_result==KAL_TRUE)
{
// 校准ok ,分别计算 x 和 y 的 slope 和 offset
touch_panel_tuning(cali_point[0].x, cali_point_adc[0].x,
cali_point[1].x, cali_point_adc[1].x,
&TPCali.x_slope, &TPCali.x_offset);
touch_panel_tuning(cali_point[0].y, cali_point_adc[0].y,
cali_point[1].y, cali_point_adc[1].y,
&TPCali.y_slope, &TPCali.y_offset);
}
// 发送消息告诉MMI , 校准的结果.
// 如果失败,MMI 需要重新校准
local_para = (tp_cali_done_struct *)
construct_local_para(sizeof(tp_cali_done_struct),TD_UL);
local_para->result=cali_result;
owner=MOD_MMI;
DRV_BuildPrimitive(tp_ilm,
MOD_TP_TASK,
owner,
MSG_ID_TP_CALI_DONE,
local_para);
msg_send_ext_queue(tp_ilm);
}
}
其实校准是否正确 还是主要看 touch_panel_check_cali 这个函数的结果。这个函数主要里有 3 个校准测试,只有都符合条件才符合。
stage 1 比例对比,也就是保证测试点1 和 点 2 倾斜一定的角度。不会与x 轴或者y轴平行
stage 2 保证第1 点 和 第 2 点 在 第3 点的 两端
stage 3 根据stage 1 和 stage 2 得出的slope 和 offset, 比较计算获得的第3点 和 希望获得第3点 误差小于 设定值。这几个函数就是几个数学公式,自己研究研究 就Ok 了。