S60应用程序的运行依赖于大量的OS组件,例如屏幕绘图和应用程序数据持久性等,可以直接使用OS的窗口服务器或者文件服务器即可。
应用程序核心框架类
应用程序框架由一套核心类组成,这些类是所有应用程序框架的基础。这些类封装了应用程序和所需OS服务器之间的相互作用。
第一层:CBase和CActive两个基类,其中CActive也是派生于CBase,而CActive又被第二层的CConEnv派生
第二层:包含两个基本组件:AppArc和CONE。AppArc代表“应用程序架构”,这些类提供了基本的应用程序结构、将系统信息提交到应用程序的机制,以及使用文件服务器持久化数据。其中的类在命名时都带有前缀“*Apa”。CONE是控制环境的缩写,在这个组件中的类提供用于处理用户输入并创建用户界面的机制--这些类主要用于和窗口服务器进行交互,其中的类在命名时都带有前缀“*Coe”。这一层中的许多类都是抽象类,仅仅定义了一个API的接口。
第三层:Uikon组件。这是具有丰富功能、非抽象框架类的一般性设备无关实现,并且提供了一个在所有symbian OS上公用的UI库层。一些具体的UI控件(比如列表框和滚动条等)都可以在该层创建,这些控件有时也被称为Eikon控件。UIkon组件中的类在命名时都带有前缀“*Eik”。添加了一个公共的symbian OS实现,和其他Symbian OS UI平台共享。
第四层:由Avkon类组成,这些类提供了核心的S60 UI功能,例如菜单支持。Avkon控件的类在命名时都带有前缀“*Akn”。在框架上添加S60特有的实现。
第五层:针对应用程序的层,设计自己的应用程序,添加自定义应用程序实现。
以上提到的这些类简单来说就是:视图(View)、文档(Document)、应用程序(Application)、应用程序 UI(Application UI)。那么一个S60 UI应用程序是如何用这些类来实现其有机程序呢,这个就涉及到一个应用程序框架的初始化问题。
应用程序框架初始化
必须创建下面的每个方法,才能提供最小的S60应用程序:
a、框架入口:所有S60 UI都实现一个全局函数E32DLL()(非UI类的全局入口函数为E32Main),当应用程序启动时,框架将首先调用该函数,该函数也称为DLL入口点,应用程序必须存在该函数。每个S60 UI 应用程序都是一个多态DLL。
b、新建应用程序实例:让框架调用NewApplication(),该函数是由DLL导出的唯一函数,负责创建应用程序类的一个实例,并返回它的指针,以后框架使用该指针完成应用程序的创建。这里在堆上创建应用程序实例使用的是“new”方法而不是Symbian OS常见的“new (ELeave)”方法,这是因为TRAP harness(陷阱捕捉)这时还没有创立。如果系统不能为新的实例分配内存,那返回的指针就是NULL,应用程序框架能够检查到并处理这个问题。
c、返回UID:由框架调用AppDllUid()返回应用程序的UID。该函数必须返回在.mmp文件中指定的UID值,该值可用于确定应用程序的实例是否正在运行。如果一个应用程序正在运行,而要启动这个程序的另一个实例,这时该运行的程序就会被切换到前台,而不是重新生成一个。
d、新建文档实例:框架通过框架调用应用程序类实例的CreateDocumentL()函数获取指向新创建的Document类实例的指针。通过文档实例指针,框架就可以调用文档实例的某些功能,同时文档实例也通过调用自身的NewL()和NewLC()双重静态构造函数(为什么是双重构造,这跟 Symbian的异常处理机制有关,而之所以设计成静态函数我现在也还不是很清楚)来创建自己。
e、是否装入文件:框架重新调用AppDllUid()来观察是否要从文件系统中装入一个文件。(似乎这里跟某些永久存储信息有关,目前项目还没用到这个功能,我也不知道这个该怎么用)
f、新建AppUi和View实例:框架通过调用文档对象的CreateAppUiL()方法生成了一个AppUi实例并获取指向它的指针。此处 AppUi实例创建自身时使用的是C++的默认构造函数并返回实例的指针,之后框架通过这个指针调用AppUi实例的ConstructL()函数来完成构造。而在这个ConstructL()函数中首先调用的是AppUi基类的BaseContructL()函数,处理了读入与该程序相关的资源的事宜,其次是新建需要加载的View类的实例(通过双重构造),如果不止一个View的情况下需要调用AddViewL()函数将新建的View实例加载进来,最后如果是多个View时通过SetDefaultViewL()函数设置缺省的默认视图。如读入一个与该程序相关的资源文件AppUi调用了 AppView类的NewL函数来生成其实例,这里也是用的双重构造
g、 View的重绘:通过以上的创建过程,框架已经拥有了View的无限调度权利,此时如果有一般的system事件框架就可以通过窗口服务器调用View的 Draw()函数,至于这些System事件一般指程序启动、应用程序获得焦点或者选项菜单消失等等。在这里需要注意的是编程开发者并不能直接调用 Draw,它要求在调用前系统的graphics context处于激活状态,如果编程开发者希望进行屏幕的重绘,则只能调用DrawNow()方法。还有就是Draw函数是不能异常退出的,这是因为该函数能被框架直接调用,而框架是不知道如何处理可能发生的异常的。因此Draw自己需要捕捉和处理可能发生的错误。
h、处理命令:完成以上步骤后,用户可以通过比如菜单选项之类的交互UI使框架调用HandleCommandL()函数,并传递一个参数aCommand,它指明了用户所选择的命令,HandleCommandL()会据此来判断不同的命令以分别调度执行。在此处还有一个必须注意的地方是如果要程序的 View实例也回应用户的按键,则必须将View实例通过AppUi的AddToStackL()函数加入到AppUi实例的控件堆栈(control stack)中。
对于上面的调用方式实现说明,个人觉得有必要再将AppUi的几个重要事件处理方法罗列一下:
HandleWsEventL()用于处理窗口服务器传递的事件,它的作用使框架调用下面这些具体事件处理方法;
HandKeyEvent()用于处理用户按键事件(此函数在控件堆栈为空时调用,否则框架将会调用OfferKeyEventL()函数);
HandleForegroundEventL()当应用程序切换到前台或从前台切换到后台时调用该函数,默认的实现可以处理键盘焦点的改变;
HandleSystemEventL()用于处理由窗口服务器生成的事件;
HandleApplicationSpecificEventL()用于处理自己定义的自定义事件。默认的实现可以处理颜色方案改变的通知;
HandleCommandL()用于处理用户选择的菜单项;
HandleSwitchOnEventL()用于处理像设备切换之类的特定切换事件;
HandleMessageReadyL()用于处理窗口服务器产生的消息事件。
在以上各函数处理事件中,除了第一个HandleWsEventL()函数外,其它函数默认都是纯虚函数,需要重载才能用。
通过以上对应用程序框架初始化步骤后,一个完整的框架就创建完毕了