欢迎回到我的J2ME系列开发文章。如果你已经从头开始学习这一系列文章,你就会想起上次我们为了可以实现同时编辑现有条目和新增条目的功能而修改了ExpensesApp应用程序的MIDlet组件。ExpenseApp不能在运行过程中存储新增的开销条目,这使得它的作用受到限制。
在本文中,我们将通过MIDP的记录管理系统(RMS)来为ExpensesApp MIDlet组件创建一个记录存储器来解决上述问题。我们将会把ExpensesApp增强到记录库的程度,这将使得该应用程序——最终在理论上——是有用的。
第一和第二篇文章
如果你决心加入学习本系列文章的行列,请确保你已经学习了本系列的前两篇文章:
探索J2ME:构建开销追踪系统
探索J2ME:创建开销细节表单
记录存了些什么?
MIDP规范要求运行平台提供某种稳固的存储手段(通过非挥发性的存储器,即掉电后存储的信息不会丢失的存储器)。RMS(记录管理系统)管理记录库,记录库即为二进制平面文件(flat file,由不包括重复组的一组同类型记录构成的文件)。记录库中的每一段数据都涉及到某一个记录,它还拥有一个由数字组成的记录ID号,ID在整个记录库中是互不重复的。每一个记录库的名字在创建它的MIDlet组件中也是互不相同的,MIDlet只可以访问它自己或同一组件中其它MIDlet所创建的记录库。当从设备(device)中删除MIDlet组件时,所有与MIDlet组件相关的记录库也全部被删除。
javax.microedition.rms软件包包含了RecordStore类,RecordStore类提供了访问记录库中的数据的初步方法。该软件包还包含了对记录库中的记录进行排序、搜索的其它类。在本文中,为了简明起见,我将集中介绍RecordStore类,其它的类将在以后介绍。
RecordStore类的运行
RecordStore类的运行过程很简单明了。你可以用静态RecordStore.openRecordStore方法打开一个已有的记录库,这会为这个指定名字的记录库返回一个RecordStore实例。如果你指定的记录库的名字不存在,该方法也可以创建一个以改名字命名的新的记录库(参见createIfNecessary参数)。使用getRecord方法你就可以通过记录ID来得到ID对应的记录。你还可以用addRecord和setRecord方法分别实现添加记录和更新记录的功能。当你完成了对记录库的操作,别忘了用closeRecordStore来关闭这个记录库。
关于记录ID的一个注释:
在Sun公司的标准实现(Sun’s reference implementation)中,给定记录的ID号与它的插入次序是相同的。记录库中第一个插入的记录以1为它的ID号,第二个插入的记录以2为ID号,以此类推。尽管这听起来似乎很方便,但是实际上并不总是如此。MIDP规范只要求每个记录都有一个ID号——ID号是如何创建的问题则留给平台的实现者。
RecordStore类的绝大多数方法都可以抛出一个或者多个RecordStoreException类型的例外。例外的子类和它们对应的意义如下所示:
InvalidRecordIDException例外,当某个被调用的方法涉及一个不存在的记录ID号(当读或者更新某个记录时)或者记录ID号无效(当添加记录时)时,就会抛出InvalidRecordIDException例外。
RecordStoreFullException例外,当RMS(记录管理系统)的存储器满时,就会抛出该例外。例如,用OpenRecordStore来打开一个指定名字的记录库时,即使没有更新记录库,它也有可能抛出RecordStoreFullException例外。
RecordStoreNotFoundException例外,当某个被调用的方法涉及到一个不存在的的记录库时,就会抛出该例外。如果指定名字的记录库不存在,OpenRecordStore也可以创建拥有该名字的记录库。该例外也可能从一个奇怪的地方抛出:CloseRecordStore方法。
RecordStoreNotOpenException例外,如果你试图访问一个尚未被打开的记录库(用OpenRecordStore方法打开记录库)时,就会抛出该例外。
最后,由于被处理的对象是二进制数据,记录库访问方法(getRecord、addRecord和setRecord)均把记录数据做为字节数组处理。你会发现——除非你有些喜欢被虐待——java.io软件包的流处理类,如ByteArrayInputStream、ByteArrayOutputStream、 DataInputStream、和DataOutputStream,使用起来并不可靠。
深入代码
你可以从这儿下载最新版本的ExpensesApp应用程序的代码。在写本文时,我起初只想修改ExpenseInfo类,但是RecordStore类是由一种古怪(quirky)的方法实现的,这使得这么做会难以置信得复杂。(在别的地方需要好几个try嵌套语句)。结果,我对Expenses和DetailForm类也做了修改,从而简化了整个过程。
事实上,绝大多数RMS(记录管理系统)都有类似的怪癖(quirk),把这些怪癖弄到一块会阻碍你的开发进度。下面是一些我曾遇到的问题:不同的类中的功能类似的方法应该有同样或者近似的名字,可是在RMS中不是这样;一些方法的名字“名不副实”,会误导你错误认识该方法的功能;还有一些方法会在编程者无法处理的情况下抛出例外。你会发现,如果你经常使用J2ME类,拥有一份MIDP的Java文档,或者至少一个有方法提示(method prompting)功能的编辑器是特别值得的。
看看代码清单A,你会发现静态方法ExpenseInfo.LoadExpenses为了从RecordStore对象中读取数据而被修改了。当打开或者创建一个记录库后,我会遍历库中所有的记录,把每个记录中的数据提取到一个字节数组中,然后为每一个记录创建一个新的ExpenseItem实例,并用一个Vector返回整套条目。ExpenseInfo.LoadExpenses现在抛出RecordStoreException例外,closeRecordStore本身也会抛出例外,因此改变(代码)是不可避免的了,这样,Expenses类的构造函数也需要加上一个try语句。
我还用RMS的API来更新了ExpenseInfo.save和 ExpenseInfo.delete方法。你可以在代码清单B中找到它们。这两个方法通过检查非零记录ID(私有ExpenseID变量)来检测记录是否存在于当前实例中。如果通过方法save来保存一个新的开销记录,就会调用addRecord方法来把该记录添加到记录库中。如果记录已经存在,save方法就调用setRecord方法来更新对应的记录。Delete方法工作过程与之类似,如果它的实例没有记录ID号就什么也不做(因为它还没有被保存),如果记录ID号(ExpenseID)非零,则调用deleteRecord方法来删除对应记录。
还有进一步的工作需要完成
我们已经让ExpensesApp应用程序前进了一大步,但是尚未全部完成。现在它还存在下列问题:
删除函数还有错误(bug):在记录库文件的中部将会引发LoadExpenses,并抛出InvalidRecordIDException例外。因此,lsMain中的cmDelete命令不可靠。
目前,开销条目按输入是到lsMain的顺序来显示的。如果按存储其中的数据来排序显示这些条目会比较有用。
你能发现这些bug吗?
请你考虑这个练习。首先,去掉Expense类的构造函数中的注释符,这样就恢复了先前被注释掉的删除函数。然后运行ExpensesApp应用程序,并添加若干个新的开销项目。接着,删除你刚才输入的第二个开销项目,并关闭ExpensesApp。当你再次进入ExpensesApp就会捕获到一个例外情况。想想这是为什么。
在下一篇探索J2ME文章中,我将向你展示如何用RecordEnumeration类来解决上述两个问题。祝你好运。
本文的相关连接请点这里