熟悉了几个模块的代码流程以后,发现屏幕创建并没有想像中的简单,每个程序似乎都要在创建自己的屏幕之前先创建一个群,随后又进入这个群后,这才开始自己屏幕的创建,为什么要这么多此一举呢,为了研究屏幕的运作流程?我用MODIS追踪了屏幕的创建过程。
首先,介绍下两个根节点,一个是屏幕根节点ROOT,所有的有效屏幕都是它的子孙节点,还一个是scenario_dangle,用来暂存刚创建或马上要销毁的屏幕节点。
然后屏幕节点分为group节点和screen节点。group节点用来管理它旗下的screen节点,它本身并不产生实际意义上的屏幕,screen节点可以理解为我们所见到的屏幕。其中ROOT下面的叶子节点必须得是screen节点。如图1所示,其中,ROOT节点下面的8101节点是开机最早创建的一个group节点,它旗下有四个screen节点,其中8105节点就是选择sim卡的界面,而8108则是选择sim卡进入待机界面前的动画绘制界面,8102节点比较奇怪,它本身好像没什么效果,不知道是不是为了保证叶子节点是screen节点才创建的。
group节点和screen节点的创建过程也不太一样,我简要概括一下两者的创建和销毁流程:
1.group节点:
典型的函数: mmi_frm_group_create mmi_frm_group_enter mmi_frm_group_close
(1) 创建节点:
a. 检查scenario_dangle中是否存在此id
b. 检查parent节点中是否存在此id
c. 若没有,则创建节点(分配内存)并赋予parent的id(但parent中未加入此节点)
d. add_node(scenario_dangle, new_node): 将节点插到scenario_dangle的tail中,以我跟踪的情况,每次都是在图1中1327号节点的右边
(2) 移动节点
a. 从scenario_dangle中获取此id的node
b. 将此node从scenario_dangle中删除(不是free掉,因为没释放资源),然后会作检查,如果node没有parent,它会被销毁,free_node,释放资源。
c. 将node加入parent节点(可以为root,也可以为group节点,两者有一定的区别,主要是添加历史方面的,这个后面再谈)的tail
(3) 激活节点
a. group_active:判断是否处于active分支,是则将前一个兄弟节点设为inactive,再激活自身节点(激活发生在group_post_active事件中,具体事件的来龙去脉我当时未了解事件模式,所以没探究为什么)
(4) 删除节点
a. 判断是否可以关闭group
b. 如果可以,逐个关闭孩子节点(close_children,有个while循环会逐个执行screen节点的删除流程)
c. 在Post_event里面,在POST_EXECUTE_GROUP_EXIT分支将group节点从parent节点中去除
d. 将group加入scenario_dangle的tail
e. post_event: POST_EXECUTE_DEINIT分支下,free_history,释放gui_buffer,input_buffer
f. 从scenario_dangle中移除节点(free_node),释放资源
2.screen节点:
典型的函数:frm_scrn_enter mmi_frm_scrn_first_enter mmi_frm_scrn_close
(1) 创建节点
a. 判断parent节点中是否有此id,没有则创建,赋予parent的id
b. 设置进入和退出函数(进入函数是为了以后能重新回到此屏幕,如果设置了进入函数,节点就会加入历史)
c. 将节点加入parent节点的tail
d. 激活自身(scrn_active)
(2) 删除节点
a. 判断parent节点中是否有此id
b. 判断该节点是否为active,如果不是active,可以直接进入post_active流程删除历史和资源
c.执行execute_node_close,将节点从parent节点处移去
d.将节点加到scenario_dangle的tail,parent的id还在
POST_EVENT:
e.POST_EXECUTE_DEINIT分支,free_history,包括gui_buffer和input_buffer的释放
f.从scenario_dangle中移除节点
g.free_node
整个开机的流程如下图所示,各个除了前面介绍的几个节点id,其它节点所对应的function可以参见表:
从主菜单进入蓝牙模块的屏幕变换如下图所示:
结合这上图我再谈谈我对屏幕历史机制的理解:首先我认为历史的添加有两种,一种是进入historyData这个栈里的,一种是不进入栈,但会运行scrn_add_history更新gui_buffer和input_buffer。
纠正图中一个错误,EntryNewScreen产生的两个屏幕9ef4和3986并不会挂到ROOT节点下面,它与其他挂在ROOT下的群节点一同由历史栈来管理。
以上图为例,运行到呈现6387号屏幕节点的时候,historyData[]里共压入了4(应该是5)个节点(其中最后一个节点它只是注册了一下,有ID,进出函数,但它的屏幕节点其实还没有更新gui_buffer这样的历史数据,历史数据都得在ROOT的下一节点创建的时候才会得到更新),都是ROOT节点的直接孩子节点。这几个节点有个共同的特点,就是它们右边的节点要激活自身的时候,都会运行EntryNewScreen(这里纠正一下,好像是个叫mmi_frm_scrn_enter的函数,我现在手机没代码,不记得名字了,EntryNewScreen和scrn_group_adaptor_entry都会调用它)这个函数(如果下一个节点是group节点,会通过调用scrn_group_adaptor_entry来运行执行前一个screen节点历史数据的更新,这是root节点直接孩子节点的特性),这个函数会通过ExecuteCurrExitHandler调出前一个屏幕,再执行mmi_frm_add_history将前一个屏幕加入历史,具体的操作就是: ++currHistoryIndex和++topHistoryIndex,判断是否为group节点,如果不是,还需要通过dm_get_category_history来获取gui_buffer。
scrn_add_history这个函数我没怎么看懂,不知道怎么创建一个history的局部变量但又没赋给谁,但是结果是node里的gui_buffer就被更新了,但知道这个我就知足了。我猜想是如果通过GoBackHistory回到某个group节点的时候,group节点可以通过孩子关系找到最后一个叶子节点,然后通过节点自身保存的那些历史数据来进行屏幕绘制。
为了验证这个想法,我跟踪了从主菜单界面返回待机界面的流程,也就是上图中从bae0到6d76的流程。没有代码也没有笔记,只有凭印象写个大概吧,首先一个screen节点要关闭自身,它得借助于它的parent,0008节点。通过0008来调用group_close,这函数会先调用close_children,逐个把孩子节点给关闭,这个例子里就是主菜单界面节点。关闭完孩子之后呢,group节点自身会调用一个inactive的函数,以我对这个函数的理解,它对从来不对group节点作inactive,只会对screen作inactive,这函数好像是两种节点共用的。但是呢,group节点会在这个函数里面把好几个post_scenario_event事件给插入group里的内部队列(这个例子会插入三个事件:POST_EXECUTE_GROUP_INACTIVE,POST_EXECUTE_GROUP_EXIT,POST_EXECUTE_GROUP_DEINIT),直到出了这函数,group_close会执行一个形似post_event_ex的函数,这函数里面会把post_event逐个取出执行,按照事件ID上写的意思,一条是inactive嘛,一条是把节点从root节点挪到scenario_dangle,最后一条则是把这个节点删除。
现在问题来了,这把自己都删除了,怎么退回到前一个group节点,又怎么再通过前一个group节点找到应该显示的screen节点呢?这里就得靠historyData这个栈了,以前面对历史的研究,应该可以猜到应该要用到historyData[0]的数据,没错,在post_event执行到最后一个事件GROUP_DEINIT的时候,它会把一个事件插入到post_event队列中,这个事件是POST_EXECUTE_GOBACK_TO_ORG_HIST,所以post_event的循环处理中又多了一个事件,这个事件会运行一个函数,执行一个与gobackhistory类似的函数,把historyData[0].scrnID作为参数传进入,然后就可以找到历史栈中6d75这个group节点了,然后在它的执行流程里有个跟BACKWARD(group_event里有个很重要的state,它有好多个位来标识事件的状态,其中一个是FORWARD和BACKWARD,用来标识是正向进入screen还是从历史中返回)相关的分支里会执行对node->tail执行active_node,递归两次就可以找到需要显示6d76了。
--------------------------------------------------------------------------------------------------------------------------
这篇文章不全,发上来这部分是在家里整理的,后来在公司添加了一小部分卡在公司的PC里拷不出来。还有,文章很多细节性的地方也不一定正确,比如表格里的entry_func,不见得都是进入函数,群组节点的应该是个回调函数proc,写文更多是为了记录自己的心得体会。所以将就放在这儿吧。