《深入BREW开发》——第七章 创建新的BREW应用程序

来源:互联网 发布:sql server 删除约束 编辑:程序博客网 时间:2024/04/30 00:38
第七章 创建新的BREW应用程序
       在第六章中,我们介绍了Applet和模块的相关内容,并且熟悉了BREW开发环境,那么现在是开始创建一个属于我们自己的应用程序的时候了。在BREW中创建应用程序最简单的方式是,通过BREW在Visual Studio C++环境中的应用程序向导。通过向导,可以一步一步的让我们构建成功开发BREW应用程序所需要的组件。接下来我们就详细的介绍一下BREW应用程序的开发方法。
7.1 写在开发前面的话
       在进行真正的BREW应用程序开发之前,有一些开发的注意事项需要事先讲明。这样可以让我们避免一些经常性的错误,从而减少开发调试的时间。这些注意事项主要包含如下几个方面:
       1、从Windows的模拟器移植到BREW设备的问题
       2、在BREW设备上会出现问题,而在BREW模拟器中不必检测的
       3、良好的BREW编程习惯
       通过对这些注意事项的检查,可减少从Wondows到BREW设备上的移植任务,同时让程序可以在BREW设备上正确运行。详细内容如下表:

注意事项
详细描述
执行空指针检查
关于这一条有如下两个方面:
1、在使用ISHELL_CreateInstance()或其他接口函数创建接口实例后,一定要进行接口指针有效性的检查,如果指针异常,则不能够使用这个接口
2、检查全部的指针以确认指针有效性,包括传入的、使用BREW方法创建的和分配内存后的。不可用的指针将引起系统的致命错误。
避免堆栈溢出
由于系统分配给BREW运行环境的堆栈空间是有限的,因此不要在堆栈中分配大数组,也就是说,不要再一个函数的局部变量中声明大数组。如果确实在程序中需要这样的数组,那么,请使用分配内存的方式。
不要使用大的循环
当一个循环运行的时间足够长时,将引起BREW设备的复位。基于这样的原因,不能在一个BREW事件里进行耗时很长的处理。
尽可能的使应用程序成为设备独立的
为了使我们的应用程序可以独立于设备的内存大小、键盘数量、颜色深度和屏幕尺寸等因素而独立运行,请使用ISHELL_GetDeviceInfo()方法来获得设备的规格,从而根据不同的规格处理不同的应用程序。
使用资源文件
使用资源文件存储指定语言的字符串、对话框和图片。这样可以方便的进行不同语言版本的开发,而不影响代码本身。
释放内存
由于在BREW设备上的可用内存是有限的,因此清理不使用的内存就十分重要了。有如下两种情况:
1、必须释放全部创建的实例
2、应用程序终止时必须释放全部分配的内存
推荐在内存不再使用的时候就释放掉。
不要使用全局或静态变量
由于全局和局部变量在BREW动态应用程序中无法处理,因此基于这样的BREW架构不支持全局和静态变量,而且全局和静态变量也会引起BREW设备环境下的链接错误。请在应用程序的结构体中声明这些数据。
不要在定义的时候初始化一个结构体
与上面不能使用全局和静态变量一样,在定义的时候初始化一个结构体也会引起BREW设备环境下的一个链接错误。
不要直接使用C语言的浮点运算
BREW平台不支持浮点运算。虽然这些运算在模拟器中运行良好,那时因为在Windows环境下,在BREW设备环境下,通常不支持浮点运算。如果确实需要使用的话,请用BREW提供的助手函数进行运算。
使用BREW支持的标准库函数
为了可以让我们的程序所占的空间最小,请不要使用目标设备的库函数,而是使用BREW提供相应的助手函数。
避免类型转换错误
由于目标设备的编译器对于类型的检查要严格,因此,请明确声明变量转换的类型,以防止从Windows环境移植到BREW设备环境下产生错误。
检查返回值
如果我们调用的BREW API含有返回值,建议处理这些返回值,这样可以让我们所写的程序更加健壮。

7.2 创建一个BREW应用程序
       在这一节里,我们将首先建立一个叫做HelloWorld的应用程序,然后分析这个程序当中的各个要素。也正是从这个例子开始,将正式的带您进入BREW的开发世界。在这本书里,我们使用的环境是Visual Studio .Net开发环境,当然我们也可以使用Visual Studio6.0来创建,并且他们之间看上去没什么太多的不同。
7.2.1建立应用程序
       可以采用如下的步骤建立HelloWorld应用程序:
       1、我们可以到高通公司的网站上去获取一个名叫HelloWorld的应用程序的Class ID,如果您还没有成为一个授权的BREW应用程序开发者,那么您可以将BREW SDK样例程序HelloWorld中的HelloWorld.bid复制过来。
       2、使用画图工具或者其他的图片编辑工具为这个应用程序创建图标。图像分为大、中、小三种,我们可以指定bmp、png、bci或jpg图片,图片的大小没有特殊的要求,我们只需要根据您的喜好选定就可以了。不过当程序运行在设备上时,不同的设备由不同的要求,您可以查看该设备的规格说明。
       3、打开Visual Studio2003。
       4、打开BREW应用程序向导。通过选择文件->新建->项目菜单,打开创建项目对话框,选择Visual C++项目下的BREWAppWizard项目。在对话框的下部输入应用程序名和应用程序存储路径。点击确定按钮,此时BREW的应用程序向导启动。
       5、由于我们的应用程序仅需要刷新屏幕,因此保留全部的设置直接点击完成。
       6、载入MIF文件编辑器,为应用程序创建MIF文件。
7、在MIF文件编辑器的Applets选项卡中,新建Applet,并选择我们已经获取的helloworld.bid文件作为次应用程序的Class ID。
8、选择应用程序的类型为“Tools”。
9、设置我们在第二步创建的三个图标做为应用程序的图标。
10、保存文件为helloworld.mfx文件,同时选择“Build->Compile MIF Script”菜单或者按“F5键,编译二进制文件。由于此命令输出的文件名采用的是DOS短文件名方式,因此请手动修改文件名。
11、关闭MIF文件编辑器,返回Visual Studio界面。
12、在“项目->helloworld项目属性”对话框的常规选项中,将输出目录置为空,以便于生成的DLL文件可以在根目录中生成。
13、在“解决方案资源管理器”中,打开helloworld.c文件。同时输入如下内容:

helloworld.c
/*====================================================================
FILE: helloworld.c
====================================================================*/
 
/*====================================================================
INCLUDES AND VARIABLE DEFINITIONS
====================================================================*/
#include "AEEModGen.h"          // Module interface definitions
#include "AEEAppGen.h"          // Applet interface definitions
#include "AEEShell.h"           // Shell interface definitions
 
#include "helloworld.bid"
 
/*-------------------------------------------------------------------
Applet structure. All variables in here are reference via "pMe->"
-------------------------------------------------------------------*/
// create an applet structure that's passed around. All variables in
// here will be able to be referenced as static.
typedef struct _helloworld {
       AEEApplet      a ;             // First element of this structure must be AEEApplet
    AEEDeviceInfo DeviceInfo; // always have access to the hardware device information
 
   // add your own variables here...
} helloworld;
 
/*-------------------------------------------------------------------
Function Prototypes
-------------------------------------------------------------------*/
static boolean helloworld_HandleEvent( helloworld* pMe,
                                  AEEEvent eCode, uint16 wParam,
                                  uint32 dwParam);
boolean helloworld_InitAppData(helloworld* pMe);
void    helloworld_FreeAppData(helloworld* pMe);
 
/*====================================================================
FUNCTION DEFINITIONS
==================================================================== */
 
/*====================================================================
FUNCTION: AEEClsCreateInstance
 
DESCRIPTION
       This function is invoked while the app is being loaded. All Modules must provide this
       function. Ensure to retain the same name and parameters for this function.
       In here, the module must verify the ClassID and then invoke the AEEApplet_New() function
       that has been provided in AEEAppGen.c.
 
   After invoking AEEApplet_New(), this function can do app specific initialization. In this
   example, a generic structure is provided so that app developers need not change app specific
   initialization section every time except for a call to IDisplay_InitAppData().
   This is done as follows: InitAppData() is called to initialize AppletData
   instance. It is app developers responsibility to fill-in app data initialization
   code of InitAppData(). App developer is also responsible to release memory
   allocated for data contained in AppletData -- this can be done in
   IDisplay_FreeAppData().
 
PROTOTYPE:
   int AEEClsCreateInstance(AEECLSID ClsId,IShell * pIShell,IModule * po,void ** ppObj)
 
PARAMETERS:
       clsID: [in]: Specifies the ClassID of the applet which is being loaded
 
       pIShell: [in]: Contains pointer to the IShell object.
 
       pIModule: pin]: Contains pointer to the IModule object to the current module to which
       this app belongs
 
       ppObj: [out]: On return, *ppObj must point to a valid IApplet structure. Allocation
       of memory for this structure and initializing the base data members is done by AEEApplet_New().
 
DEPENDENCIES
 none
 
RETURN VALUE
 AEE_SUCCESS: If the app needs to be loaded and if AEEApplet_New() invocation was
     successful
 EFAILED: If the app does not need to be loaded or if errors occurred in
     AEEApplet_New(). If this function returns FALSE, the app will not be loaded.
 
SIDE EFFECTS
 none
====================================================================*/
int AEEClsCreateInstance(AEECLSID ClsId, IShell *pIShell, IModule *po, void **ppObj)
{
       *ppObj = NULL;
 
       if( ClsId == AEECLSID_HELLOWORLD )
       {
              // Create the applet and make room for the applet structure
              if( AEEApplet_New(sizeof(helloworld),
                          ClsId,
                          pIShell,
                          po,
                          (IApplet**)ppObj,
                          (AEEHANDLER)helloworld_HandleEvent,
                          (PFNFREEAPPDATA)helloworld_FreeAppData) )
              {
                     //Initialize applet data, this is called before sending EVT_APP_START
            // to the HandleEvent function
                     if(helloworld_InitAppData((helloworld*)*ppObj))
                     {
                            //Data initialized successfully
                            return(AEE_SUCCESS);
                     }
                     else
                     {
                            //Release the applet. This will free the memory allocated for the applet when
                            // AEEApplet_New was called.
                            IAPPLET_Release((IApplet*)*ppObj);
                            return EFAILED;
                     }
        } // end AEEApplet_New
    }
       return(EFAILED);
}
 
/*====================================================================
FUNCTION SampleAppWizard_HandleEvent
 
DESCRIPTION
       This is the EventHandler for this app. All events to this app are handled in this
       function. All APPs must supply an Event Handler.
 
PROTOTYPE:
       boolean SampleAppWizard_HandleEvent(IApplet * pi, AEEEvent eCode, uint16 wParam, uint32 dwParam)
 
PARAMETERS:
       pi: Pointer to the AEEApplet structure. This structure contains information specific
       to this applet. It was initialized during the AEEClsCreateInstance() function.
 
       ecode: Specifies the Event sent to this applet
 
   wParam, dwParam: Event specific data.
 
DEPENDENCIES
 none
 
RETURN VALUE
 TRUE: If the app has processed the event
 FALSE: If the app did not process the event
 
SIDE EFFECTS
 none
====================================================================*/
static boolean helloworld_HandleEvent(helloworld* pMe, AEEEvent eCode, uint16 wParam, uint32 dwParam)
       AECHAR szBuf[] = {'H','e','l','l','o',' ',
                      'W','o','r','l','d','/0'};
 
    switch (eCode)
       {
        // App is told it is starting up
        case EVT_APP_START:
                  // Add your code here...
                     // Clear the display.
                     IDISPLAY_ClearScreen( pMe->a.m_pIDisplay );
                     // Display string on the screen
                     IDISPLAY_DrawText( pMe->a.m_pIDisplay, // What
                                                    AEE_FONT_BOLD,            // What font
                                                    szBuf,                    // How many chars
                                                    -1, 0, 0, 0,              // Where & clip
                                                    IDF_ALIGN_CENTER | IDF_ALIGN_MIDDLE );
                     // Redraw the display to show the drawn text
                     IDISPLAY_Update (pMe->a.m_pIDisplay);
            return(TRUE);
 
        // App is told it is exiting
        case EVT_APP_STOP:
            // Add your code here...
                 return(TRUE);
 
        // App is being suspended
        case EVT_APP_SUSPEND:
                  // Add your code here...
                 return(TRUE);
 
        // App is being resumed
        case EVT_APP_RESUME:
                  // Add your code here...
 
                 return(TRUE);
 
        // A key was pressed. Look at the wParam above to see which key was pressed. The key
        // codes are in AEEVCodes.h. Example "AVK_1" means that the "1" key was pressed.
        case EVT_KEY:
                  // Add your code here...
                 return(TRUE);
 
        // If nothing fits up to this point then we'll just break out
        default:
            break;
   }
 
   return FALSE;
}
 
// this function is called when your application is starting up
boolean helloworld_InitAppData(helloworld* pMe)
{
    // Get the device information for this handset.
    // Reference all the data by looking at the pMe->DeviceInfo structure
    // Check the API reference guide for all the handy device info you can get
    pMe->DeviceInfo.wStructSize = sizeof(pMe->DeviceInfo);
    ISHELL_GetDeviceInfo(pMe->a.m_pIShell,&pMe->DeviceInfo);
 
    // Insert your code here for initializing or allocating resources...
 
    // if there have been no failures up to this point then return success
    return TRUE;
}
 
// this function is called when your application is exiting
void helloworld_FreeAppData(helloworld* pMe)
{
    // insert your code here for freeing any resources you have allocated...
}

       这个文件当中的代码大部分是经过BREW向导产生的,为了方便排版,我对里面的代码进行了一些小改动。由于这是第一次展示BREW应用程序,因此保留了向导生成的注释等内容,目的是让您能够看到BREW应用的全貌。今后的应用程序展示,将只使用骨干程序,以节省版面空间并减少对您钱财的浪费。
       正如前面我们所见的,在这个文件中共有四个BREW骨干函数存在,它们分别是:AEEClsCreateInstance、helloworld_HandleEvent、helloworld_InitAppData以及 helloworld_FreeAppData。它们在整个BREW应用程序中扮演着不同的角色,而且AEEClsCreateInstance 和***_HandleEvent函数还是每一个动态应用程序必不可少的。接下来让我们来看看它们各自的作用以及相互之间的关系。
       AEEClsCreateInstance(AEECLSID ClsId, IShell *pIShell, IModule *po, void **ppObj)函数是整个BREW应用程序的一个入口点,在其内部调用了AEEApplet_New函数用来创建应用程序实例。AEEApplet_New函数在 AEEAppGen.c文件中,这个函数可以指定一个事件处理函数和一个资源释放函数,在这个程序中指定的分别是 helloworld_HandleEvent和helloworld_FreeAppData函数。在AEEClsCreateInstance函数中将几个重要的参数传进了BREW应用程序中,如IShell指针pIShell、要创建的Class ID ClsId、模块指针po以及应用程序实例返回指针ppObj等。我们还可以看到在AEEApplet_New函数调用成功后,将执行 helloworld_InitAppData函数,用来初始化数据,这就是这个初始化函数得到执行的地方。
       在helloworld.c文件中,还定义了一个helloworld结构体。由于BREW实现原理上的特点,这个结构体就成为了BREW应用程序存储全局变量的地方。如果我们需要在我们的应用程序运行期间保存数据,请在这个结构体中增加变量,而不要使用全局函数。在运行时,AEEApplet_New函数会根据这个结构体的大小分配存储空间。这个结构体中默认声明的第一个变量是AEEApplet结构,这个声明的顺序不可以改变。AEEApplet的定义如下(参考AEEAppGen.h文件):

struct _AEEApplet
{
   //
   // NOTE: These 3 fields must be declared in this order!
  //
   DECLARE_VTBL(IApplet) 
   AEECLSID       clsID;
  
   uint32         m_nRefs;                    // Applet reference counter
   IShell    *    m_pIShell;        // pointer to IShell
   IModule   *    m_pIModule;             // pointer to IModule
       IDisplay *    m_pIDisplay;      // pointer to IDisplay
 
       //Pointer to Handle Event Function
   AEEHANDLER     pAppHandleEvent;
 
// Pointer to FreeAppData function. This will be invoked when the
   // reference count of the App goes to zero. This function is supplied
   // by the app developer.
   // NOTE: Apps should NOT directly call their FreeAppData function.
   PFNFREEAPPDATA pFreeAppData;
};

       这个结构体中的成员变量都是在AEEApplet_New函数中进行填充的,我们可以看到里面有 IDisplay接口指针定义,由于对于每一个BREW应用程序来说,IDisplay接口是必备的,因此AEEApplet_New函数里面会创建这个接口的实例,这样我们就不必在我们的应用程序中再次创建这些接口了。在这个结构体里面还有一个AEEHANDLER类型的变量,这其实是一个函数指针,用来保存我们通过AEEApplet_New函数传递进来的应用程序中捕获事件的函数,在这里面指定的是helloworld_HandleEvent函数。还有一个PFNFREEAPPDATA类型的变量,这也是一个回调函数的变量,用来保存释放资源的函数,在应用程序退出时将会调用这个函数执行指定的资源释放函数,在本例中这个函数就是helloworld_FreeAppData函数。m_pIShell和m_pIModule变量分别存储了 BREW Shell和模块接口的指针,我们可以在调用BREW Shell接口函数(定义在AEEShell.h中的ISHELL_开头的接口)的时候可以使用这个指针,因为IShell指针是通过应用程序的入口函数传递进来的,而不需要创建(IShell是不能够自己创建自己的)。关于IModule指针我们会在第三部分的Shell内幕中有详细的介绍,这里就不多说了。其他的变量是BREW规定的,在这里面我们也不多说了(我们才刚刚开始啊)。
       在BREW应用程序启动后,会首先向应用程序中发送EVT_APP_START事件,这个事件是应用程序收到的第一个BREW事件。由于我们指定的应用程序事件捕获函数是helloworld_HandleEvent,因此在这里将由此函数处理这个事件。在这个事件里我们可以进行应用程序的初始化操作,例如,显示一个界面、创建一个接口实例等等。在本例中我们调用了三个IDisplay接口的方法:IDISPLAY_ClearScreen和IDISPLAY_ DrawText以及IDISPLAY_Update方法,它们的作用依次是清除屏幕显示、在屏幕上绘制文字以及刷新屏幕。运行后,这段代码会在屏幕的中央显示“Hello World”字样。在处理进行完成之后,需要使用“return TRUE”来告知BREW应用程序已经启动成功了,可以接收其他事件了。否则表示应用程序启动失败了,程序将不能正常运行。
       在应用程序启动成功之后,可以接收BREW传递给应用程序的其他事件了,例如用户点击了键盘,则会有 EVT_KEY事件发送。当应用程序结束的时候,BREW会向应用程序的事件捕获函数发送EVT_APP_STOP事件。如果我们在 EVT_APP_START事件中创建了接口实例,那么与此相对应的,我们应该在EVT_APP_STOP事件中释放这些接口实例。这个事件也要求捕获者返回TRUE,以表示应用程序已经成功停止运行了。注意,BREW内部支持的Unicode的字符编码,Unicode编码是国际标准的双字节字符编码,越来越多的系统都开始支持Unicode编码了。双字节宽度的Unicode字符与单字节的ASCII编码相比,Unicode可以支持最多65536个字符(单字节最多256个),几乎可以囊括全世界不同语言的全部字符,中文从Unicode编码的0x4E00编号开始。
       现在我们只需要简单的几步就已经创建了一个BREW应用程序了,不要惊奇,一切就是这么简单!
7.2.2测试应用程序
在创建完成一个BREW应用程序之后,我们的第一个任务就是要在BREW模拟其中运行我们的应用程序。首先我们需要在Visual Studio程序中生成应用程序。在Visual Studio中生成应用程序的时候,会创建一个.dll的动态链接库文件,BREW模拟器通过载入这个文件而执行我们的应用程序。使用Visual Studio生成应用程序是十分容易的,我们只需要选择“生成->生成解决方案”就可以了。
选择“调试->启动”菜单项编译连接程序,则可以进入调试运行模式,在这种模式下我们可以使用Visual Studio的调试功能跟踪我们的应用程序执行。由于我们生成的BREW应用程序是一个单独的DLL文件,因此,在我们第一次运行调试程序的时候,需要在调试运行程序的对话框中输入模拟器文件BREW_Simulator.exe做为运行DLL文件的可执行文件,设置对话框如图7.1所示。
图7.1 选择运行应用程序的模拟器程序
       一旦我们生成了我们的应用程序之后,我们就需要配置模拟器,用来运行我们的应用程序了。我们需要指定我们的应用程序的MIF文件路径和应用程序所在的路径,而且应用程序的.dll文件必须在与MIF文件名同名的文件夹下面。就以本例为例,我们指定的 MIF文件路径是Code/hellowrold,而应用程序的路径则是Code/,因为本例中MIF文件在helloworld的路径下面。当然我们也可以把MIF文件复制到其他的地方,然后指定这个路径作为MIF路径。
       对于初学者来说可能遇到不能启动应用程序并提示“为了节省空间模块已经被移除”的提示,其实这是由于在我们指定的应用程序路径下面没有找到相关的DLL文件所致。通常的原因是我们将DLL的输出路径设置在了默认的helloworld/debug下面了。解决这个问题有两种方法,一种是将这个文件手动的转移到helloworld路径下,另外就是更改项目的输出路径。更改的方法请参照上一节中建立应用程序的第12步进行设置。
       可以参照如下的步骤来测试我们的应用程序,这样的分步方式会让我们觉得使用模拟器更加简单一些:
       1、使用Visual Studio中的“调试->启动”或者按F5键运行应用程序。
       2、当Visual Studio提示选择执行DLL的应用程序时,请选择BREW模拟器所在的文件。
       3、打开模拟器的属性页面。如果没有属性页面可以通过View -> Properties打开。
       4、在属性页面的“Properties”选项卡中,设置“MIF directory different from Applet Dirctory”项目为“Yes”。
       5、设置MIF文件路径为我们的应用程序MIF文件所在的位置。
       6、设置应用程序路径为我们的应用程序所在文件夹的上一层路径。注意应用程序所在的路径必须与MIF文件名相同。
       7、使用模拟器中的设备按键,选择我们的应用程序开始运行。
       在本例中,我们启动HelloWorld应用程序之后,将会在模拟器的设备屏幕的中间看见“Hello World”字样,此时证明我们的应用程序已经运行成功了。我们可以点击模拟器上的End键退出当前的应用程序。
7.2.3在设备上运行应用程序
乍一看来,在一个BREW设备上运行我们的应用程序是一件十分浪费时间的事情,但是在一个真实的硬件环境中运行我们的应用程序是十分重要的,因为这样做可以让我们检测到应用程序在模拟器中没有遇到的错误,尤其是在我们自然不自然的使用 Visual Studio库函数的时候。不过不幸的是,让我们的应用程序运行在一个BREW设备上,并不是一件轻松的事情,因为我们必须联系高通公司的工程师去获得我们所需要的BREW设备。如果我们需要在一个还不能从网络下载应用程序的BREW设备(通常指手机)上运行我们的应用程序,我们必须首先按照如下步骤进行操作:
1、从高通公司那里获得我们所需要的手机及其规格说明和模拟器设备文件。
2、针对该设备的规格,使用其模拟器设备文件进行模拟开发。
3、生成BREW设备上可以运行的二进制.mod文件,生成本地的Class ID和MIF文件等。参照上一节的相关描述。
4、从设备的规格说明上获得设备BREW菜单的使用方法,打开BREW设备的BREW测试模式(通常在一个叫做BREW Flags的菜单内,选中BREW_TEST_ENABLE项目)。
5、从高通的测试网页上获取测试用的签名文件,这个签名文件通常与BREW设备的ESN有关,这个ESN通常使用一个贴纸贴在设备的某个地方。
6、使用BREW Apploader应用程序将应用程序载入BREW设备中。
7、在BREW设备的一个叫做“应用程序管理器”的应用程序中打开我们的应用程序,此时我们就可以在BREW设备上测试应用程序了。
由于要在一个BREW设备上测试应用程序,必须首先成为一个授权的BREW开发用户,这样我们才能够获得BREW Apploader和测试用的签名文件。因此我们需要访问高通的BREW网站去查看详细的如何成为一个授权用户信息,并按照要求进行注册。成为一名授权的 BREW应用程序开发者之后,我们不但可以获得测试等相关的BREW工具,而且我们还可以购买一定数量的Class ID。关于如何使用BREW Apploader应用程序以及如何为设备生成应用程序的问题,您可以查看后面章节的介绍。
要想做到上面的这些事情,实在是一个让人头痛的事情,或许这是高通公司基于商业和应用程序安全的角度的考虑。不过,如果BREW平台可以变得更为开放一些,或许将会在嵌入式开发平台领域产生更大的影响力。
7.3 BREW开发初步
       在我们使用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中的数据成员了。
       经过这种方式处理的应用程序中,既包含了程序的运行代码,又包含了程序的数据存储空间,由此组成了一个完整的可执行应用程序。
7.3.2理解BREW接口
       与大多数面向对象的平台一样,BREW平台中的各种接口均继承自一个通用的接口。图7.2 列举出了一部分BREW接口的继承关系:
图7.2 BREW接口继承关系
       图7.2中列出了BREW接口中一部分具有继承关系的接口,这些列出来的接口只是BREW众多接口中的一小部分,不过确是最常用的一部分接口。从图的左边到右边,按照箭头方向依次是从基类接口到派生接口。派生接口的实例可以调用其基类接口定义的方法进行调用。所有的BREW接口均继承自IBase接口,用来进行最为基本的资源管理。IBase接口提供了两个方法IBASE_AddRef和 IBASE_Release。对于任何一个BREW接口的指针,我们都可以使用IBASE_AddRef接口来增加接口指针的引用计数;使用 IBASE_Release接口减少接口指针引用计数,当接口的引用计数变为0之后,将释放这个接口所占用的资源。
       通过这种接口之间的继承关系,我们可以编写一些针对某基类接口的控制函数,从而可以实现对多种派生接口的控制。例如我们可以编写一个使用数据流接口IAStream的函数,这样我们为这个函数传入IFile或者ISocket接口都是合法的。我们还可以编写一个所有控件的设置矩形显示框的公用函数,在这个函数中我们可以传入任何一个继承自IControl的接口指针,然后调用 ICONTROL_SetTect方法来设置控件的显示区域。
       最常用的一个BREW接口是IShell接口,它提供了一组系统级的方法,包括创建一个接口的实例等功能。例如,我们可以使用ISHELL_CreateInstance接口创建一个IDisplay接口指针的实例:

ISHELL_CreateInstance( pIShell,
                      AEECLSID_DISPLAY,
                      (void **) &pme->m_pIDisplay);

       如果我们花一些时间去浏览每一个BREW头文件的话,可以发现pIShell代表了一个IShell 接口的指针,而pme->m_pIDisplay则是一个IDisplay类型的接口指针,通过在这个变量前面加上取址运算符“&”作为参数传入,我们就可以获得一个IDisplay型的接口指针了。最后,我们就可以通过这个接口指针使用IDisplay接口中提供的各种方法了。
       除了ISHELL_CreateInstance接口外,IShell接口中还提供了各种不同作用的接口,如设置闹钟和定时器的接口,启动和关闭应用程序的接口,创建和关闭对话框的接口等等。您可以查看BREW的API文档查看其中的每一个接口,或者在本书的下一篇中您也能看见这些不同种类API的作用以及它们可能的内部机理(尽我所能的为您剖析)。
       除了IShell等接口之外,BREW还提供了一组助手函数,它们中有很多的实现是为了替换C标准库的函数。幸运的是,我们不需要为如何使用它们而感到担心,因为通常它们的形式与标准的C语言库相比就是大写的而已。一些常用的函数对比如下:

BREW助手函数
C语言标准库
ATOI
atoi
DBGPRINTF
printf
FREE
free
MALLOC
malloc
MEMCMP
memcmp
MEMCPY
memcpy
MEMMOVE
memmove
MEMSET
memset
REALLOC
realloc
SPRINTF
sprintf
STRCAT
strcat
STRCHR
strchr
STRCMP
strcmp
STRCPY
strcpy
STRDUP
strdup
STRLEN
strlen
STRNCPY
strncpy
STRSTR
strstr

       还有很多属于BREW专有的一些助手函数,不过这里没有列举出来,因为它们实在是太多了。由于 BREW的字符串处理是以Unicode为基础的,因此除了ASCII字符串助手函数之外,它还提供了一组处理宽字节的字符串处理函数。对应于单字节的助手函数,这些宽字节的助手函数只需要增加一个W就可以了,如WSTRCPY代表宽字节字符的复制函数。在编写程序时,请确认我们的应用程序中使用的是 BREW助手函数,而不是C语言的库函数,否则可能引起应用程序在BREW设备上的运行错误或者无端的增加程序的大小。
       对于那些已经学习过面向对象(如SmallTalk,C++等)的人来说,需要注意的是BREW没有那些语言的一些特征。例如,BREW中所有的数据类型仅仅是数据类型而已,而不是一个对象。请不要用我们固有的面向对象的思维方式去理解BREW,否则会给我们的编码带来一些麻烦,尤其是在继承这个问题上,BREW的继承不是在语法上的,而是在二进制层面的一种继承。关于这一点在后面的章节中将会有详细的叙述,现在唯一需要注意的是不要用我们以前的知识去理解BREW的面向对象方式。
7.4 指定语言的资源文件
       无论是BREW的系统资源文件,还是BREW应用程序的资源文件,都根据不同的语言放置在不同的目录下面。例如,英文的资源文件放在一个叫做en的目录下,简体中文的资源文件放在一个叫做“zhcn”的目录下面。这些目录的命名遵从ISO639的语言名称定义。在BREW SDK的AEELngCode.h头文件中,定义了全部的语言目录名称,有兴趣的读者可以看看这个文件。
       在BREW运行设备上,BREW会根据设备指定的不同语言,在运行应用程序时到不同的目录下载入资源文件。正是BREW的这种处理方式,使得BREW可以轻松的开发多语言的不同语言版本。同时,这也是BREW的一大特色。
       资源文件中可以添加字符串等资源,可以使用IShell接口的资源文件相关函数载入资源文件,例如可以使用ISHELL_LoadResString(IShell * pIShell, const char * pszResFile, int16 nResID, AECHAR * pBuff, int nSize ) 接口函数从指定的资源文件中载入字符串。具体各种接口函数的使用方法请参考BREW API的说明文档。
7.5 为BREW设备生成应用程序
       如果需要使我们的应用程序可以在BREW设备上运行,那么,就必须有相应设备上的C/C++编译器。当前的BREW设备都是运行在ARM CPU上的,因此这就需要ARM编译器ADS(ARM Development Suite)1.0.1、ADS1.2或者是BREW Builder。还可以使用GNU编译器(GCC)2.95。
7.5.1 ARM开发工具集(ADS
       ADS是ARM公司针对ARM内核CPU的C/C++语言编译链接的工具集和,目前BREW平台都是运行在ARM内核的CPU上的,因此BREW的动态应用程序也是使用ARM编译器编译的。当前ADS的最高版本是ADS1.2。在BREW的开发向导中,提供了对ARM编译环境的支持,可以直接在Visual Studio中生成目标二进制文件(.mod)。要获得此工具,需要从ARM公司购买。关于ADS的详细信息请参考网站http://www.arm.com上的信息。
7.5.2 BREW Builder
       通过高通公司与ARM公司联系,ARM公司为开发者提供了一套专门针对BREW的ARM编译器,它的软件购买费用大约是ADS的三分之一。在这个软件中包含了编译器、链接器和汇编工具,其中不包括ADS的集成开发环境和库文件管理工具,也不包括浮点运算的功能,同时提供的帮助文档也是PDF格式的,而不是ARM的DynaText格式。它使用的编译环境与ADS是一样的,可以在Visual Studio中直接使用。当前BREW Builder的名称是Realview Compilation Tools for BREW。
7.5.3 GNU编译器
       除了ADS和BREW Builder之外,我们还可以使用GCC编译器来编译BREW应用程序,而且这个软件是免费的。虽然使用GCC编译工具不是BREW官方推荐的做法(不推荐大多数是因为商业利益的关系),但是高通公司还是提供了相关的工具,它们主要有:
       1、make工具,用来管理源代码工程。
       2、Cygwin工具,用来在Windows环境下模拟Unix命令。
       3、GCC编译器,用来编译和链接源代码。
       4、其他,主要是用来解决GCC编译中库文件链接问题的相关文件。
       关于这些工具的详细信息和使用方法,可以到高通的网站上去查看详细的信息。如果使用GNU编译器,那么相关的编译的Make File就需要自己动手编写了,它没有集成到Visual Studio的环境中去。当然,高通公司也提供了示例文件可以供您参考。
7.5.4各种编译工具的选择
       前面介绍的三种工具都可以作为BREW目标设备的编译工具,我们可以结合自己的实际情况进行安装。 ADS适合于那些开发BREW扩展功能和进行BREW移植的厂商,通过它我们可以使用全部ARM编译器的功能。如果我们只是作为动态应用程序的开发者,那么我们使用BREW Builder或者GCC就足够了,它们的功能已经可以满足要求了。GCC属于开放源码组织的工具,因此是免费的,对于不希望为此付费的开发者来说最适合不过了,只不过需要自己搭建编译环境,并书写Make File。
       选择完工具之后,我们就可以根据所选择的方式生成可以在BREW设备上安装并运行的应用程序了。
7.5.5 BREW文件类型和动态应用程序的安装
       BREW应用程序由MIF文件(.mif)、资源文件(.bar)、签名文件(.sig)和模块文件(.mod)组成。签名文件是BREW系统用来验证当前应用程序是否具有在本机运行权限的文件,在通过空中接口的网络下载安装时,签名文件是由ADS服务器提供下载的。但是在应用程序开发测试过程中,BREW提供了一种测试模式。在这种测试模式下,应用程序可以根据BREW设备的ESN号码,从高通网站上申请一个签名文件。同时打开BREW设备的测试模式(通常在BREW设备中都会提供一个隐藏菜单,在此菜单中可以进行相关功能的设置),就可以使用这个签名文件在BREW设备中运行程序了。模拟器中不需要使用签名文件。
在安装BREW动态应用程序时,将这些文件安装在BREW设备的不同目录下。BREW对安装应用程序的路径有明确的要求,从BREW3.0开始的BREW设备目录结构如下:

路径名
描述
fs:/mif
存储MIF文件的文件夹
fs:/sys
存储BREW系统文件的地方,如系统配置文件、系统资源文件等
fs:/mod
存储应用程序文件夹及文件的地方。在这个文件夹中,使以各个MIF文件名相同名称的文件夹以及MOD文件和签名文件等
fs:/shared
存储公共文件的地方

       假设现在有应用程序TestA,那么它的组成文件可能会是如下的结构:
fs:/mif/testa.mif
fs:/mod/testa/testa.mod
fs:/mod/testa/testa.sig
fs:/mod/testa/testa.bar
fs:/mod/testa/public.bar
在BREW3.0版本之前,这个目录结构如下:
/testa.mif
/testa/testa.mod
/testa/testa.sig
/testa/testa.bar
/testa/public.bar
也就是说,在BREW3.0版本之前,所有的MIF文件都是放在BREW文件系统的根目录下面的,各个应用程序所在的路径也是在根目录下的。注意这里面的public.bar文件,为了避免进入“资源文件名称也与MIF文件名相同的误区 ”,我特别的加入这个文件来告诉您:资源文件的命名没有特殊限制(除了要要遵守文件系统的约定),而且同一个应用程序中也可以有多个资源文件。
7.6 使用BREW工具
       BREW给应用程序开发者提供了多种不同的工具,用来编辑或创建不同种类的BREW要素。通过这些工具的协同使用,使得我们可以很容易的开发出BREW的应用程序。这一节我们就来详细的介绍每种工具的使用方法。
7.6.1使用MIF编辑器
       每一个BREW应用程序(准确地说是每一个模块)都有对应的MIF文件,用来描述该模块中每一个应用程序的信息,如Class ID、图标、名称等等,MIF编辑器就是用来编辑MIF文件中这些内容的。在BREW MIF编辑器中可以创建MFX和MIF文件,MFX是开发 MIF 过程中使用的XML格式的中间文件。MIF是一种从MFX文件编译而成的特殊类型的BREW资源文件,其中包含有关BREW模块 (MOD)文件内容的信息。MIF创建之后,将以二进制形式提交并加载到目标设备。在模拟器上运行应用程序时,也可以使用MIF。大多数情况下,我们只需要使用Applets和General两个选项卡。Applets选项卡如图7.3所示。
       通过这个Applets选项卡,我们可以在模块中增加应用程序,包括设置通知信息、设置标记(Flags)、以及显示信息等。添加一个Applet信息时,需要指定Class ID所在的BID文件。BID文件可以本地生成或者从高通的网站上获得。由于每个模块中可以包含多个应用程序,因此需要多个BID文件,并且这些BID文件中包含的Class ID不能够相同。一旦Applet的入口被创建,那我们就可以编辑这个Applet的信息了。我们可以设置这个应用的图标,图标分为大、中、小三种,分别对应了在应用程序管理器中的不同菜单模式下的显示图标。BREW模拟器默认的界面就是应用程序管理器了,当我们设置了这些图标之后我们可以通过改变设置而看到不同的画面。指定的图片格式可以示BMP、PNG或JPEG。我们还可以指定应用程序的类型,如果我们选择的类型是“Hidden”,那么这个应用程序的图标将不会出现在应用程序管理器中。如果我们使用BREW开发设备的用户界面的话,那么这些应用程序的类型都应改选择“Hidden”,以使他们不会在设备上的应用程序管理器中显示出来。
图7.3Applets选项卡
       我们还可以选择高级设置按钮,进入Applet高级设置对话框。在这个对话框中我们可以设置应用程序的标志(Flags)、接收的通知事件和显示信息等。其中各种标志的描述如下:

标志
描述
Popup
此项标志描述当载入应用程序时不清空设备屏幕。如果没有选中此选项,那么当载入应用程序时将首先清空当前的屏幕。如果我们的应用程序在启动时出现白屏,那么可以选择此项解决问题。
Screensaver
此项表示当前的应用程序属于一个屏幕保护的应用程序,这将导致此应用程序出现在屏幕保护程序的列表中。
Phone
此标志表示将BREW设定的关闭全部应用程序的按键事件(通常是End键)像其他正常的按键一样传递给此应用程序(仅在此应用程序处于激活状态时)。此标志只有在应用程序具有系统级别的访问权限时才有效。
Show On Config Menu
此选项表示是否将此应用程序在BREW设置菜单中显示,显示的内容是在下面的“Menu string”输入框中输入的内容。
Show On Hidden Config Menu
此选项表示是否将此应用程序在BREW隐藏设置菜单中显示,显示的内容是在下面的“Menu string”输入框中输入的内容。

       有些BREW设备支持配置或设置菜单,允许设备用户为每个BREW小程序设置首选项。例如,游戏用户可以从首选菜单中选择游戏难度等级。我们可以在MIF中指定配置菜单上是否显示小程序。如果我们的小程序包括在这些菜单中,从配置菜单或隐藏配置菜单中选择时,就向它发送EVT_APP_CONFIG或EVT_APP_HIDDEN_CONFIG事件。小程序收到这些事件后,应该显示一个设置菜单以便于用户设置参数。模拟器不支持这些配置菜单。这些配置菜单目前很少使用,因此很少有应用程序会选择这个选项。
       使用高级设置对话框还可以设置应用程序接收的通知事件。通知的来源有AEECLSID_NET、AEECLSID_SHELL和AEECLSID_TAPI三个类。在“掩码”字段的下拉列表中选择掩码,可以确定给类发送哪些通知。此下拉列表中的选项将随上面所选的系统类而变化。如果通知是由非BREW标准类提供的,则可以选择自定义,然后输入Class ID。在“掩码”字段中,输入掩码的编号,可以确定给类发送哪些通知。掩码0xFFFFFFFF指定发送全部通知;掩码0x00000000则指定不发送通知。通知及可以在MIF文件中指定,也可以使用BREW接口来指定,在后面的Shell功能介绍里会有介绍。
       每一个应用程序还可以设置显示的信息,通常不必使用这些内容,因此这里就不做详细的介绍了,有兴趣的读者可以参考BREW的帮助文档。
       另一个常用的选项卡就是General选项卡了,General选项卡如图7.4所示。
图7.4 General选项卡
       General选项卡中可以添加模块的版权和版本信息,以及访问文件系统的限制和MIF文件中的字符串格式。这些内容都是模块的常规信息。最大文件数可以输入从7到65535之间的数字,代表指定模块可以创建文件的总数,它包括在模块目录以及共享目录中创建的文件和目录;最大空间中可以指定的最小空间为20,480字节,最大为4294967294字节,代表模块可以占用的文件空间总量,它包括模块目录和共享目录中的文件空间。字符串格式是指存储在此MIF文件中的字符串(如此选项卡中的作者名称和Applets选项卡中的Applet名称等)编码格式。关于编码格式我们会在后面的章节中有详细说明。
       Extensions选项卡中是设置此模块中导出类的地方。导出类是模块中所有非Applet的 Class ID。同BREW MIF编辑器窗口中的“Applets”选项卡一样,我们可以从BREW Class ID网页,或通过选择计算机上现有的BID文件获取非Applet类的ClassID。MIF可以使用两种类型的导出类:非保护和受保护。非保护导出类可供给任何应用程序随时使用。受保护导出类则必须明确声明为该MIF的外部依存(也就是在该MIF文件的Dependencies选项卡中增加使用该受保护导出类的类Class ID)。如果将受保护类指定给MIF而没有将其声明为外部依存,则将返回EPRIVLEVEL错误。虽然非保护类没有被明确声明为类的外部依存也可以使用,但这会导致无法跟踪BREW中的“使用模块”,引起此导出类的使用问题。我们可以考虑如下情况,如果使用了此非保护导出类的类,没有声明为此模块的外部依存,那么将有可能发生在使用此非保护导出类的类不知情的情况下删除了导出类所在的模块,从而导致使用此导出类失败。因此,模块必须始终声明受保护类和非保护类的外部依存。
       Extensions选项卡还可以设置导出的MIME类型。导出MIME类型是模块中的非 Applet类,执行该类可以处理特定MIME类型的文件(例如SND声音文件和MIDI文件)。对于MIME处理程序类,我们可以指定该类所处理的 MIME类型。其它类可以使用IShell接口的 GetHandler函数获取处理程序类的实例,该处理程序类以它处理的MIME类型命名。这个功能就类似于我们在Windows中使用特定的应用程序处理特定文件一样。
       Dependencies选项卡用来设置模块的外部依存。外部依存的含义是依赖或使用此模块的外部 Class ID。这里面我们容易进入的一个误区是,认为外部依存是此模块所依赖或使用的其它模块的类,而事实上却是恰恰相反的,因此请各位读者提起十二分精神注意此事。在此选项卡左边是“Available”的Class ID列表,也就是可用的Class ID,但是在这个列表中的Class ID还没有成为此模块的外部依存,我们需要使用“Add”按钮将我们希望作为外部依存的Class ID转移到右边的“Used”列表中。
       Privileges选项卡用来设置模块可以使用各种系统资源的权限,这些权限包括:

名称
描述
File
IFile、IDBMgr、IDatabase和IDBRecord接口的文件和数据库函数。
Network
INetwork和ISocket接口的网络和套接字函数,用以设置TCP和UDP套接字。
Web Access
访问IWeb接口。
TAPI
ITAPI接口中的TAPI函数。
Position Location
IPOSDET_SetGPSConfig、IPOSDET_GetGPSConfig 和 IPOSDET_Get GPSInfo 函数,提供基于GPS的定位信息。ISHELL_GetPosition 函数,提供对设备定位功能的访问。
Access To Ringer Directory
BREW SDK 和手持设备上存储音质文件的目录的写入权限。 在运行 IRINGER_Create() 函数时,将自动创建该目录。
Write Access To Shared Directory
BREW SDK 和手持设备上 <BREW/sdk/examples/Shared> 目录的写入权限。
Access To Sector Information
访问从 IPOSDET_GetSectorInfo API 获取的扇区信息。
Access To Address Book
访问IAddrBook接口

       在“Advanced Privileges”对话框中,可以确定模块是否可以访问某些BREW系统函数,它们仅限运营商和设备制造商使用,BREW 应用程序开发者不能使用。Download,指示可以访问IDownload接口中的函数,这些函数用于从运营商网站下载BREW应用程序。 All/System,指示可以访问所有 BREW 系统函数。
       Access Control选项卡用来设置模块的ACL(Access Control Level),关于ACL的详细信息可以参考第十章数据存储功能中关于ACL的描述。License选项卡用来设置该模块的许可证选项,此选项仅在进行应用程序测试的时候才有效,因此大多数情况下我们可以不予理会。
       上面的各个选项卡填写完毕之后,我们就可以保存这个MIF文件了。保存时可以选择保存的类型:v1、 v2、MFX文件。v1版本用于BREW3.0之前的BREW平台,v2版本用于BREW3.0版本及其之后的版本,我们可以根据实际情况保存不同的格式。MFX文件最终也需要编译成上面的两种二进制形式才能使用。
7.6.2使用资源文件编辑器
       BREW资源编辑器允许我们创建应用程序中使用的对话框、字符串、二进制数据以及文件对象。我们还可以使用资源编辑器创建控件,如:菜单、列表、日期选择器以及计时器等。如果创建的应用程序需要在不同语言的BREW设备上运行,这种资源文件的管理方式将十分有用。资源文件编辑器的操作界面如图7.5所示。
图7.5 资源文件编辑器
       字符串资源元素是一个字符数组,这些字符可以是Unicode、ISOLATIN 1、KSC5601、S-JIS或GB2312。默认类型为Unicode。通过将小程序使用的全部字符串保存在资源文件中,我们可以轻松地针对不同国家 /地区本地化小程序。对象资源可以是各种不同的格式或类型,但一定具有MIME类型。对象资源通常为图形图像。对象资源的另一个常见用法是在应用程序资源中嵌入HTML文件。使用这种用法时,数据应采用ASCII格式且MIME类型应设置为"text/html"。当然我们还可以直接存储二进制数据。
对话框资源由设备屏幕上显示的一个或多个BREW控件组成。应用程序可以定义多个对话框接口,引导用户在一系列要求输入信息的对话框中完成输入。BREW应用程序使用IShell接口的CreateDialog函数从资源文件中加载对话框,并在屏幕上显示其控件。加载对话框之后,可使用 IDateCtl、IMenuCtl、ITextCtl和ITimeCtl接口函数修改其控件的外观和行为、获取设备用户在各个控件中输入或选择的数据。 IShell的EndDialog函数可终止对话框,并在设备屏幕上显示之前激活的对话框(如果有)。
在我们建立了资源文件之后,我们需要将资源文件编译成二进制的(.bar)文件,同时还会生成资源文件ID定义的一个头文件。通过这个头文件中的ID,使得我们可以在应用程序中使用相应资源。与MIF文件不同的是,只要我们喜欢,一个应用程序中可以使用多个资源文件。而且命名也没有特殊的要求,可以使用任意我们自己喜欢的名称。
7.6.3使用BREW模拟器
       BREW模拟器用于模拟选定的BREW设备,使得我们可以加载BREW环境下开发的测试小程序和类。模拟的BREW设备可以使用各种屏幕、字体、键盘、可用内存量、支持的语言和其它参数。在模拟过程中,模拟器将在PC显示器上打开设备的图像。通过点击对应设备按键的图像区域,可以对要模拟的Applet提供按键输入,同时Applet生成屏幕输出显示在设备图像的屏幕区域。BREW模拟器还可以通过鼠标事件模拟触摸屏设备所产生的EVT_PEN_DOWN、EVT_PEN_MOVE和EVT_PEN_UP事件。通过这些事件可以实现应用程序的屏幕点击操作。BREW模拟器的效果如图5.5所示(在第五章)。
       正是如图5.5所展示的一样,BREW模拟器为我们提供了一个图形化的模拟界面。我们可以通过鼠标点击模拟设备上的按键与应用程序之间交互,也可以通过鼠标点击屏幕来模拟触摸屏事件。BREW模拟器可以通过不同的皮肤模拟不同的设备,在这里我们就称之为模拟设备吧。模拟设备文件使用BREW设备文件编辑器创建和编辑的,可以通过模拟器“File->Load Device”菜单来载入不同的模拟设备。
       应用程序管理器会在小程序目录中搜索模拟器中显示的小程序。 默认情况下,模拟器在 <BREW/sdk/examples> 目录下查找小程序,但是我们可以通过“File->Change Applet Dir…”菜单选项更改该目录。默认情况下,MIF文件目录和Applet目录是相同的。如果我们希望设置一个单独的MIF文件目录,我们可以通过 “Tools->Setting”菜单来进行设置。使用该菜单选项还可以指定是否激活堆验证以及默认的DNS(Domain Name Server)服务器。堆验证(Heap Validation)的目的是检测在应用程序退出之后是否存在未被释放的已分配内存空间,这样的机制可以保证我们的小程序中不会存在内存泄露。DNS服务器主要是提供给模拟器一个单独的DNS服务器选项,如果没有设置DNS,将使用当前PC机的设置。当打开Setting对话框的时候,模拟器会向当前正在运行的Applet发送EVT_APP_SUSPEND事件,关闭对话框的时候会发送EVT_APP_RESUME事件,因此通过这个对话框可以模拟应用程序的挂起和恢复的操作。
       除了上面的功能之外,BREW模拟器还可以模拟TAPI、SMS、GPS功能的数据输入,使得我们可以通过模拟器开发这些功能。当前模拟器的缺点是不能够模拟UIM卡部分的处理和操作。关于模拟器其他的一些功能和设置请参考相应的帮助文档。
7.6.4使用设备文件编辑器
       BREW模拟器通过模拟设备文件来模拟真实的应用程序运行的硬件平台和软件平台环境,之所以能够实现这种模拟方式的关键在于设备文件的存在。而编辑设备文件的工具就是BREW Device Configuratior,也就是设备文件编辑器。通常情况下设备文件编辑器只对OEM用户提供安装和下载,设备文件则是通过OEM设备生产厂商提供给 BREW应用程序开发者。这样可以保证每个模拟设备的信息是准确的。
       通过BREW设备文件编辑器我们可以模拟设备的按键、屏幕大小等许多信息。为了能够构建一个模拟设备文件,需要提供两张设备的图片,一张做为前景设备图片,另一张做为按键按下时的效果图。通常这两张的图片是同一张图片,但是有不同的偏移,这样就会有一种按下去的效果。可以在前景图上标记不同的区域,按键或者屏幕。然后通过对不同区域的属性设置,设备模拟工作就算初步完成了。然后,需要填写设备相信信息的属性表格,其中包括设备制造厂商,显示屏信息,内存信息等多种设备规格,通过这些信息我们可以了解整个设备的情况。设备文件编辑器的效果如图7.6所示。
图7.6 设备文件编辑器
7.6.5使用应用程序下载器
       在模拟器中开发完成应用程序并生成可以在设备上运行的二进制(.mod)文件之后,我们需要一种能够将应用程序下载到BREW设备中去测试的工具。这个工具就是BREW Application Loader,也就是应用程序下载器。
       为了使用BREW应用程序下载器,我们需要首先从高通网站获取一个签名文件,这个签名文件通常以(.sig)作为扩展名。当我们安装小程序的时候,我们将需要将MIF文件、签名文件、资源文件和模块二进制文件复制到BREW设备中去。可以按照如下步骤进行:
       1、使用BREW设备的数据线连接到PC机的一个未使用的端口上。这个端口可能是COM口,也可以是USB口,这与BREW设备有关。
       2、载入BREW Application Loader应用程序。
       3、选择在步骤一中指定的串口连接到BREW设备。
       BREW设备和PC机之间将通过特定的通信协议进行通信,然后我们将看到BREW设备内部的目录结构,我们可以通过BREW应用程序下载器打开、创建目录,而且可以将设备中的文件复制出来。在下载我们的应用程序的时候,我们需要将应用程序的MIF文件复制到文件系统的根目录下,然后将其他文件复制到与MIF文件名同名的目录下面,如果这个目录不存在,我们需要去创建它。我们也可以指定一次性的下载模块的全部内容,这样就不需要我们手动的复制文件了。由于这个工具的通信协议属于高通特有的格式,因此目前只有基于高通的BREW设备平台才能使用此工具。如果我们的BREW设备不是使用高通芯片的,那么我们可能需要从该BREW设备的制造商那里获得相应的应用程序下载工具。
7.6.6使用记录器(Logger)获取调试信息
       与BREW应用程序下载工具一样,BREW记录器也是一个BREW设备与PC连接的工具。BREW记录器的作用是获取在BREW设备中运行的应用程序的调试信息,这些调试信息是通过BREW助手函数DBGPRINTF输出的。通过BREW记录器我们可以捕获应用程序在运行时所输出的调试信息,这些信息在模拟器环境下开发时,也会从Visual Studio的输出窗口中输出。这些输出的信息会以记录的形式出现在屏幕上,您可以保存这些信息以便于分析问题。
       通过在应用程序中添加适当的调试信息,可以很方便的跟踪处理应用程序在BREW设备中遇到的情况,记录遇到的问题,使得我们可以更加容易的分析解决问题。
       使用BREW Logger的方法与使用BREW AppLoader的方法几乎相同,也是需要选择设备所在的端口,打开之后开始调试。由于这些工具需要获得高通公司的授权之后才能够下载使用,因此,这里就不详细的介绍他们的使用方法了。添加这些说明是为了让您了解BREW提供了这些方便的工具,有需要的时候您可以去获得这些工具。
7.7 小结
       在本章中,我们建立了第一个BREW应用程序,从中我们了解到了建立BREW应用程序的方法和注意事项,随后我们分析了BREW应用程序的组成部件,最关键的两个函数是入口函数AEEClsCreateInstance和事件捕获函数。与此同时我们分析了这些关键点之间的关系,并演示了如何生成一个拥有自己数据类型的BREW应用程序的方法。接下来,我们讲述了为BREW设备生成应用程序的方法,以及 BREW提供的各种工具的使用。通过对这些BREW工具的讲述,我们可以初步的使用这些工具进行BREW开发了。
思考题
       1、BREW事件捕获函数HandleEvent的第一个参数是(IApplet *)类型的,我们可否将其改为(MyAppTyp *)类型的?为什么?
       2、BREW MIF编辑器中,输出Class ID表示什么意思?
原创粉丝点击