初涉mtk平台,遇到一个奇怪的问题,假如有这样一段代码:(郭金原创,转载请注明出处)
void EntryFristScreen()
{
EntryNewScreen(SCR_ID_1, NULL, EntryFristScreen, NULL);//第三个参数newEntryHandler不为空
guiBuffer = GetCurrGuiBuffer(SCR_ID_1);
........//为了更好的说明问题,省去部分代码和参数
SetLeftSoftkeyFunction(EntrySecondScreen, KEY_EVENT_UP);
SetRightSoftkeyFunction(GoBackHistory, KEY_EVENT_UP);
ShowCategory52Screen(。。, 。。, 。。, 。。, 。。, 。。,
。。,。。, 。。, 。。, 。。,
0, guiBuffer); //其中0为设置的列表项高亮度条所在位置
//问题就在这里,当第一次进入52Screen的时候高亮条在列表项索引0位置,改变高亮条位置之后进入下一屏, //在返回,虽然依然是调用EntryFristScreen,虽然ShowCategory52Screen中高亮参数依然是0,但 //是高亮位置已经不在列表索引0位置
}
为了弄清这个问题,和更好的说明Screen的History机制,我们添加一屏,如下:
void EntrySecondScreen()
{
EntryNewScreen(SCR_ID_2, NULL, EntrySecondScreen, NULL);
guiBuffer = GetCurrGuiBuffer(SCR_ID_2);
SetLeftSoftkeyFunction(EntryXXXScreen, KEY_EVENT_UP);
SetRightSoftkeyFunction(GoBackHistory, KEY_EVENT_UP);
ShowCategoryXXXScreen(。。, 。。, 。。, 。。, 。。, 。。,
。。, 。。, 。。, 。。, 。。,
。。, 。。);
}
我们要分析的是从第一屏进到第二屏这个动作过程,对于这个过程中发生的任何操作我们只关心对SCR_ID_1和SCR_ID_2的操作,其他的一概不管,首先我们来分析EntryFristScreen 中EntryNewScreen(SCR_ID_1, NULL, EntryFristScreen, NULL)所作的操作,在这个操作中除了对SCR_ID_1上一屏的操作我们不管外,这个操作中和
SCR_ID_1相关的操作是:
currExitScrnID = newscrnID; //即currExitScrnID = SCR_ID_1;
//currExitScrnID = scrnID; //在SetGenericExitHandler中有一个重复的操作,我们忽略它
currExitFuncPtr = exitFuncPtr; //即currExitFuncPtr = NULL;
currEntryFuncPtr = entryFuncPtr; //即currEntryFuncPtr = EntryFristScreen
在EntryNewScreen(SCR_ID_1, NULL, EntryFristScreen, NULL)中与SCR_ID_1相关的重要操作仅有这么多,guiBuffer = GetCurrGuiBuffer(SCR_ID_1)操作由于SCR_ID_1并没有被写入historyData栈,所以guiBuffer = NULL,而在ShowCategory52Screen中的层层调用中,我们只需要记住这其中的一个操作,就是对全局函数指针的赋值:
GetCategoryHistory = get_history_function;即GetCategoryHistory = dm_get_category_history。
关于第一屏的分析就到这里了,我们即将进入第二屏,在进入第二屏之前,我们先假设它会保存上一屏的参数(高亮度条位置等参数,保存到historyData的Scr栈中的guiBuffer和inputBuffer中),我们先假设这一假设成立,然后再证实它。让我们开始第二屏的分析,同样,在所有的操作过程中,我们只关心SCR_ID_1和SCR_ID_2相关的,并且和主题相关的操作:
首先,EntryNewScreen(SCR_ID_2, NULL, EntrySecondScreen, NULL)的操作;以EntryNewScreen->ExecuteCurrExitHandler->ExecuteCurrExitHandler_Ext->GenericExitScreen->AddHistory【#define AddHistory(addHistory) AddHistoryReference(&(addHistory))】这样的调用顺序来分析,我们着重分析一下GenericExitScreen函数,在执行enericExitScreen时,currExitScrnID = SCR_ID_1;currExitFuncPtr = NULL;
currEntryFuncPtr = EntryFristScreen;GetCategoryHistory = dm_get_category_history;这4个变量的值并没有发生改变;因此这段代码
void GenericExitScreen(U16 scrnID, FuncPtr entryFuncPtr)
{
history h;
U16 nHistory = 0;
h.scrnID = scrnID;
h.entryFuncPtr = entryFuncPtr;
mmi_ucs2cpy((S8*) h.inputBuffer, (S8*) & nHistory);//只起一个初始化的作用,把inputBuffer数组置空
GetCategoryHistory(h.guiBuffer);
AddHistory(h);
}
在ExecuteCurrExitHandler_Ext中调用GenericExitScreen(currExitScrnID, currEntryFuncPtr);相当于:
void GenericExitScreen(currExitScrnID, currEntryFuncPtr)
{
history h;
U16 nHistory = 0;
h.scrnID = SCR_ID_1;
h.entryFuncPtr = EntryFristScreen;
mmi_ucs2cpy((S8*) h.inputBuffer, (S8*) & nHistory);
dm_get_category_history(h.guiBuffer);
AddHistory(h);
}
在dm_get_category_history中依据刚才画SCR_ID_1过程中的全局变量(因为SCR_ID_2的画屏操作尚未开始,因此当前的全局变量是记录的是画上一屏的参数或称上下文,保留现场),将这些现场数据保存到h.guiBuffer(对于其他屏,也有保存inputBuffer的过程)中,在dm_get_category_history中根据现场的全局变量找到对应的Manager Controls,
这里是:
case DM_LIST1:
{
get_list_menu_category_history((U16) p_dm_data->s32CatId, history_buffer);
break;
}
在get_list_menu_category_history中将现场的全局变量保存到h.guiBuffer中(这里只用到了guiBuffer),
//这里的history_buffer就是我们传进来的h.guiBuffer
void get_list_menu_category_history(U16 history_ID, U8 *history_buffer)
{
if (history_buffer != NULL)
{
U16 hID = (U16) (history_ID | 0x8000); list_menu_category_history *h = (list_menu_category_history*) history_buffer; h->history_ID = hID;
#ifndef __MMI_DICTIONARY__
h->highlighted_item = (S16) MMI_fixed_list_menu.highlighted_item;
h->first_displayed_item = (S16) MMI_fixed_list_menu.first_displayed_item;
h->last_displayed_item = (S16) MMI_fixed_list_menu.last_displayed_item;
h->displayed_items = (S16) MMI_fixed_list_menu.displayed_items;
#else h->highlighted_item = MMI_fixed_list_menu.highlighted_item;
h->first_displayed_item = MMI_fixed_list_menu.first_displayed_item;
h->last_displayed_item = MMI_fixed_list_menu.last_displayed_item;
h->displayed_items = MMI_fixed_list_menu.displayed_items;
#endif
h->flags = MMI_fixed_list_menu.flags;
h->state = (S8) (-1);
h->num_items = MMI_fixed_list_menu.n_items;
}
}
回到GenericExitScreen函数,在获得现场数据之后调用AddHistory(h),将现场数据添加到Screen的History栈historyData中,History栈中inputBuffer和guiBuffer空间的申请都是在AddHistory也就是AddHistoryReference这个操作中完成的,并用我们用dm_get_category_history得到的现场数据为他赋值:
void AddHistoryReference(history *addHistory)
{
increment();
memset(&historyData[currHistoryIndex], 0, sizeof(historyNode)); historyData[currHistoryIndex].scrnID = addHistory->scrnID; historyData[currHistoryIndex].entryFuncPtr = addHistory->entryFuncPtr;
#ifdef __MMI_UI_SMALL_SCREEN_SUPPORT__
historyData[currHistoryIndex].isSmallScreen = (U16) small_history_node;
#endif
length = mmi_ucs2strlen((PS8) addHistory->inputBuffer);
if (length)
{
historyData[currHistoryIndex].inputBuffer = OslMalloc(length * ENCODING_LENGTH + ENCODING_LENGTH);
mmi_ucs2cpy((PS8) historyData[currHistoryIndex].inputBuffer, (PS8) addHistory->inputBuffer); }
historyData[currHistoryIndex].guiBuffer = OslMalloc(MAX_GUI_BUFFER); memcpy(historyData[currHistoryIndex].guiBuffer, addHistory->guiBuffer, MAX_GUI_BUFFER);
}
到这里我们前面的假设已经得到证明,保存上一屏的现场数据的操作在进入新的一屏前的这一时刻完成,进入新的一屏后在返回上一屏(SCR_ID_1),系统就会从historyData中去读取上一屏(SCR_ID_1)的现场数据。假如依旧是
void EntryFristScreen()
{
EntryNewScreen(SCR_ID_1, NULL, EntryFristScreen, NULL);
guiBuffer = GetCurrGuiBuffer(SCR_ID_1);
........//为了更好的说明问题,省去部分代码和参数
SetLeftSoftkeyFunction(EntrySecondScreen, KEY_EVENT_UP);
SetRightSoftkeyFunction(GoBackHistory, KEY_EVENT_UP);
ShowCategory52Screen(。。, 。。, 。。, 。。, 。。, 。。,
。。,。。, 。。, 。。, 。。,
0, guiBuffer);
}
此时在调用guiBuffer = GetCurrGuiBuffer(SCR_ID_1); guiBuffer 就不为空了,在ShowCategory52Screen中会通过set_list_menu_category_history(..., guiBuffer)去读取上次退出时的现场数据,而不去理会ShowCategory52Screen中倒数第二个参数(highlighted_item = 0)的赋值情况。所以高亮度条不停留在列表索引0位置。
写到这里,你是不是豁然开朗了?为了能善始善终,我们再来看一下guiBuffer 和inputBuffer的是在哪些时机下释放的,在History模块中,只有static void mmi_free_history_buffer(S16 id)和void DinitHistory(void)操作调用了OslMfree(historyData[id].guiBuffer)和OslMfree(historyData[id].inputBuffer);其中DinitHistory是一个全局初始化函数或善后处理函数,即初始化或重新初始化整个History栈;而mmi_free_history_buffer又是一个局部的接口,因此调用它的外部接口就是释放操作的时机,归结起来,在调用一下接口的时候(假如条件满足),会导致inputBuffer和guiBuffer的释放:
U8 DeleteScreens(U16 start_scrnid, U16 end_scrnid)
U8 DeleteBeyondScrTillScr(U16 beyondScrnid, U16 tillScrnid)
U16 DeleteBetweenScreen(U16 StartScrId, U16 EndScrId)
U16 DeleteScreenIfPresent(U16 ScrId)
U16 DeleteScreenFromToNnodes(U16 ScrId, U16 num_nodes)
U8 DeleteFromScrUptoScr(U16 start_scrnid, U16 upto_scrnid)
pBOOL HistoryReplace(U16 out_scrn_id, U16 in_scrn_id, FuncPtr in_src_func)
U8 ExecuteRootMainHistoryScreen(void *funcPtr)
当然还有GoBackHistory()
今天写了第一篇关于工作中分析问题的文章,本来在写文章之前还不是完全清晰History机制保存现场数据guiBuffer 和inputBuffer的机制,经过边写边看,居然发现了它的来龙去脉,看来写文章对完全理解某一问题还是有帮助的。