MTK的屏幕历史记录(history机制)也是开发中肯定要遇到的。简单的讲就是应用(界面)切换时,有一个正常的循序。比如A->B->C,那么最长见返回顺序就是C->B->A,这里的A、B、C可以是应用,也可以是某个应用里的不同界面,也就是说,当在某个屏幕按下返回键时,能正常地返回到上一界面。从应用开发的角度来讲,应用完全可以自己实现一套机制,让应用自己界面切换顺序正常。但是如果应用之间的切换,那么就需要使用MTK history机制。history的实现也并不复杂,在进入一个新的应用时,先调用上一个应用的退出函数,告知应用已经被退出,需要进入到新应用。然后把上一应用的相关信息保存到history里面。等该新应用退出时,那么应用会从history里取出上一应用的信息,并进入上一个应用。
History的内部是用一个stack 来实现,稍微想一下就能明白。最早打开的应用,需要最后关闭(先进后出)。MTK 是手机,是一个单任务手机(这里说的单任务,就是一个时刻只能一个应用在运行,好吧,我承认它能后台播放MP3,但这只是个特例)。也就是说在一个时刻,只能有一个应用处于激活状态,一个应用激活了,那么另一个应用就要被退出,这是使得MTK history 机制可以正常工作基本。
每个应用都从函数 EntryNewScreen 开始进入(即这个函数是进入应用时,一开始就会调用的函数),这个函数告诉MTK history 机制,需要进入一个新的应用,原来的应用需要被退出(或者说挂起,保存到历史记录里),等到新应用退出时,重新回到原来的应用(或者说恢复状态,这个具体由应用来确定)。
功能:用来创建一个新屏幕;
参数:newscrnID 新应用的屏幕id(一个应用可能会有多个屏幕id),屏幕id为屏幕的唯一标识;
newExitHandler 屏幕退出函数:当这个应用退出时被执行的回调函数,无论是哪种退出方式(主动,被动);
newEntryHandler 屏幕入口函数:如果保存到历史记录里,那么当从历史记录里恢复时,会调用这个函数;
flag 一般为NULL,其实他是用来标识屏幕的类型的。屏幕有以下几种:MMI_FRM_FULL_SCREEN,MMI_FRM_SMALL_SCREEN,MMI_FRM_TAB_PAGE。
U8 EntryNewScreen(U16 newscrnID, FuncPtr newExitHandler, FuncPtr newEntryHandler, void *flag)
{
return mmi_frm_entry_new_screen(newscrnID, (exit_func_ptr)newExitHandler, (entry_func_ptr)newEntryHandler, flag);
}
U8 mmi_frm_entry_new_screen(U16 newscrnID, FuncPtr newExitHandler, FuncPtr newEntryHandler, void *flag)
{
// 具体这里的tab page是什么也不是很清楚,大部分情况下不用(即flag != MMI_TAB_PAGE成立)
if (flag != MMI_TAB_PAGE)
{
is_tab_page = MMI_FALSE;
// 保存当前屏幕id
currTopScrnID = newscrnID;
// mmi_is_orderly_exit_screen这个变量主要是用于调用GoBackToHistory返回到一个指定screen时使用
// IsMainLCDHistoryBack()判断是不是屏幕历史栈底;这里的判断是为了防止重复调用
if (!mmi_is_orderly_exit_screen || !IsMainLCDHistoryBack())
{
// 执行保存历史记录,执行上一个屏幕的退出函数,并把上一个屏幕加入屏幕历史栈中。
ExecuteCurrExitHandler();
}
//设置当前屏幕入口函数和退出函数,并保存屏幕ID,及信息。
currExitScrnID = newscrnID;
if ((newExitHandler != NULL) || (newEntryHandler != NULL))
{
// 保存当前活动应用相关信息
mmi_frm_set_generic_exit_handler(newscrnID, newExitHandler, newEntryHandler);
}
mmi_is_orderly_exit_screen = MMI_FALSE;
IsBackHistory = MMI_FALSE;
}
else
{
is_tab_page = MMI_TRUE;
ExecuteCurrExitHandler();
curr_tab_exit_func_ptr = newExitHandler;
SetKeyHandler(mmi_frm_general_tab_l_arrow_key_hdlr, KEY_LEFT_ARROW, KEY_EVENT_DOWN);
SetKeyHandler(mmi_frm_general_tab_r_arrow_key_hdlr, KEY_RIGHT_ARROW, KEY_EVENT_DOWN);
}
return MMI_TRUE;
}
其实这个函数也没有做什么特别的,主要执行函数ExecuteCurrExitHandler,这个函数动作就是执行历史记录保存和回调退出函数。
void ExecuteCurrExitHandler(void)
{
ExecuteCurrExitHandler_Ext();
// 打开按键声音
mmi_frm_kbd_set_tone_state(MMI_KEY_TONE_ENABLED);
// 清理 按键和触摸屏
ClearInputEventHandler(MMI_DEVICE_KEY);
}
这个函数需要注意的是,ClearInputEventHandler。他会把按键和触摸屏所有注册的回调函数都清空,但是需要注意的是,对于按键系统会默认注册END键,这是一个比较特殊的按键,普通情况下,按END键是一键返回到桌面,如果是在打电话状态,那么是挂电话键。举个简单的例子,就是当通电话的时候,如果需要去查看电话本,那么在电话本应用里,END键就是挂电话键,而普通情况下,END键就是一键返回建。
void ExecuteCurrExitHandler_Ext(void)
{
// 处理一些屏幕显示的特殊处理
UI_common_screen_pre_exit();
// 清空中断event ,具体分析见MMI event小结部分
if (curr_entry_handler || curr_exit_handler)
{
mmi_frm_reset_interrupt_event_context();
}
// 非tab page 退出,这个是最常用的
if (!is_tab_page)
{
//如果有进入函数,也就是说这个应用需要保存到历史记录里
if (curr_entry_handler)
{
//在这里将屏幕加入历史屏幕栈。
mmi_frm_generic_exit_scrn(currExitScrnID, curr_entry_handler);
}
// 执行退出函数
if (curr_exit_handler)
{
mmu_frm_execute_scrn_exit_handler = MMI_TRUE;
//在这里执行屏幕退出函数
curr_exit_handler(curr_exit_scrn_arg_p);
mmu_frm_execute_scrn_exit_handler = MMI_FALSE;
}
curr_entry_handler = NULL;
curr_exit_handler = NULL;
}
// 清空高亮信息
g_mask_hilite_partent_id = 0;
g_mask_hilite_mask = 0xFFFFFFFF;
// 结束一些UI 动作
UI_common_screen_exit();
}
从代码可以看出,如果上个屏幕的入口函数curr_entry_handler == NULL的话,是不会被加入屏幕历史栈的。但可以在退出函数中手动将屏幕加入屏幕历史栈。
如果的退出函数curr_exit_handler也为NULL,那么上个屏幕就不会被加入屏幕历史栈。
所以将一个屏幕加入屏幕历史栈有两种方法:
①传入屏幕入口函数。
②传入退出函数,并在退出函数中定义代码,手动加入屏幕历史栈。
这两种方法不可同时进行。否则会添加两次。如果需要同时传入屏幕入口函数和退出函数的话,我们只需在退出函数中不写加入屏幕历史栈的代码即可。
这里需要注意的是在函数ExecuteCurrExitHandler_Ext里面curr_entry_handler 和 curr_exit_handler都表示需要被挂起的应用的指针,而不是新应用相应的指针。只有当在调用mmi_frm_set_generic_exit_handler后,curr_Entry_FuncPtr 和 curr_exit_handler,才表示当前应用。
如果不想把当前应用保存到历史记录里面,只要设置curr_entry_handler 为NULL就行。比如A->B->C 而,进入B的时候,设置了curr_entry_handler 为NULL,那么当从C返回时,将直接返回到A,跳过B。
This function is the general screen's exit handler. If the previous screen is the normal screen (not inline editor screen) and needs to add in the history, the framework calls this function to execute the general exit handler first, then tries to call its exit handler.这个函数是通用的屏幕退出处理程序。如果原来的屏幕是正常的屏幕(不是inline editor screen),而且该屏幕需要添加到历史栈里,则首先调用这个函数来执行一般的退出处理,然后调用它的退出处理程序。
void mmi_frm_generic_exit_scrn(U16 scrn_id, entry_func_ptr entry_func_ptr)
{
if (!is_tab_page)
{
if ((scrn_id == currExitScrnID) && (curr_exit_scrn_arg_p != NULL))
{
mmi_frm_add_history(scrn_id, entry_func_ptr, GetCategoryHistory, NULL, NULL, NULL, NULL, curr_exit_scrn_arg_p);
}
else
{
mmi_frm_add_history(scrn_id, entry_func_ptr, GetCategoryHistory, NULL, NULL, NULL, NULL, NULL);
}
}
}
MMI_BOOL mmi_frm_add_history(
U16 scrn_id, /* the screen id */
entry_func_ptr entryFuncPtr, /* the screen's entry function */
HistoryGetData getGuiFuncPtr, /* the function to get GUI data */
HistoryGetSize getInputBufSizeFuncPtr, /* the function to get input buffer size */
HistoryGetData getInputBufFuncPtr, /* the function to get input buffer */
MemAlloc mallocFuncPtr, /* the function to allocate memory */
MemFree mfreeFuncPtr, /* the function to free memory */
void* app_arg_p)
{
S16 inputBuf_size;
/* Check for OK pressed or BACK pressed */
if (IsBackHistory != MMI_TRUE)
{
increment(); //++currHistoryIndex;++topHistoryIndex;
/* Store History to History Data Structure */
memset(&historyData[topHistoryIndex], 0, sizeof(historyNode));
historyData[topHistoryIndex].scrnID = scrn_id;
historyData[topHistoryIndex].entryFuncPtr = entryFuncPtr;
MMI_ASSERT(getGuiFuncPtr);
historyData[topHistoryIndex].guiBuffer = OslMalloc(MAX_GUI_BUFFER);
memset(historyData[topHistoryIndex].guiBuffer, 0, MAX_GUI_BUFFER);
getGuiFuncPtr(historyData[topHistoryIndex].guiBuffer);
historyData[topHistoryIndex].mallcFuncPtr = mallocFuncPtr;
historyData[topHistoryIndex].mfreeFuncPtr = mfreeFuncPtr;
historyData[topHistoryIndex].app_arg = app_arg_p;
if (getInputBufSizeFuncPtr)
{
MMI_ASSERT(getInputBufFuncPtr);
inputBuf_size = (S16)getInputBufSizeFuncPtr();
if (inputBuf_size > 0)
{
if(mallocFuncPtr)
{
/* Check memory free function pointer is valid */
MMI_ASSERT(mfreeFuncPtr);
historyData[topHistoryIndex].inputBuffer = (U8*)mallocFuncPtr(inputBuf_size + ENCODING_LENGTH + MMI_FRM_INPUT_BUFFER_HEADER_SIZE);
}
else
{
historyData[topHistoryIndex].inputBuffer = OslMalloc(inputBuf_size + ENCODING_LENGTH + MMI_FRM_INPUT_BUFFER_HEADER_SIZE);
}
memcpy(historyData[topHistoryIndex].inputBuffer, &inputBuf_size, MMI_FRM_INPUT_BUFFER_HEADER_SIZE);
getInputBufFuncPtr(historyData[topHistoryIndex].inputBuffer + MMI_FRM_INPUT_BUFFER_HEADER_SIZE);
}
else
{
historyData[topHistoryIndex].inputBuffer = NULL;
}
}
currHistoryIndex = topHistoryIndex;
}
IsBackHistory = MMI_FALSE;
return MMI_TRUE;
}