在我们使用BREW进行开发的时候,我们必须彻底的了解两个概念:应用程序流程和在我们的应用程序中可以使用的接口。因为这些主题对于BREW开发来说是至关重要的,所以在这一节里将介绍这些内容。如果说前几章让我们窥见了BREW的全貌,那么从这一节开始将正式带您进入BREW的应用程序开发。
7.3.1理解BREW应用程序流程
正如我们前面所见到的一样,我们的应用程序是基于事件驱动机制的,也就是说,我们的应用程序通过BREW平台发送过来的事件开始运行的。这些事件不但包含了诸如控件发送过来的用户接口事件,而且还包含了描述应用程序诸如启动和停止等外部行为的事件,例如接收一条短消息或者启动一个应用程序。因此,我们的应用程序的中心是一个叫做_HandleEvent的事件捕获函数。应用程序通过这个事件捕获函数获得系统中的事件(更为准确的说法应该是BREW平台通过这个函数将事件传递给应用程序),并通过这些事件检测系统的运行状态(如按键、启动应用程序等等)。进一步的,我们的应用程序判断这些传进来的事件,并决定如何处理这些事件(捕获则返回TRUE,不捕获则返回FALSE)。如果我们的应用程序在某个事件中并不希望进行任何处理,通常情况下应首先将它传递给应用程序正在使用的接口(如控件),然后再返回处理结果。这样可以让应用程序中使用的接口获得处理事件的机会。
在我们的应用程序可以捕获事件之前,我们首先需要在系统中注册我们的事件捕获函数。由于我们的应用程序实际上是一个BREW接口的实例,并且事件捕获函数不过是这个接口中的一个方法而已,因此我们必须在应用程序接口中增加一个指向我们的应用程序事件捕获函数的引用。虽然这并不是一件困难的事,而且是每一个BREW应用程序都必须去做的事情,但是BREW还是提供了一个助手函数用来帮助我们实现这个应用程序接口,包括注册一个事件捕获函数。
所有的这一切都发生在我们的应用程序载入的时候。当BREW试图载入一个应用程序的时候,它将执行每一个应用程序的AEEClsCreateInstance函数。在我们的应用程序的这个函数中,将判断传入的Class ID是否与我们应用程序的Class ID相同,如果相同则创建一个自身的实例。典型的我们可以通过调用BREW的助手函数AEEApplet_New函数来实现这些功能。这个AEEApplet_New函数将在幕后为我们的应用程序分配存储空间、实现应用程序接口并返回一个我们的应用程序实例。下面就是一个简单的AEEClsCreateInstance函数:
int AEEClsCreateInstance( AEECLSID clsID, IShell * pIShell, IModule * po, void ** ppObj ) { boolean result; *ppObj = NULL; // 如果Class ID符合我的应用程序...
if( clsID == AEECLSID_MYCLASSID ) { // 使用BREW助手函数创建应用程序实例
result = AEEApplet_New( sizeof( AEEApplet ), clsID, pIShell, po, (IApplet**)ppObj, (AEEHANDLER) HandleEvent, NULL ); } return result ? AEE_SUCCESS : EFAILED; }
以面向对象的思想去考虑,我们可以将AEEClsCreateInstance函数理解为一个创建对象实例的对象工厂,它负责创建一个指定类的实例。在我们的应用程序中实现的AEEClsCreateInstance函数仅仅是一个系统调用的类方法,在其他的应用程序请求启动我们的应用程序时,系统将会调用它。注意,无论我们是在创建一个应用程序或者一个扩展接口(与其它应用程序共享的一个BREW接口),这些启动的处理都是一样的需要通过AEEClsCreateInstance函数,只不过扩展接口不需要注册事件捕获函数,因此不必调用AEEApplet_New函数了。在下一部分“一识庐山真面目”里,将会详细的剖析这一启动过程,届时将进一步增加我们对BREW平台的理解。
一旦我们已经创建了我们应用程序的实例,BREW系统将会调用我们应用程序的事件捕获函数,传递各种BREW事件。通常,在我们的应用程序中必须处理以下的几个事件:
1、EVT_APP_START事件。在应用程序启动时,我们在应用程序中注册的事件捕获函数将会接收到这个事件,这表示我们的应用程序已经开始运行了。在我们的应用程序中,可以在这个事件中进行创建接口,或者分配内存空间等操作。
2、EVT_APP_STOP事件。在我们的应用程序结束时将接收到这个事件,表示应用程序已经停止运行了。我们应该在应用程序收到这个事件的时候,释放全部分配的内存和和创建的接口实例等资源。
3、EVT_APP_SUSPEND事件。在我们的应用程序接收到这个事件的时候,它表示应用程序需要中断执行。这种情况通常发生在我们在当前的应用程序中启动了另一个应用程序,或者在我们的应用程序运行过程中收到了一个电话等需要打断当前应用程序运行的情况下。在这个事件中,我们需要保存应用程序中的相关状态数据,用于在应用程序恢复执行时恢复程序的状态。此事件过后,应用程序进入挂起状态。
4、EVT_APP_RESUME事件。在我们的应用程序从中断执行(挂起)状态返回到运行状态时,将会收到这个事件。在这个事件中我们需要根据在EVT_APP_SUSPEND事件中保存的状态数据恢复应用程序的执行状态。此事件之后,应用程序就处于正常的活动状态了。
下面将介绍一下其他相关的BREW系统事件,对于每一个BREW事件,如果有 wParam和dwParam参数,将被传递给给定的小程序和控件。如果我们处理这个事件,需要在事件处理函数中返回TRUE,否则返回FALSE。这些事件如下所示:
事件名称 |
所属类型 |
描述 |
EVT_APP_START |
系统事件 |
启动应用程序的事件,dwParam = ( AEEAppStart * ) |
EVT_APP_STOP |
系统事件 |
应用程序停止,无参数 |
EVT_APP_SUSPEND |
系统事件 |
应用程序挂起,无参数 |
EVT_APP_RESUME |
系统事件 |
应用程序恢复,dwParam = ( AEEAppStart * ) |
EVT_APP_CONFIG |
系统事件 |
切换应用程序,显示配置界面 |
EVT_APP_HIDDEN_CONFIG |
系统事件 |
切换应用程序,显示隐藏配置界面 |
EVT_APP_BROWSE_URL |
系统事件 |
在EVT_APP_START之后调用,dwParam = (const AECHAR * pURL) |
EVT_APP_BROWSE_FILE |
系统事件 |
在 EVT_APP_START 之后调用,dwParam = (const AECHAR * pszFileName) |
EVT_APP_MESSAGE |
系统事件 |
文本消息,wParam = AEESMSEncoding,dwParam取决于 wParam 值的字符串格式 |
EVT_APP_TERMINATE |
系统事件 |
EVT_APP_STOP的强制版本。小程序将被释放。 |
EVT_EXIT |
系统事件 |
在BREW终止时发送给所有已加载的小程序 |
EVT_APP_NO_CLOSE |
系统事件 |
应用程序不应关闭 |
EVT_APP_NO_SLEEP |
系统事件 |
应用程序正在运行 - 运行应用程序很长时间之后调用 |
EVT_KEY |
按键事件 |
按键事件,wParam = 按键代码 |
EVT_KEY_PRESS |
按键事件 |
按键按下事件,wParam = 按键代码,发生在EVT_KEY事件之前 |
EVT_KEY_RELEASE |
按键事件 |
按键抬起事件,wParam = 按键代码,发生在EVT_KEY事件之后 |
EVT_CHAR |
按键事件 |
字符事件,wParam表示发生事件的宽字符 |
EVT_UPDATECHAR |
按键事件 |
字符更新事件,wParam表示发生事件的宽字符 |
EVT_COMMAND |
控件事件 |
自定义控件的事件,wParam表示发生事件的Item ID值。如菜单按下时发生此事件。 |
EVT_CTL_TAB |
控件事件 |
控件焦点切换事件,dwParam = 控件;wParam = 0-向左顺序切换,1-向右顺序切换 |
EVT_CTL_SET_TITLE |
控件事件 |
设置标题的消息接口,wParam = ID;dwParam = 资源文件(如果 ID != 0)或文本 |
EVT_CTL_SET_TEXT |
控件事件 |
设置文本的消息接口, wParam = ID;dwParam = 资源文件(如果 ID != 0)或文本 |
EVT_DIALOG_INIT |
对话框事件 |
对话框初始化事件,创建控件,预初始化值、标记和其它项,wParam = ID;dwParam = IDialog * |
EVT_DIALOG_START |
对话框事件 |
对话框打开事件,wParam = ID;dwParam = IDialog * |
EVT_DIALOG_END |
对话框事件 |
对话框正常结束事件,wParam = ID;dwParam = IDialog * |
EVT_COPYRIGHT_END |
对话框事件 |
版权对话框结束事件 |
EVT_ALARM |
Shell事件 |
闹钟事件,wParam表示闹钟序号 |
EVT_NOTIFY |
Shell事件 |
通知事件,dwParam = AEENotify * |
EVT_BUSY |
Shell事件 |
发送到应用程序以确定是否可以中止或停止该应用程序。 |
EVT_FLIP |
设备事件 |
设备翻盖事件,wParam = TRUE(打开);FALSE(关闭) |
EVT_KEYGUARD |
设备事件 |
键盘锁事件,wParam = TRUE(键盘锁定打开) |
EVT_HEADSET |
设备事件 |
耳机事件,wParam == TRUE(已插入耳机,否则为 FALSE) |
EVT_PEN_DOWN |
设备事件 |
触摸笔按下事件,dwParam = 点笔的位置: 高16位表示x坐标,低16位表示y坐标 |
EVT_PEN_MOVE |
设备事件 |
触摸笔移动事件,dwParam = 点笔的位置: 高16位表示x坐标,低16位表示y坐标 |
EVT_PEN_UP |
设备事件 |
触摸笔抬起事件,dwParam = 点笔的位置: 高16位表示x坐标,低16位表示y坐标 |
EVT_PEN_STALE_MOVE |
设备事件 |
触摸笔事件过多时发送,应用程序通常忽略这个事件。dwParam = 点笔的位置: 高16位表示x坐标,低16位表示y坐标 |
EVT_USER |
用户事件 |
用户定义事件(应用程序私有) |
除了上面的这些BREW系统预定义事件外,在BREW SDK中AEE.h文件中还定义了其他的一些事件,有兴趣的读者可以详细的查看这个文件。我们也可以定义属于我们自己应用程序独有的事件,这些事件通常以EVT_USER事件作为起始序号,这样可以避免与系统事件之间的冲突。
通常,一个事件处理函数的形式如下面的代码所示:
|
static boolean HandleEvent( IApplet *pi, AEEEvent eCode, uint16 wParam, uint32 dwParam ) { MyAppTyp * pMe = (AEEApplet*)pi; // 决定如何处理收到的事件
switch (eCode){ // 应用程序启动
case EVT_APP_START: // 进行相关的初始化操作
return TRUE; // 挂起应用程序
case EVT_APP_SUSPEND: // 保存数据用于恢复应用程序
return TRUE; // 恢复应用程序到运行状态
case EVT_APP_RESUME: // 恢复挂起时的应用程序状态
return TRUE; // 应用程序关闭
case EVT_APP_STOP: // 释放资源
return TRUE; default: break; } return FALSE; }
|
在事件处理函数中,使用一个switch语句来分发不同的事件,这样的程序既运行高效又划分的清楚。最简单的一个BREW应用程序是由两个函数组成的:AEEClsCreateInstance和一个对应的事件捕获函数。这两个函数共同组成了应用程序的可执行区域,但是,应用程序的数据在什么地方呢?
BREW平台与其它很多轻量级的应用程序平台一样,都不支持全局变量。这既是一个祝福也是一个诅咒。没有全局变量,我们的应用程序将易于调试和维护;然而,不幸的问题是如果没有全局变量,我们如何存放那些不需要通过函数堆栈传递的数据呢?(不支持全局变量也会引起其他的一些问题,如对于静态变量的管理,C编译器将它同全局变量一样对待,因此也不能够在函数中使用静态的变量。因此我们必需注意在应用程序的任何部分都不能使用静态变量,否则即便可以在模拟器中正常运行,但是也不能在BREW设备上正常运行。)
做为一种替代全局变量的方法,我们可以定义一个包含应用程序变量的结构体,在这个结构体中我们可以保存应用程序的状态数据以及创建的接口实例指针等全局内容。在运行时为我们的这个结构体分配存储空间,这样在我们的应用程序看来,它就有了存储全局数据的地方了。BREW平台就是这样做的,我们可以在AEEClsCreateInstance函数中调用AEEApplet_New的地方,指定我们自己的结构体来做为创建应用程序实例时的数据结构。为了能够这样做,在我们的应用程序接口体中定义的第一个成员变量必须是AEEApplet结构体,这样就相当于我们的结构体是AEEApplet结构的一个超集,只有这样才不会影响到正常的应用程序实例的创建。一旦AEEApplet_New执行完毕之后,就已经为我们的应用程序结构体分配了存储空间,并为第一个成员变量AEEApplet填充了数据。这样,我们就可以得到如下的一个简单的应用程序代码了:
typedef struct _MyAppTyp { AEEApplet a; // 应用程序信息结构体
uint32 m_launchTime, m_nEvents; // 应用程序指定的数据
} MyAppTyp; int AEEClsCreateInstance( AEECLSID clsID, IShell * pIShell, IModule * po, void ** ppObj ) { boolean result; *ppObj = NULL; // 如果Class ID符合我的应用程序...
if( clsID == AEECLSID_MYCLASSID ) { // 使用BREW助手函数创建应用程序实例
result = AEEApplet_New( sizeof(MyAppTyp), clsID, pIShell, po, (IApplet**)ppObj, (AEEHANDLER) HandleEvent, NULL ); } return result ? AEE_SUCCESS : EFAILED; } static boolean HandleEvent( IApplet *pi, AEEEvent eCode, uint16 wParam, uint32 dwParam ) { MyAppTyp *pMe = (MyAppTyp )pi; pMe->m_nEvents++; // 决定如何处理收到的事件
switch (eCode){ // 应用程序启动
case EVT_APP_START: pMe->m_launchTime = GETTIMESECONDS(); pMe->m_nEvents = 1; return TRUE; // 挂起应用程序
case EVT_APP_SUSPEND: return TRUE; // 恢复应用程序到运行状态
case EVT_APP_RESUME: return TRUE; // 应用程序关闭
case EVT_APP_STOP: DBGPRINTF( "Application ran for %ld seconds.", GETTIMESECONDS() - pMe->m_launchTime ); DBGPRINTF( "Application received %ld events.", pMe->m_nEvents ); return TRUE; default: break; } return FALSE; }
|
在这个例子里面,应用程序的结构体中,除了AEEApplet结构外,就包含了两个变量,用来记录起始运行时间和收到的事件数量。这个应用程序记录了接收的事件数量以及运行的时间,在应用程序退出之后通过DBGPRINTF函数在调试窗口中输出这些信息。与前面提供的函数不同的地方在于调用AEEApplet_New函数时分配空间的结构体从AEEApplet变为了MyAppTyp结构体。与此相对应的,HandleEvent函数接收的结构体指针就属于MyAppTyp结构体的指针了。由于AEEApplet结构体定义在MyAppTyp结构体的顶端,因此MyAppTyp结构体指针可以安全的转换为AEEApplet结构体的指针。在应用程序中,由于实际上创建的是一个MyAppTyp的存储空间,因此通过HandleEvent函数第一个参数(IApplet *)传递进来的指针可以转换为MyAppTyp类型,这样我们就可以在应用程序中使用MyAppTyp中的数据成员了。
经过这种方式处理的应用程序中,既包含了程序的运行代码,又包含了程序的数据存储空间,由此组成了一个完整的可执行应用程序。
ps:以上内容来自 《深入BREW开发》 作者:焦玉海
阅读(2215) | 评论(0) | 转发(0) |