在Symbian下开发程序这么久,但是一直不清楚UI程序框架的具体实现是怎样的。虽然有一些参考书籍讲解了大体的结构,但是没有具体看到实现的Code,还是没有那么具体的理解。开源使得我有机会自己去看看,UI程序框架到底是怎样的。
这篇文章是一个系列的如下文章,这是第一篇。
(1)UI程序结构
(3)窗口管理及绘制过程
【UI程序基本结构及用到的核心库】
了解Symbian UI程序框架的人,都会很熟悉下图。从下图,我们大概可以看出,初始化过程中会首先创建CAknApplication,再通过CAknApplication创建CAknDocument,接着创建CAknViewAppUi,最后会创建CAknView及控件(实际写程序时,创建的都是这些类的派生类)。下面我们跟着Code走,把这个过程解释的更详细些。
写UI程序,一般都会引用这几个库:
库名称 | 功能 | 代码位置 |
apparc.lib | 提供了Application/Document管理的基本代码 | /FCL /sf /mw /appsupport /appfw /apparchitecture /group /APPARC.MMP |
cone.lib | 是UI程序框架的基础库,包括CCoeEnv等的实现 | /FCL /sf /mw /classicui /lafagnosticuifoundation /cone /group /CONE.MMP |
eikcore.lib | 在cone.lib基础上继续封装,包括CEikApplication等CEikXXX类的实现。程序的起始函数EikStart::RunApplication在这个库中的EikStart.cpp中 | /FCL/sf/mw/classicui/commonuisupport/uikon/group/EikCore.mmp |
avkon.lib | 在eikcore.lib上继续封装,包括CAknApplication等CAknXXX类的实现 | /FCL /sf /mw /classicui /uifw /AvKon /group /avkon.mmp |
其他 | eikdlg.lib、eikctl.lib、aknicon.lib等,实现各个层次的具体控件或对话框功能 | /FCL /sf /mw /classicui / |
从上面我们可以看出,UI程序的基础库实际有三个,它们的关系如下。应用程序一般使用Ackon库中的类实现,而Avkon的实现是基于Uikon的,Uikon库基于CONE库。Uikon库的实现同时还用到了AppArc库,用以注册应用程序信息到应用程序Server中。每个应用程序在初始化过程中,都会创建一个CEikonEnv(控件环境)对象,控件管理的功能及程序运行过程的控制,基本上都是由这个对象完成的。这个对象的实现代码主要在Eikcore和CONE库中,它在创建时,会创建一个指向窗口服务器的本地session,用以和窗口服务器通信。程序本地链接的各个库完成了应用程序初始化及执行过程控制的功能,窗口消息的获得及绘制过程等,都是由窗口服务器完成的。
下面我们来看程序框架的实现代码,从源头开始,先看看EikStart::RunApplication函数的实现,它的实现在文件/FCL/sf/mw/classicui/commonuisupport/uikon/coresrc/EIKSTART.cpp 中。我们只看关键代码,其他忽略。关键代码是创建CEikonEnv对象,及调用它的Execute函数。创建CEikonEnv对象的过程,实际是初始化程序基本组成部分的过程,CAknApplication,CAknDocument及CAknApplicationUi的派生类对象的创建都是在这个过程完成的。调用CEikonEnv::Execute函数,实际是启动程序执行过程。CEikonEnv是一个活动对象,它负责从窗口服务器获取Event,然后分发给对应的View或者控件处理。
【CEikonEnv的重要信息】
1. 下面是CEikonEnv类的派生关系,可以看出CEikonEnv基于CCoeEnv实现,并且是一个活动对象。后面我们会发现,整个程序的事件分发实际是依赖这个活动对象的。
2. 每个应用程序只有一个CEikonEnv对象,可通过CCoeEnv::Static()访问。CCoeEnv在创建时,会把this指针保存到线程私有数据中,以后要通过CCoeEnv::Static()访问时,从线程私有数据中获取即可。具体代码可参考文件/FCL /sf /mw /classicui /lafagnosticuifoundation /cone /src /COEMAIN.CPP
3. CCoeEnv对象有以下成员变量,可以供应用程序使用,以下成员变量的意义见下面注释。
CCoeAppUi* iAppUi; //指向用户的CAknApplicationUi派生类,CCoeEnv在分发事件时会用到它
RFs iFsSession; //应用程序共有的file session,一般情况下,你不需要再在你的实现代码中新建file session
RWsSession iWsSession; //与窗口服务器通信的session,例如从窗口服务器获取Event就需要用到它
RWindowGroup iRootWin; //指向程序根窗口,如果你需要向程序本窗口发消息,你可以用这个变量
【UI程序的初始化过程】
上面以及说了,UI程序初始化过程,实际是创建CEikonEnv对象的过程,我们来看看这个过程中发生了什么?程序在E32Main函数中会调用EikStart::RunApplication,并以一个能创建CAknApplication派生类的函数指针为参数。EikStart::RunApplication中创建CEikConEnv时,会再次传递这个参数,同时传递的还有程序的命令行参数。
- CCoeEnv创建过程
CEikonEnv的创建过程包括父类CCoeEnv的创建和它的自己的创建过程。CCoeEnv相关的代码在文件/FCL /sf /mw /classicui /lafagnosticuifoundation /cone /src /COEMAIN.CPP 中。我们这里只简要列一下,其构造过程中完成的动作。CCoeEnv::CCoeEnv()中把this指针保存到了线程私有数据,并且创建了清理栈(在UI程序框架下,你就不需要自己创建清理栈了)。CCoeEnv::ConstructL中完成的动作比较多,如下:
1. 创建活动对象调度器,并把自己加到活动对象调度器中。(在UI程序框架下,你就不需要自己创建活动对象调度器了)
2. 用iFsSession连接到文件服务器。
3. 用iWsSession连接到窗口服务器。
4. 基于iWsSession,初始化iRootWin。
5. 创建Graphics Context。
6. 调用RequestEventNotification,准备从窗口服务器获取Event。RequestEventNotification代码如下。
void CCoeEnv::RequestEventNotification()
/** Asks WSERV to give notification when an event is waiting */
{
iWsSession.EventReady(&iStatus);
SetActive();
}
- CEikonEnv创建过程
CEikonEnv的创建过程在文件/FCL/sf/mw/classicui/commonuisupport/uikon/coresrc/EIKENV.cpp 中的函数CEikonEnv::ConstructAppFromCommandLineL中,下面列出一些关键代码。
…
// Create the application process
iProcess = CEikProcess::NewL(iFsSession, aCommandLine.ParentProcessId());
…
// Create the document object using the application factory
CEikDocument& doc = *static_cast(iProcess->AddNewDocumentL(aApplicationFactory));
iProcess->SetMainDocument(&doc);
UpdateTaskNameL();
…
doc.PrepareToEditL(); // creates AppUi
请注意,这里的CEikProcess并不是一个进程,而只是application framework下的一个对象而已。它主要提供应用程序的Application/Document对象的管理功能,包括程序启动时文档加载和对象的创建,程序运行过程中文档的切换和对象的更新,管理程序时文档的保存和对象的销毁等。我们来看看AddNewDocumentL函数的实现,在CEikProcess的基类CApaProcess中(文件/FCL /sf /mw /appsupport /appfw /apparchitecture /apparc /apaproc.cpp )。以下是其代码,主要完成两件事情:AddAppL中调用传入的函数指针创建CAknApplication(还记得EikStart::RunApplication函数带的参数吗?传到这里了),CreateDocL中调用刚刚新建的CAknApplication对象的成员函数CreateDocumentL创建CAknDocument对象。
EXPORT_C CApaDocument* CApaProcess::AddNewDocumentL(TApaApplicationFactory aApplicationFactory)
{
__SHOW_TRACE(_L("Starting CApaProcess::AddNewDocumentL"));
__APA_PROFILE_START(0);
RApaApplication* app = AddAppL(aApplicationFactory);
// use the app to create a doc
CApaDocument* doc = NULL;
TRAPD(err, doc = CreateDocL(app->Application()));
if (err)
RemoveApp(app); // remove app as it has been orphaned
User::LeaveIfError(err);
__PROFILE_END(0);
return doc;
}
至此,初始化过程完成了。静态的程序结构已经创建完成,剩下的事情就是怎样让它run起来。
【UI程序框架是怎样控制程序执行的?】
与程序执行相关的代码主要有两个地方:一是前面提到的在创建CCoeEnv的最后一步,调用RequestEventNotification准备从窗口服务器获取Event;二是完成CEikonEnv之后,调用它的Execute成员函数。Execute成员函数的实现在CCoeEnv中(/FCL /sf /mw /classicui /lafagnosticuifoundation /cone /src /COEMAIN.CPP ),CEikonEnv没有重写。查看代码可以发现,Execute函数只是启动了活动对象调度器而已,真正的动作要等到窗口服务器返回Event才真正开始。窗口服务器返回Event后,会调用CCoeEnv::RunL,其主要代码如下。
TWsEvent event;
iWsSession.GetEvent(event);
RequestEventNotification(); //Request now so that WSERV has time to respond (on SMP systems)
// before the return to the Active Scheduler
const TUint handle=event.Handle();
240 if (handle)
241 {
242 CCoeControl* const window=IsHandleValid(handle)? reinterpret_cast<CCoeControl*>(handle) : NULL;
243 iLastEvent=event;
244 iAppUi->MonitorWsEvent(event);
245 //coverity[var_deref_model]
//Passing a NULL value of window to this function is allowed because the window is not always
//dereferenced by this function.
247 //The window will not be NULL if it is to be dereferenced by this function.
248 iAppUi->HandleWsEventL(event,window);
}
这段代码会首先调用iWsSession.GetEvent从窗口服务器获得Event,然后再次调用RequestEventNotification准备收取Event,最后调用iAppUi->HandleWsEventL处理获得的Event。至于处理Event的详细过程,这是另一篇的内容,此处暂略。