最新资讯
敏捷与高效:手机应用程序开发模式研究
【作者】:南昌数码 【添加时间】:2007-8-3 10:36:08
一.手机应用程序开发现状
随着技术的发展,最初作为个人移动通信终端的手机正在逐渐向数字设备方向演进。从硬件平台来看,以arm IP核为代表的专门针对嵌入式系统的cpu已经广泛的用于手机上,这在极大程度上提高了手机的数据处理能力。而与此同时,flash也成为手机的一个组件,其较大的容量正好满足应用程序对数据存储空间的基本要求。正是cpu和flash在手机上的广泛使用增强了其计算能力,为能够开发各种运行于手机上的应用程序提供了最基本的支持。
当然,从软件来看,嵌入式实时操作系统和手机软件开发平台的出现对应用程序的繁荣发展也是功不可没。正是这一类系统软件的出现,将底层与硬件有关的细节屏蔽起来,完成对系统各种资源的管理和调度(内存,cpu,任务等),并提供了诸如图形系统和事件机制等支撑应用程序开发的特性。这样,嵌入式系统开发的技术门槛降低了,程序员可以专注于与应用相关的处理逻辑,可以设计非常复杂的程序。而在以前,这是不可想象的,即使一个简单的嵌入式程序也要做很多工作。
Mp3,mp4播放器,camara,u disk, bluetooth,文件管理器,e-mail,浏览器,手机电视,办公软件等应用软件都出现在手机上。因此,完全可以说手机已不再是传统意义上的手机,而是渐渐成为个人数据处理终端。而且,可以做一个大胆的设想,未来的手机完全有可能摆脱通信网的限制,成为互联网的移动终端。这种观点不是空穴来风,网络电话从技术上来说已经不成问题,现在未能普及起来更多是政策的原因以及运营商的态度,其相对较低的成本是其最大优势之一。再加上以IP技术为基础的无线宽带接入技术的成熟,也许会出现这样的产品,人们能够在旅途中通过互联网与远方的朋友进行视频通话;企业也可以利用这种产品,随时随地地组织视频会议。
随着软硬件基础平台和无线环境的逐渐发展和成熟,未来的手机应用程序具有巨大的想象空间,也许会催生出很多专业的软件供应商。
站在行业发展的大背景下再来反观我们的手机厂商和专业的手机设计公司,他们越来越强调技术的重要性,通过长期的研发投入已经有了一定程度技术积累。但仍然存在一些不足,由于这是一个年轻而又变化迅速的行业,能借鉴的开发经验和技术有限。
从目前来看,影响手机应用程序开发的因素主要有下面几条:
1.技术更新快,开发周期短。
2.手机软件开发平台多样化,接口不统一
3.市场变化快
4.软件开发模式不合适。对于大型软件而言,往往在分析阶段投入较多时间,尽可能挖掘出所有的需求。而手机开发不允许这样慢条斯理的提炼需求,往往在得到部分需求后就开始进入编码阶段。但抛弃分析和设计也不可取,开发失败原因很多时候就是前期工作没有做好,使得程序开发不停地做重复和徒劳无功的工作。
5.原型的开发代价较高。原型有利于验证方案可行性,提炼新的需求和发现设计中存在的缺陷,也有利于客户和技术人员沟通交流。但由于周期短,完成原型后所剩的时间也不多了,所以原型能否有效得到利用是一个大问题。
6.应用程序通用性差。前面已经提到,针对手机的软件开发平台种类是相当多的,有的是专业软件公司,如wince,更多的是芯片产商和手机产商,如高通的brew和nokia的symbian。这些平台的技术路线和定位都不同,而且由于接口的不统一,彼此都是封闭的系统,这样造成的结果是往往是在一个软件平台上开发的应用程序,当在另外一个平台上开发时又得重复开发。从技术来说,这个问题完全是可以避免的,但需要经验丰富
的设计者在构建软件架构时充分考虑到可移植性问题,然而遗憾的是目前国内有部分企业在嵌入式软件开发方面仍处于积累阶段,迫于时间的压力不停地做低水平的开发,从长远来看,这是不利于企业竞争实力的。
7.软件的可扩展性差,维护型差,不能从容应对瞬间变化的市场和口味挑剔的客户。举个简单例子,在windows程序里,要改改界面,改改字体或者显示效果是件很简单的事,但同样的一个小小的修改要求,对手机上的应用程序而言却是伤筋动骨。有一部分原因是某些厂家的手机软件开发平台设计地比较简陋,更多的原因还是很多企业还没有足够的能力去应对这些问题。
8.缺乏合适的嵌入式程序设计模式作为指导。当然,pc机领域的软件开发发展到今天积累很丰富的设计思想和技术,而嵌入式开发相对来说还不太成熟。有很多程序员有pc机开发经历,会将这些软件设计模式和编成技术迁移到嵌入式系统领域。但实际上,嵌入式程序有其独特的地方。传统的程序最经典的描述就是算法+数据结构了,按照设计好的流程对数据一步一步的作处理,这是一个顺序结构。但嵌入式系统是事件驱动的,对每一个事件都会对应一个代码片断,当外界产生事件时,程序会跳转到这一块代码,执行完后接着等待下一个事件。因此,如果用顺序结构来编程,程序在结构上不能表现其固有的自然属性:事件,状态,转换,动作。
二.基于状态机的开发模式
经过上述的分析和讨论,我们把目前嵌入式应用程序开发所面临的问题和症结剖析出来。接下来,给出一个行之有效的开发模式,最终目标是保证整个开发过程是敏捷和高效的。
敏捷:适应快速变化环境,当需求发生变化的时候,能够快速将需求变化转换成设计模式的变化,最终转化为代码的变化;与此同时,将程序结构中可变与不变的部分尽量独立开来,使受变化影响的模块尽可能小。
高效:在开发过程中有效沟通;合理配置开发人员;有序,稳健地推动项目实施,不论在开发的哪个环节出现新的需求,都能够做快速调整和反应,始终能够控制整个开发任务。
开发模式由MApp模版(Mobile Application Program Template)和开发流程两部分组成。提出开发模式的目的是为了使设计和开发思想以及软件结构得到重用,降低技术门槛,减少开发时间,并有利于整个开发团队形成统一的规范。开发者通过拷贝MApp 模版,就能轻而易举地利用state diagram对应用程序建模,分解任务作并行开发。
1. 状态机设计模式 和visual state简介
MApp开发模式建立的基础是状态机设计模式和visual state工具的应用,所以先对这两个概念作简单地介绍。
状态机设计模式:uml的一部分,一个用状态机模型描述的系统主要三个要素构成:事件、状态和动作。在每个状态下,系统所能接收的事件以及对事件的响应都有所不同;在事件地激励下,当条件满足时,系统可以从一个状态转换到另外一个状态。
在嵌入式领域,每个嵌入式程序都是典型的event-reactive系统,即系统是由环境传来的事件驱动的,将事件分发给处理该事件的代码片断,程序的执行路径不是顺序的。因此,用状态机思想来设计嵌入式系统是较为理想的方法,能够很自然的描述嵌入式领域所面对的问题。
visual state:IAR公司的可视化建模工具,是专门面向嵌入式领域的状态机建模工具。利用VS建立的状态机模型,能在需求和设计阶段为项目组开发人员沟通和交流提供便利。VS提供了Validator和Veirfy这些工具能够对生成的状态机模型进行检验。最后,visual state也是一个代码自动生成工具,能够根据模型生成c语言源代码,程序员不必
再像以前要自己去实现状态机模型,减少了开发工作量。
2. MApp开发模版软件结构
如图1,在基于状态机的开发模式下,一个完整的应用程序由这些模块构成:
Migrate Module:这个模块将软件开发平台(准确地说应该是开发框架)和应用程序其它部分完全隔离开来,成为两个毫不相关的部分。在移植应用程序的时候,主要修改Migrate Module,不会影响到其它的Module。
VS Engine Module:主要有如下两个功能。
1.调用semlib提供的api接口,驱动状态机模型工作,如作状态转换或者执行actions。
2.visual state system 在生成代码时也定义了数据类型和事件ID,在vs system里只能识别这些数据类型和事件ID。VS 和手机软件开发平台是两个不同的系统,因此,需要在这里把外界的参数转换为VS系统能识别的类型。
App Model:应用程序的状态机模型就包含在这个模块里,包括事件,状态,变量,转换定义和action的声明。更重要的是,里面包含状态转换表,VS Engine就是根据该表来驱动模型工作的。这个模块是visual state 根据uml语言建立的state diagram 自动生成的。该模块不依赖于其它模块
SEM Lib:visual state 提供的库函数,几个重要的接口
Action functions:以aciton为最小粒度可以把这部分模块分解为若干个action的集合。带来以下好处。
1.任务分解:action之间彼此独立的,每个程序员可以负责其中几个action。
2.最小的改动:每个action改动频率不一样的,在一个开发周期完成后,要反复修改的只是几个action,把变动限制在这部分区域,不去影响其它的action。
3.语义冲突
(1)手机软件平台与应用程序模块集成,如图2
我们把程序看成contol logic+action Implemention两部分构成,手机软件平台与应用程序模块通过Event的方式通信。很多平台都实现了事件机制,能够获得底层硬件产生的事件或者mmi自身的事件,然后将它们分发给对应的事件处理函数。contol logic是一个事件驱动的状态机模型,当手机软件平台把事件发给contol logic时,contro logical就会根据既定规则运行,调用有关的actions。
(2)语义冲突
在实际的运用中,上述方案存在一些不足,会遇到语义冲突问题,如图3。
造成语义冲突问题的主要原因是,这两个系统都是独立构建的,都分别对事件作了定义,由于没有约定,会在符号的定义出现冲突。而且没有完全消除应用程序模块接口对手机软件平台的依赖:当接收包含参数的事件时,必须包含平台的头文件,里面包含对数据类型的定义。
(3)消除语义冲突
如图4是改进后的集成方案
Independent Type Library包含与平台无关,同时也与visual state无关的符号定义。Transform将Independent Type 的数据转换成visual state system 所接受的数据类型。这样,手机软件平台与应用程序模块以Independent Type Library为中介进行通信,消除了语义冲突的可能性,同时也消除了接口对平台的依赖。
4.可重用的开发模版
(1)MApp源代码结构
图5给出了MApp模版源代码的组织结构,下面详细介绍每一部分。
MApp.c:在具体的某个应用程序里,文件名可替换为应用程序名。例如:要用该模版去开发一个MP4 Player程序,可改名为MP4_Player.c。位于Migrate Module,主要功能有,
1. 将平台相关的event 转换为独立于平台的event。
2. 在调用引擎之前,做一些预处理。例如,对有的设计较为简单的软件开发平台,可能并没有实现消息循环,那么我们就可以在这里设计一个消息循环机制来响应外界的事件。下面是给出的sample code。
//----------------------------------------------------------------------------------------------------------------
//消息循环,这不是一个很好的设计,仅用于举例说明
while(1)
{
//得到输入事件,包括来自mmi层的事件和驱动传过来的事件
Mmi_GetEvent(EventID);
//将平台类型的event转换为平台无关类型event,调用Application 接口
switch(EventID)
{
case EV_PLAY_MP4:
MP4_Player(IND_EV_PLAY_MP4);//引擎封装在里面
break;
case EV_EXIT:
return;
break;
default:
break;
}
}
//------------------------------------------------------------------------------------------------------------
VS_Engine.c:当收到外界的输入的Event时,内部的引擎开始工作。在程序设计阶段建立的状态机模型已经被一一对应的转换为c语言源代码。引擎根据建立好的模型对Event作处理或者作状态转换。
在具体的应用程序里,把模块接口函数名改为应用程序名。例如:要用该模版去开发一个MP4 Player程序,可将接口MApp改为MP4_Player()。同时要改动的是转换器,当然这是很简单的。
下面是引擎驱动state machine model工作的代码片断:
//-------------------------------------------------------------------------------------
//根据当前输入的Event,State和VS定义的规则,做提取actions的预处理
if ((cc = SEM_Deduct(eventNo)) != SES_OKAY)
return;
//依次提取出每一个action
while ((cc = SEM_GetOutput(&actionExpressNo)) == SES_FOUND)
//依次执行每一个动作
SEM_Action(actionExpressNo);
if (cc != SES_OKAY)
return;
//状态机转换到下一个状态
if ((cc = SEM_NextState()) != SES_OKAY)
return;
//-------------------------------------------------------------------------------------
注意:Visual State可以接收带参数的事件,所以与之相应,这里也同时实现了另外一套扩展接口VS_Engine_EX,可以接收事件的参数。
//-------------------------------------------------------------------------------------
//扩展接口,可传入event的参数
void VS_Engine_EX(SEM_EVENT_TYPE Para_evNo,...)
{
va_list ap;
va_start(ap, Para_evNo);
switch(eventNo)
{
//PLAY_MP4带有一个参数,指示播放第几首mp4文件
case VS_PLAY_MP4:
Para_Int=va_arg(ap, VS_INT);//得到参数
//Visual State的扩展接口
if ((cc = SEM_Deduct(eventNo,Para_Int)) != SES_OKAY)
return;
break;
//对没有参数的event,可以调用Visual State的基本接口
default:// no para
if ((cc = SEM_Deduct(eventNo)) != SES_OKAY)
return;
break;
}
va_end(ap);
}
//-------------------------------------------------------------------------------------
xxxSystem.c和xxxSysData.c:
设计阶段的状态机模型就包含在这两个文件中,它们是整个应用程序的控制逻辑,决定了代码执行路径。
在具体的应用程序里,把函数名改为建模时状态机系统名称。它们是visual state自动生成的代码,文件名会根据建模时状态机系统名称自动对应起来,无需用户关心。
SEMlibB.c
Visual State 提供的函数库。可参考visual state 提供的文档。
actionFunctionx.c:在开发模版里,程序员主要的开发任务就是实现这些actions。在一个项目里为了让多个程序员同时进行开发工作,可以划分为若干个文件:actionFunction1.c,actionFunction2.c,….。由于action之间是独立的,这样分解任务完全可行。在每个action,里程序员主要工作就是调用各种驱动程序接口完成数据的处理。下面给出一个action实现代码片断的例子。
//--------------------------------------------------------------------------------------------------------------
Play_Mp4(VS_INT Index)
{
//用户编码实现action,下面是一些sample code
Lcd_Flash(TRUE);//lcd 的驱动
//mp4解码芯片驱动的抽象接口,Mp4_Cmd(COMMAND TYPE,COMMAND PARAMETER);
Mp4_Cmd(CMD_MP4_PLAY,Index);
Mmi_Draw();//mmi层接口,刷新界面
}
//--------------------------------------------------------------------------------------------------------------
(2)目录结构:
如何组织程序文件的组织结构,对整个工程文件部署的可维护性有很重要的影响。文件的模块化,易于集成到不同工程中,也易于移动和修改。在嵌入式系统开发中,makefile文件往往都是开发者自己写的,模块化的组织结构易于编写清晰易读的makefile文件。MApp的目录结构组织如图6所示。
\MApp:应用程序的源文件,可分为两类:
1. Visual state自动生成的文件:xxxSystem.c,xxxSystemData.c,xxxSystem.h,
xxxSystemAction.h,xxxSystemData.h,SEMBDef.h,SEMTypes.h
2. 模版文件:MApp,c,VS_Engine.c,actionFunctiox.c,MApp.h,VS_Engine.h
\Restore:辅助目录,保存旧版本的作用。在工程进展中,有可能因为需求的变化要修改状态机模型,在将重新生成的文件覆盖以前文件时,为了避免意外,将旧的文件保存在该目录下。
\VS_Lib:visual state的库函数文件,是所有应用程序模块要用到的公共文件。SEMLibB.c,SEMLibB.h,VSTypes.h都在该目录下。
\VS_Model\MApp_coder:对每个应用程序,在设计阶段得到的状态机模型和自动生成的代码保存在该目录下。该目录不用加入makefile文件。
5.基于MApp模版的工程开发流程
如图7所示,软件开发是一个逐步求精的过程,而且面对一个充满变数的领域,一个更可取的软件工程思想就是:不必等到所有的需求工作完成就可以进入后续的工作。基于这种思想,在每个周期里处理新的需求和问题软件,开发要经过数个周期才能完结。
根据MApp流程,我们可以按照工作难度和工作量来合理配置开发人员。并且,我们可以根据项目需求分解任务,提高项目开发的并发度。为了有效管理软件版本,配置一台服务器来备份和共享整个工程文件。
三.模版的应用----开发一个新的应用程序
举例:手机无线遥控Cd播放器
描述:这是手机在无线局域网和数字家电设备中的一个应用。配备了bluetooth芯片的数字设备能不受电缆限制进行通信和信息共享。目前很多手机已经配置了bluetooth模块,出现一些基本的应用。家电设备正向信息化和数字化发展,将成为网络的终端实现信息的互通和共享。在这样的技术背景下,实现带有蓝芽技术的手机与cd player之间的通信,让手机成为Cd Player的控制终端,发送各种操作指令完成播放功能,并能
显示Cd Player的工作状态。
1. 系统逻辑结构
如图8所示,Obex Protocol 是bluetooth应用层协议,类似于http的作用,无线环境下的应用程序在该协议上进行通信。它是基于对象的,传输的信息单元看作一个个对象。
Virtual Cd Player Driver 是对cd player上的Driver的模拟,把对cd player的各种操作全部映射到Mobile端。这种思想类似于Linux里的逻辑设备,程序员不用关心cd player物理设备所处的位置,仿佛就是集成到手机里面了;同时Virtual Cd Player Driver屏蔽了底层的无线通信协议,程序员不用了解底层的工作原理,而且便于移植,即使将来换成WiMax协议,只需修改Virtual Cd Player Driver即可。
我们的Cd_Player App Module运行在软件平台之上,响应用户的按键事件以及底层中断传来的事件,根据状态机模型去调用action,最后这些action会调用Virtual Cd Player Driver 的API对虚拟的cd player设备进行控制。
2.开发流程:
(1)先对Mapp Moudle做一份拷贝,修改目录名Mapp为Cd_Player。然后提交到server上的project目录下。
(2)在本地机上,在Visual State里建立Cd Player的状态机模型。
用coder自动生成代码。建立的模型和程序文件提交到 \Model\Cd_Player_coder目录下。
(3).从服务器下载整个Cd_Player目录,修改事件转换器和Migrate Module。然后将整个目录提交到服务器覆盖以前版本。
(4).项目组每个程序员从服务器下载整个工程目录,开始独立的开发任务,实现每个actions。每个程序对自己的代码进行测试后,分别提交到服务器Restore目录下
(5).在某个关键时刻,由项目负责人check每个程序员提交版本,然后集成工程文件,进行build。
以上是一个基本的开发步骤,借助开发模版可以理顺整个开发流程,明确开发人员的分工和各自职责。当然,最重要的作用是能够在工程的任何阶段有效处理需求和设计变更带来的影响,实现迭代开发,逐步输出高质量的代码产品。
四.应对需求变化
在设计MApp开发模式时,各种可能面对的变化因素以及相应的应对之道都已经被考虑在内,如下表所示:
变化因素
描述
变化时刻
影响范围
处理措施
Platform
依赖平台的消息系统
移植已有的实例和创建新的实例
Migrate Module&
事件转换部分
替换相关的代码
增加处理分支
增量型
增加action
开发任何时刻
actionFunctin.c
做增量开发
Model
拓扑结构改变
状态机基本结构改变
开发任何时刻
所有自动生成代码文件
1.重新建模 2.重用actions 3.对改动较大的actions重新编码
App Module Instance
用模版创建某个具体应用程序时要做些改动
实例化时;
一次性工作
目录名,
文件名,函数名
作简单替换即可
很显然,在诸多变化因素中,除了模型拓扑结构变化以外,其它变化所影响的范围以及要增加的工作量都很小。对模型拓扑结构变化这种情况,我们作一个简单分析和讨论。
举例:当Cd_Player项目已经进入程序设计阶段时,新的需求被提出来,要求在界面显示Cd_Player的工作状态。
我们可以按照下面的步骤在应用程序里实现新的需求。
1.重新建立的状态机模型 这一步骤包含了主要工作量。我们的基本思想很简单,那就是需求改动多少,设计就改动东少,不能超过实际复杂程度。重建的状态机模型如图10所示:
2.代替旧的模块,如图11所示,
由于模块是彼此独立的,很容易将旧的模块替换掉,几乎不需要额外的重复工作。
3.对改动的action或者增加的action进行编码。在这个例子中,增加了ClearDisplayWindow,
DisplayVideoInfo,DisplayIdleInfo,所以我们要做的就是实现它们。由于模型的事件没有改变,也就是对外接口没有变化,那么VS Engine Module是不用作任何改变的。
由此可见,在MApp模版框架里,即使当模型拓扑结构发生变化时,我们也能将其造成的影响和修改范围控制在一个较小的区域,不会波及到整个应用程序。不管在项目哪个阶段,什么原因造成的变化,我们能够利用MApp模版控制工程的进展,尽可能地将需求变更带来的负面影响化解掉。
五.结束语
状态机是嵌入式领域行之有效的设计模式,而复用是我们在软件开发中孜孜以求的目标。MApp开发模式以模版的方式将这些设计思想和软件结构”固化”在里面,实现了更高层次的复用,开发人员通过拷贝就能重用这些技巧和经验,针对具体地应用拟定具体的方案。
在解决手机应用程序开发中遇到的各种问题时,我们并没有用到非常复杂的技术或者类似于cmm重型的软件工程方法,相反,我们的思想和方法都是很直观而且很容易理解的。简单实用的技术有时候也能够有效地解决我们所遇到的各种困难和挑战。
附言:
本文的研究基础是建立在状态机设计模式和Visual State uml建模工具之上。状态机设计模式属公开技术,IAR公司网上公开提供Visual State demo版,不涉及未公开技术或商业机密。
文中所用代码仅为阐述所用,是在Visual State demo版生成源码的基础上修改而成,不涉及第三方未公开的商业软件。
随着技术的发展,最初作为个人移动通信终端的手机正在逐渐向数字设备方向演进。从硬件平台来看,以arm IP核为代表的专门针对嵌入式系统的cpu已经广泛的用于手机上,这在极大程度上提高了手机的数据处理能力。而与此同时,flash也成为手机的一个组件,其较大的容量正好满足应用程序对数据存储空间的基本要求。正是cpu和flash在手机上的广泛使用增强了其计算能力,为能够开发各种运行于手机上的应用程序提供了最基本的支持。
当然,从软件来看,嵌入式实时操作系统和手机软件开发平台的出现对应用程序的繁荣发展也是功不可没。正是这一类系统软件的出现,将底层与硬件有关的细节屏蔽起来,完成对系统各种资源的管理和调度(内存,cpu,任务等),并提供了诸如图形系统和事件机制等支撑应用程序开发的特性。这样,嵌入式系统开发的技术门槛降低了,程序员可以专注于与应用相关的处理逻辑,可以设计非常复杂的程序。而在以前,这是不可想象的,即使一个简单的嵌入式程序也要做很多工作。
Mp3,mp4播放器,camara,u disk, bluetooth,文件管理器,e-mail,浏览器,手机电视,办公软件等应用软件都出现在手机上。因此,完全可以说手机已不再是传统意义上的手机,而是渐渐成为个人数据处理终端。而且,可以做一个大胆的设想,未来的手机完全有可能摆脱通信网的限制,成为互联网的移动终端。这种观点不是空穴来风,网络电话从技术上来说已经不成问题,现在未能普及起来更多是政策的原因以及运营商的态度,其相对较低的成本是其最大优势之一。再加上以IP技术为基础的无线宽带接入技术的成熟,也许会出现这样的产品,人们能够在旅途中通过互联网与远方的朋友进行视频通话;企业也可以利用这种产品,随时随地地组织视频会议。
随着软硬件基础平台和无线环境的逐渐发展和成熟,未来的手机应用程序具有巨大的想象空间,也许会催生出很多专业的软件供应商。
站在行业发展的大背景下再来反观我们的手机厂商和专业的手机设计公司,他们越来越强调技术的重要性,通过长期的研发投入已经有了一定程度技术积累。但仍然存在一些不足,由于这是一个年轻而又变化迅速的行业,能借鉴的开发经验和技术有限。
从目前来看,影响手机应用程序开发的因素主要有下面几条:
1.技术更新快,开发周期短。
2.手机软件开发平台多样化,接口不统一
3.市场变化快
4.软件开发模式不合适。对于大型软件而言,往往在分析阶段投入较多时间,尽可能挖掘出所有的需求。而手机开发不允许这样慢条斯理的提炼需求,往往在得到部分需求后就开始进入编码阶段。但抛弃分析和设计也不可取,开发失败原因很多时候就是前期工作没有做好,使得程序开发不停地做重复和徒劳无功的工作。
5.原型的开发代价较高。原型有利于验证方案可行性,提炼新的需求和发现设计中存在的缺陷,也有利于客户和技术人员沟通交流。但由于周期短,完成原型后所剩的时间也不多了,所以原型能否有效得到利用是一个大问题。
6.应用程序通用性差。前面已经提到,针对手机的软件开发平台种类是相当多的,有的是专业软件公司,如wince,更多的是芯片产商和手机产商,如高通的brew和nokia的symbian。这些平台的技术路线和定位都不同,而且由于接口的不统一,彼此都是封闭的系统,这样造成的结果是往往是在一个软件平台上开发的应用程序,当在另外一个平台上开发时又得重复开发。从技术来说,这个问题完全是可以避免的,但需要经验丰富
的设计者在构建软件架构时充分考虑到可移植性问题,然而遗憾的是目前国内有部分企业在嵌入式软件开发方面仍处于积累阶段,迫于时间的压力不停地做低水平的开发,从长远来看,这是不利于企业竞争实力的。
7.软件的可扩展性差,维护型差,不能从容应对瞬间变化的市场和口味挑剔的客户。举个简单例子,在windows程序里,要改改界面,改改字体或者显示效果是件很简单的事,但同样的一个小小的修改要求,对手机上的应用程序而言却是伤筋动骨。有一部分原因是某些厂家的手机软件开发平台设计地比较简陋,更多的原因还是很多企业还没有足够的能力去应对这些问题。
8.缺乏合适的嵌入式程序设计模式作为指导。当然,pc机领域的软件开发发展到今天积累很丰富的设计思想和技术,而嵌入式开发相对来说还不太成熟。有很多程序员有pc机开发经历,会将这些软件设计模式和编成技术迁移到嵌入式系统领域。但实际上,嵌入式程序有其独特的地方。传统的程序最经典的描述就是算法+数据结构了,按照设计好的流程对数据一步一步的作处理,这是一个顺序结构。但嵌入式系统是事件驱动的,对每一个事件都会对应一个代码片断,当外界产生事件时,程序会跳转到这一块代码,执行完后接着等待下一个事件。因此,如果用顺序结构来编程,程序在结构上不能表现其固有的自然属性:事件,状态,转换,动作。
二.基于状态机的开发模式
经过上述的分析和讨论,我们把目前嵌入式应用程序开发所面临的问题和症结剖析出来。接下来,给出一个行之有效的开发模式,最终目标是保证整个开发过程是敏捷和高效的。
敏捷:适应快速变化环境,当需求发生变化的时候,能够快速将需求变化转换成设计模式的变化,最终转化为代码的变化;与此同时,将程序结构中可变与不变的部分尽量独立开来,使受变化影响的模块尽可能小。
高效:在开发过程中有效沟通;合理配置开发人员;有序,稳健地推动项目实施,不论在开发的哪个环节出现新的需求,都能够做快速调整和反应,始终能够控制整个开发任务。
开发模式由MApp模版(Mobile Application Program Template)和开发流程两部分组成。提出开发模式的目的是为了使设计和开发思想以及软件结构得到重用,降低技术门槛,减少开发时间,并有利于整个开发团队形成统一的规范。开发者通过拷贝MApp 模版,就能轻而易举地利用state diagram对应用程序建模,分解任务作并行开发。
1. 状态机设计模式 和visual state简介
MApp开发模式建立的基础是状态机设计模式和visual state工具的应用,所以先对这两个概念作简单地介绍。
状态机设计模式:uml的一部分,一个用状态机模型描述的系统主要三个要素构成:事件、状态和动作。在每个状态下,系统所能接收的事件以及对事件的响应都有所不同;在事件地激励下,当条件满足时,系统可以从一个状态转换到另外一个状态。
在嵌入式领域,每个嵌入式程序都是典型的event-reactive系统,即系统是由环境传来的事件驱动的,将事件分发给处理该事件的代码片断,程序的执行路径不是顺序的。因此,用状态机思想来设计嵌入式系统是较为理想的方法,能够很自然的描述嵌入式领域所面对的问题。
visual state:IAR公司的可视化建模工具,是专门面向嵌入式领域的状态机建模工具。利用VS建立的状态机模型,能在需求和设计阶段为项目组开发人员沟通和交流提供便利。VS提供了Validator和Veirfy这些工具能够对生成的状态机模型进行检验。最后,visual state也是一个代码自动生成工具,能够根据模型生成c语言源代码,程序员不必
再像以前要自己去实现状态机模型,减少了开发工作量。
2. MApp开发模版软件结构
如图1,在基于状态机的开发模式下,一个完整的应用程序由这些模块构成:
Migrate Module:这个模块将软件开发平台(准确地说应该是开发框架)和应用程序其它部分完全隔离开来,成为两个毫不相关的部分。在移植应用程序的时候,主要修改Migrate Module,不会影响到其它的Module。
VS Engine Module:主要有如下两个功能。
1.调用semlib提供的api接口,驱动状态机模型工作,如作状态转换或者执行actions。
2.visual state system 在生成代码时也定义了数据类型和事件ID,在vs system里只能识别这些数据类型和事件ID。VS 和手机软件开发平台是两个不同的系统,因此,需要在这里把外界的参数转换为VS系统能识别的类型。
App Model:应用程序的状态机模型就包含在这个模块里,包括事件,状态,变量,转换定义和action的声明。更重要的是,里面包含状态转换表,VS Engine就是根据该表来驱动模型工作的。这个模块是visual state 根据uml语言建立的state diagram 自动生成的。该模块不依赖于其它模块
SEM Lib:visual state 提供的库函数,几个重要的接口
Action functions:以aciton为最小粒度可以把这部分模块分解为若干个action的集合。带来以下好处。
1.任务分解:action之间彼此独立的,每个程序员可以负责其中几个action。
2.最小的改动:每个action改动频率不一样的,在一个开发周期完成后,要反复修改的只是几个action,把变动限制在这部分区域,不去影响其它的action。
3.语义冲突
(1)手机软件平台与应用程序模块集成,如图2
我们把程序看成contol logic+action Implemention两部分构成,手机软件平台与应用程序模块通过Event的方式通信。很多平台都实现了事件机制,能够获得底层硬件产生的事件或者mmi自身的事件,然后将它们分发给对应的事件处理函数。contol logic是一个事件驱动的状态机模型,当手机软件平台把事件发给contol logic时,contro logical就会根据既定规则运行,调用有关的actions。
(2)语义冲突
在实际的运用中,上述方案存在一些不足,会遇到语义冲突问题,如图3。
造成语义冲突问题的主要原因是,这两个系统都是独立构建的,都分别对事件作了定义,由于没有约定,会在符号的定义出现冲突。而且没有完全消除应用程序模块接口对手机软件平台的依赖:当接收包含参数的事件时,必须包含平台的头文件,里面包含对数据类型的定义。
(3)消除语义冲突
如图4是改进后的集成方案
Independent Type Library包含与平台无关,同时也与visual state无关的符号定义。Transform将Independent Type 的数据转换成visual state system 所接受的数据类型。这样,手机软件平台与应用程序模块以Independent Type Library为中介进行通信,消除了语义冲突的可能性,同时也消除了接口对平台的依赖。
4.可重用的开发模版
(1)MApp源代码结构
图5给出了MApp模版源代码的组织结构,下面详细介绍每一部分。
MApp.c:在具体的某个应用程序里,文件名可替换为应用程序名。例如:要用该模版去开发一个MP4 Player程序,可改名为MP4_Player.c。位于Migrate Module,主要功能有,
1. 将平台相关的event 转换为独立于平台的event。
2. 在调用引擎之前,做一些预处理。例如,对有的设计较为简单的软件开发平台,可能并没有实现消息循环,那么我们就可以在这里设计一个消息循环机制来响应外界的事件。下面是给出的sample code。
//----------------------------------------------------------------------------------------------------------------
//消息循环,这不是一个很好的设计,仅用于举例说明
while(1)
{
//得到输入事件,包括来自mmi层的事件和驱动传过来的事件
Mmi_GetEvent(EventID);
//将平台类型的event转换为平台无关类型event,调用Application 接口
switch(EventID)
{
case EV_PLAY_MP4:
MP4_Player(IND_EV_PLAY_MP4);//引擎封装在里面
break;
case EV_EXIT:
return;
break;
default:
break;
}
}
//------------------------------------------------------------------------------------------------------------
VS_Engine.c:当收到外界的输入的Event时,内部的引擎开始工作。在程序设计阶段建立的状态机模型已经被一一对应的转换为c语言源代码。引擎根据建立好的模型对Event作处理或者作状态转换。
在具体的应用程序里,把模块接口函数名改为应用程序名。例如:要用该模版去开发一个MP4 Player程序,可将接口MApp改为MP4_Player()。同时要改动的是转换器,当然这是很简单的。
下面是引擎驱动state machine model工作的代码片断:
//-------------------------------------------------------------------------------------
//根据当前输入的Event,State和VS定义的规则,做提取actions的预处理
if ((cc = SEM_Deduct(eventNo)) != SES_OKAY)
return;
//依次提取出每一个action
while ((cc = SEM_GetOutput(&actionExpressNo)) == SES_FOUND)
//依次执行每一个动作
SEM_Action(actionExpressNo);
if (cc != SES_OKAY)
return;
//状态机转换到下一个状态
if ((cc = SEM_NextState()) != SES_OKAY)
return;
//-------------------------------------------------------------------------------------
注意:Visual State可以接收带参数的事件,所以与之相应,这里也同时实现了另外一套扩展接口VS_Engine_EX,可以接收事件的参数。
//-------------------------------------------------------------------------------------
//扩展接口,可传入event的参数
void VS_Engine_EX(SEM_EVENT_TYPE Para_evNo,...)
{
va_list ap;
va_start(ap, Para_evNo);
switch(eventNo)
{
//PLAY_MP4带有一个参数,指示播放第几首mp4文件
case VS_PLAY_MP4:
Para_Int=va_arg(ap, VS_INT);//得到参数
//Visual State的扩展接口
if ((cc = SEM_Deduct(eventNo,Para_Int)) != SES_OKAY)
return;
break;
//对没有参数的event,可以调用Visual State的基本接口
default:// no para
if ((cc = SEM_Deduct(eventNo)) != SES_OKAY)
return;
break;
}
va_end(ap);
}
//-------------------------------------------------------------------------------------
xxxSystem.c和xxxSysData.c:
设计阶段的状态机模型就包含在这两个文件中,它们是整个应用程序的控制逻辑,决定了代码执行路径。
在具体的应用程序里,把函数名改为建模时状态机系统名称。它们是visual state自动生成的代码,文件名会根据建模时状态机系统名称自动对应起来,无需用户关心。
SEMlibB.c
Visual State 提供的函数库。可参考visual state 提供的文档。
actionFunctionx.c:在开发模版里,程序员主要的开发任务就是实现这些actions。在一个项目里为了让多个程序员同时进行开发工作,可以划分为若干个文件:actionFunction1.c,actionFunction2.c,….。由于action之间是独立的,这样分解任务完全可行。在每个action,里程序员主要工作就是调用各种驱动程序接口完成数据的处理。下面给出一个action实现代码片断的例子。
//--------------------------------------------------------------------------------------------------------------
Play_Mp4(VS_INT Index)
{
//用户编码实现action,下面是一些sample code
Lcd_Flash(TRUE);//lcd 的驱动
//mp4解码芯片驱动的抽象接口,Mp4_Cmd(COMMAND TYPE,COMMAND PARAMETER);
Mp4_Cmd(CMD_MP4_PLAY,Index);
Mmi_Draw();//mmi层接口,刷新界面
}
//--------------------------------------------------------------------------------------------------------------
(2)目录结构:
如何组织程序文件的组织结构,对整个工程文件部署的可维护性有很重要的影响。文件的模块化,易于集成到不同工程中,也易于移动和修改。在嵌入式系统开发中,makefile文件往往都是开发者自己写的,模块化的组织结构易于编写清晰易读的makefile文件。MApp的目录结构组织如图6所示。
\MApp:应用程序的源文件,可分为两类:
1. Visual state自动生成的文件:xxxSystem.c,xxxSystemData.c,xxxSystem.h,
xxxSystemAction.h,xxxSystemData.h,SEMBDef.h,SEMTypes.h
2. 模版文件:MApp,c,VS_Engine.c,actionFunctiox.c,MApp.h,VS_Engine.h
\Restore:辅助目录,保存旧版本的作用。在工程进展中,有可能因为需求的变化要修改状态机模型,在将重新生成的文件覆盖以前文件时,为了避免意外,将旧的文件保存在该目录下。
\VS_Lib:visual state的库函数文件,是所有应用程序模块要用到的公共文件。SEMLibB.c,SEMLibB.h,VSTypes.h都在该目录下。
\VS_Model\MApp_coder:对每个应用程序,在设计阶段得到的状态机模型和自动生成的代码保存在该目录下。该目录不用加入makefile文件。
5.基于MApp模版的工程开发流程
如图7所示,软件开发是一个逐步求精的过程,而且面对一个充满变数的领域,一个更可取的软件工程思想就是:不必等到所有的需求工作完成就可以进入后续的工作。基于这种思想,在每个周期里处理新的需求和问题软件,开发要经过数个周期才能完结。
根据MApp流程,我们可以按照工作难度和工作量来合理配置开发人员。并且,我们可以根据项目需求分解任务,提高项目开发的并发度。为了有效管理软件版本,配置一台服务器来备份和共享整个工程文件。
三.模版的应用----开发一个新的应用程序
举例:手机无线遥控Cd播放器
描述:这是手机在无线局域网和数字家电设备中的一个应用。配备了bluetooth芯片的数字设备能不受电缆限制进行通信和信息共享。目前很多手机已经配置了bluetooth模块,出现一些基本的应用。家电设备正向信息化和数字化发展,将成为网络的终端实现信息的互通和共享。在这样的技术背景下,实现带有蓝芽技术的手机与cd player之间的通信,让手机成为Cd Player的控制终端,发送各种操作指令完成播放功能,并能
显示Cd Player的工作状态。
1. 系统逻辑结构
如图8所示,Obex Protocol 是bluetooth应用层协议,类似于http的作用,无线环境下的应用程序在该协议上进行通信。它是基于对象的,传输的信息单元看作一个个对象。
Virtual Cd Player Driver 是对cd player上的Driver的模拟,把对cd player的各种操作全部映射到Mobile端。这种思想类似于Linux里的逻辑设备,程序员不用关心cd player物理设备所处的位置,仿佛就是集成到手机里面了;同时Virtual Cd Player Driver屏蔽了底层的无线通信协议,程序员不用了解底层的工作原理,而且便于移植,即使将来换成WiMax协议,只需修改Virtual Cd Player Driver即可。
我们的Cd_Player App Module运行在软件平台之上,响应用户的按键事件以及底层中断传来的事件,根据状态机模型去调用action,最后这些action会调用Virtual Cd Player Driver 的API对虚拟的cd player设备进行控制。
2.开发流程:
(1)先对Mapp Moudle做一份拷贝,修改目录名Mapp为Cd_Player。然后提交到server上的project目录下。
(2)在本地机上,在Visual State里建立Cd Player的状态机模型。
用coder自动生成代码。建立的模型和程序文件提交到 \Model\Cd_Player_coder目录下。
(3).从服务器下载整个Cd_Player目录,修改事件转换器和Migrate Module。然后将整个目录提交到服务器覆盖以前版本。
(4).项目组每个程序员从服务器下载整个工程目录,开始独立的开发任务,实现每个actions。每个程序对自己的代码进行测试后,分别提交到服务器Restore目录下
(5).在某个关键时刻,由项目负责人check每个程序员提交版本,然后集成工程文件,进行build。
以上是一个基本的开发步骤,借助开发模版可以理顺整个开发流程,明确开发人员的分工和各自职责。当然,最重要的作用是能够在工程的任何阶段有效处理需求和设计变更带来的影响,实现迭代开发,逐步输出高质量的代码产品。
四.应对需求变化
在设计MApp开发模式时,各种可能面对的变化因素以及相应的应对之道都已经被考虑在内,如下表所示:
变化因素
描述
变化时刻
影响范围
处理措施
Platform
依赖平台的消息系统
移植已有的实例和创建新的实例
Migrate Module&
事件转换部分
替换相关的代码
增加处理分支
增量型
增加action
开发任何时刻
actionFunctin.c
做增量开发
Model
拓扑结构改变
状态机基本结构改变
开发任何时刻
所有自动生成代码文件
1.重新建模 2.重用actions 3.对改动较大的actions重新编码
App Module Instance
用模版创建某个具体应用程序时要做些改动
实例化时;
一次性工作
目录名,
文件名,函数名
作简单替换即可
很显然,在诸多变化因素中,除了模型拓扑结构变化以外,其它变化所影响的范围以及要增加的工作量都很小。对模型拓扑结构变化这种情况,我们作一个简单分析和讨论。
举例:当Cd_Player项目已经进入程序设计阶段时,新的需求被提出来,要求在界面显示Cd_Player的工作状态。
我们可以按照下面的步骤在应用程序里实现新的需求。
1.重新建立的状态机模型 这一步骤包含了主要工作量。我们的基本思想很简单,那就是需求改动多少,设计就改动东少,不能超过实际复杂程度。重建的状态机模型如图10所示:
2.代替旧的模块,如图11所示,
由于模块是彼此独立的,很容易将旧的模块替换掉,几乎不需要额外的重复工作。
3.对改动的action或者增加的action进行编码。在这个例子中,增加了ClearDisplayWindow,
DisplayVideoInfo,DisplayIdleInfo,所以我们要做的就是实现它们。由于模型的事件没有改变,也就是对外接口没有变化,那么VS Engine Module是不用作任何改变的。
由此可见,在MApp模版框架里,即使当模型拓扑结构发生变化时,我们也能将其造成的影响和修改范围控制在一个较小的区域,不会波及到整个应用程序。不管在项目哪个阶段,什么原因造成的变化,我们能够利用MApp模版控制工程的进展,尽可能地将需求变更带来的负面影响化解掉。
五.结束语
状态机是嵌入式领域行之有效的设计模式,而复用是我们在软件开发中孜孜以求的目标。MApp开发模式以模版的方式将这些设计思想和软件结构”固化”在里面,实现了更高层次的复用,开发人员通过拷贝就能重用这些技巧和经验,针对具体地应用拟定具体的方案。
在解决手机应用程序开发中遇到的各种问题时,我们并没有用到非常复杂的技术或者类似于cmm重型的软件工程方法,相反,我们的思想和方法都是很直观而且很容易理解的。简单实用的技术有时候也能够有效地解决我们所遇到的各种困难和挑战。
附言:
本文的研究基础是建立在状态机设计模式和Visual State uml建模工具之上。状态机设计模式属公开技术,IAR公司网上公开提供Visual State demo版,不涉及未公开技术或商业机密。
文中所用代码仅为阐述所用,是在Visual State demo版生成源码的基础上修改而成,不涉及第三方未公开的商业软件。
页数:1
