郭克华J2ME移动开发
实战教学视频讲义
第15章 RMS基础编程
郭克华所有作品由ChinaSEI独家发布。
网址为:https://www.doczj.com/doc/7011219462.html,
对应视频可在https://www.doczj.com/doc/7011219462.html,上下载。
本讲义属于郭克华团队网友整理,比视频略有扩充,如果有文字等小错,请多包涵。
在不盈利的情况下,欢迎免费传播。
版权所有.郭克华
本讲义经过修正、扩充,连同视频,由清华大学出版社出版。
详细可查询https://www.doczj.com/doc/7011219462.html,/49067,
https://www.doczj.com/doc/7011219462.html,/product.aspx?product_id=20742080
第15章 RMS基础编程
【本章导读语】
在J2ME移动开发过程中,经常会出现数据需要持久存储的情况,如:游戏数据要存盘,怎么办?一种方法是存入文件。但是,并不是所有的手机都支持文件存储。
为了满足这种要求,MIDP中推出了一个记录管理系统(Record Management System, RMS),它和数据库管理系统很类似,相应的支持包为:
打开文档,可以看到,该包中只包含一个类:
这个类也就是进行RMS操作的基础。
RMS是MIDP中提供的数据持久化存储的支持,本章内容将特别针对RMS的基础开发进行讲解。
【15-1】RecordStore基本操作
〖实例需求〗
javax.microedition.rms中只包含一个RecordStore类,顾名思义,RecordStore是记录集的意思,里面可以存储一条条记录。为了便于理解,你可以将RMS和和数据库管理系统中的概念作简单类比:
RMS:记录管理系统,相当于数据库中的数据库管理系统。
RecordStore:记录集,相当于表格。
本例中将基于文档,利用MIDlet,来讲解RMS中RecordStore的维护,包括建立RecordStore、删除RecordStore、访问RecordStore基本信息等。
〖开发过程〗
第一步:了解基本知识。
我们打开文档,来看看javax.microedition.rms.RecordStore类。
首先,RecordStore类没有可用的构造函数。根据我们的经验,当一个类没有可用的构造函数时,有可能这个类是抽象类,供扩展的。但我们发现,该类不是抽象类。那就可能是另一种情况:该类的对象可以由一个静态函数来创建。
查看该类的成员函数,会发现有如下3个函数可以生成RecordStore对象:
(1):
J2ME移动开发实战教程
该函数有两个参数:
参数1表示记录集名称(区分大小写);
参数2表示如果记录集不存在,是否创建。
如下代码:
RecordStore rs1 = RecordStore.openRecordStore("RS1", true);
表示创建一个名为“RS1”的记录集,如果不存在,则创建。
(2):
该函数有4个参数:
参数1表示记录集名称;
参数2表示如果记录集不存在,是否创建;
参数3表示创建方式,一共有两种选项:
RecordStore.AUTHMODE_ANY:该记录集可以被任何其他套件访问;
RecordStore.PRIV ATE:该记录集不可被其他套件访问。
参数4表示其他套件是否可以进行写操作。
通过以上两个函数就可以创建记录集,可以简单理解为数据库中的建表。在实际开发的过程中,我们使用第一个函数即可。
继续查看文档,还可以发现,在RecordStore类中,关于记录集的维护,还有如下函数:
1:得到记录集占据的空间:
2:得到记录集名称:
3:关闭记录集:
4:列出系统中当前的所有记录集名称:
5:删除某个记录集:
x 2 x
第15章RMS基础编程
第二步:对RecordStore的测试。
(1)编写代码
建立项目Prj15_1,在里面创建MIDlet1,将代码改成如下形式:
MIDlet1.java
package prj15_1;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.rms.RecordStore;
public class MIDlet1 extends MIDlet {
protected void startApp() throws MIDletStateChangeException {
try{
//打开记录集rs1
RecordStore.openRecordStore("RS1", true);
=
RecordStore
rs1
//得到记录集rs1大小
System.out.println("RS1占据大小:" + rs1.getSize());
//得到记录集rs1名称
System.out.println("名称:" + rs1.getName());
//关闭记录集rs1
rs1.closeRecordStore();
//列出系统中的记录集
this.list();
//建立记录集rs2
RecordStore.openRecordStore("RS2", true);
=
rs2
RecordStore
//删除记录集rs1
RecordStore.deleteRecordStore("RS1");
//列出系统中的记录集
this.list();
}catch(Exception ex){
ex.printStackTrace();
}
}
public void list(){
String[] names = RecordStore.listRecordStores();
x 3 x
J2ME移动开发实战教程
System.out.println("------目前的记录集为------");
for(int i=0;i System.out.println(names[i]); } } protected void destroyApp(boolean arg0) throws MIDletStateChangeException {} protected void pauseApp() {} } 运行此MIDlet,出现手机界面,控制台打印如下信息: 以上效果说明,系统中可以建立多个记录集来存储数据。 读者也许会问,既然是持久化存储,在手机上应该永久保存。那么,在PC机的模拟器上,RMS文件是怎么保存的呢? 在windows平台中的默认安装下,找到C:\Documents and Settings\用户名称\j2mewtk\2.5.2\appdb\DefaultColorPhone,在里面可以看到相应的文件。如,在本题中,文件显示为: 【15-2】RecordStore记录操作 〖实例需求〗 javax.microedition.rms中只包含一个RecordStore类,如果说RecordStore就相当于数据库中的表格,那针对RecordStore也应该有一些增、删、改、查的方法。 本例中将基于文档,利用MIDlet,来讲解RecordStore中记录的维护,包括添加、删除、修改、遍历查询记录等。 〖开发过程〗 第一步:了解基本知识。 打开文档,找到javax.microedition.rms.RecordStore类。首先需要了解如下基本概念:1:RecordStore中存储数据比表格中简单,每一条记录都是一个字节数组。但是这样也带来了难度,有些内容,如对象,需要转为字节数组之后才能存入。 2:每条记录都有一个ID,第一条记录的ID号为1,依此类推。 3:记录集中间的记录被删除,后面的记录不会前移,它们的ID号不变。 x 4 x 第15章RMS基础编程 这几个基本概念在后面我们将会有专门的测试。 在RecordStore文档中,提供了对记录增删改查的功能,在这里一一列出: 1:添加记录: 该函数一共3个参数: 参数1为数据的字节数组; 参数2表示该字节数组写入RecordStore时,从第offset个字节截取,截取的字节数目由参数3决定。 例如,我们如果要将“中国人”存入记录集,代码如下: RecordStore rs1 = RecordStore.openRecordStore("RS1", true); String str = "中国人"; byte[] data = str.getBytes(); rs.addRecord(data, 0, data.length); 2:删除记录: 该函数有1个参数,表示删除某个ID处的记录,如果该ID处没有记录,则抛出异常。 例如,我们如果要将记录集中第2条记录删除,代码如下: RecordStore rs1 = RecordStore.openRecordStore("RS1", true); rs1.deleteRecord(2); 3:修改记录: 该函数较好理解,参数1被修改记录的ID位置,参数2表示新的数据,参数3和参数4表示对参数2数组的截取。 例如,我们如果将“中国人”存入记录集,然后改为“ChinaPeople”代码如下: RecordStore rs1 = RecordStore.openRecordStore("RS1", true); String str = "中国人"; byte[] data = str.getBytes(); rs.addRecord(data, 0, data.length); x 5 x J2ME移动开发实战教程 str = “ChinaPeople”; data = str.getBytes(); rs.setRecord(1, data, 0, data.length); 4:查询记录包含以下几个功能: (1):得到记录条数: 注意,当RecordStore中删掉一条记录之后,该函数的返回值会变小,但是由于被删除记录后面的记录并没有前移,因此系统中的最大ID号并没有减小。 如下代码: RecordStore rs1 = RecordStore.openRecordStore("RS1", true); String str1 = "中国人"; byte[] b1 = str1.getBytes(); rs.addRecord(b1, 0, b1.length); String str2 = "郭克华"; byte[] b2 = str2.getBytes(); rs.addRecord(b2, 0, b2.length); rs.deleteRecord(1); System.out.println("当前纪录条数:"+ rs.getNumRecords()); 删除第1条记录之后,系统打印的结果为: 当前记录条数:1 但是要注意,第2条记录并没有前移。也就是说第2条记录还是存在的。而第1条记录变为无效了。 (2):得到记录集中的下一个记录号: 该函数实际上是首先找到记录集中记录号的最大值,然后加1,就是记录集中的下一个记录号。当RecordStore中删掉一条记录之后,由于被删除记录后面的记录并没有前移,因此该函数的返回值不变。 如下代码: RecordStore rs1 = RecordStore.openRecordStore("RS1", true); String str1 = "中国人"; byte[] b1 = str1.getBytes(); rs.addRecord(b1, 0, b1.length); String str2 = "郭克华"; byte[] b2 = str2.getBytes(); x 6 x 第15章RMS基础编程 rs.addRecord(b2, 0, b2.length); rs.deleteRecord(1); System.out.println("下一个记录号:"+ rs.getNextRecordID()); 删除第1条记录之后,系统打印的结果为: 下一个记录号:3 (3):根据ID号获得数据的字节数组: 以上两个函数中,第一个函数使用较广。 如下代码: RecordStore rs1 = RecordStore.openRecordStore("RS1", true); String str1 = "中国人"; byte[] b1 = str1.getBytes(); rs.addRecord(b1, 0, b1.length); String str2 = "郭克华"; byte[] b2 = str2.getBytes(); rs.addRecord(b2, 0, b2.length); byte[] b3 = rs.getRecord(1); String str3 = new String(b3); System.out.println(str3); 系统打印的结果为: 中国人 (4):根据ID获得记录所占字节数: 如下代码: RecordStore rs1 = RecordStore.openRecordStore("RS1", true); String str1 = "中国人"; byte[] b1 = str1.getBytes(); rs.addRecord(b1, 0, b1.length); x 7 x J2ME移动开发实战教程 String str2 = "China"; byte[] b2 = str2.getBytes(); rs.addRecord(b2, 0, b2.length); System.out.println("记录1占据空间为:"+ rs1.getRecordSize(1)); System.out.println("记录2占据空间为:"+ rs1.getRecordSize(2)); 系统打印的结果为: 记录1占据空间为:6 记录2占据空间为:5 注意,因为一个汉字占用2个字节,所以“中国人”所占字节数为6。第二步:对RecordStore中增删改查的综合测试。 (1)编写代码 在项目Prj15_1中创建MIDlet2,将代码改成如下形式: MIDlet2.java package prj15_1; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; import javax.microedition.rms.RecordStore; public class MIDlet2 extends MIDlet { protected void startApp() throws MIDletStateChangeException { null; = RecordStore rs try{ rs RecordStore.openRecordStore("RS1", true); = System.out.println("添加:中国人"); "中国人"; = String str1 byte[] b1 = str1.getBytes(); 0, b1.length); rs.addRecord(b1, System.out.println("当前纪录条数:"+ rs.getNumRecords()); System.out.println("添加:郭克华"); = "郭克华"; str2 String byte[] b2 = str2.getBytes(); b2.length); rs.addRecord(b2, 0, System.out.println("当前纪录条数:"+ rs.getNumRecords()); x 8 x 第15章RMS基础编程 System.out.println("修改记录第一条记录为:中国"); = "中国"; newStr String byte[] newBytes = newStr.getBytes(); newBytes.length); 0, newBytes, rs.setRecord(1, //根据ID得到纪录 byte[] b = rs.getRecord(2); System.out.println("第二条记录是:" + new String(b)); //得到第一条记录所占字节的大小 System.out.println("第一条记录所占字节的大小为:" + rs.getRecordSize(1)); }catch(Exception ex){ ex.printStackTrace(); } finally{ try{ rs.closeRecordStore(); }catch(Exception ex){} } } protected void destroyApp(boolean arg0) throws MIDletStateChangeException {} protected void pauseApp() {} } 运行此MIDlet,出现手机界面,控制台打印如下信息: 以上效果说明,RecordStore中提供了比较灵活的做法,来对记录进行增删改查。(2)对记录删除和ID的测试 前面已经说过,当RecordStore中删掉一条记录之后,getNumRecords函数的返回值会变小,但是由于被删除记录后面的记录并没有前移,因此系统中的最大ID号并没有减小。本节对这个原理进行测试。在项目Prj15_1中创建MIDlet3,将代码改成如下形式: MIDlet3.java package prj15_1; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; x 9 x J2ME移动开发实战教程 import javax.microedition.rms.RecordStore; public class MIDlet3 extends MIDlet { protected void startApp() throws MIDletStateChangeException { null; = RecordStore rs try{ RecordStore.openRecordStore("RS1", true); = rs System.out.println("空记录集大小:" + rs.getSize()); System.out.println("添加:中国"); byte[] b1 = "中国".getBytes(); b1.length); rs.addRecord(b1, 0, System.out.println("当前纪录条数:"+ rs.getNumRecords()); System.out.println("当前记录集大小:" + rs.getSize()); System.out.println("添加:美国"); byte[] b2 = "美国".getBytes(); b2.length); 0, rs.addRecord(b2, System.out.println("当前纪录条数:"+ rs.getNumRecords()); System.out.println("当前记录集大小:" + rs.getSize()); System.out.println("删除第1条记录"); rs.deleteRecord(1); System.out.println("当前纪录条数:"+ rs.getNumRecords()); System.out.println("当前记录集大小:" + rs.getSize()); System.out.println("第2条记录是:" + new String(rs.getRecord(2))); System.out.println("第1条记录是:" + new String(rs.getRecord(1))); }catch(Exception ex){ ex.printStackTrace(); } finally{ try{ rs.closeRecordStore(); }catch(Exception ex){} } } x 10 x 第15章RMS基础编程 protected void destroyApp(boolean arg0) throws MIDletStateChangeException {} protected void pauseApp() {} } 运行此MIDlet,出现手机界面,控制台打印如下信息: 以上效果说明,记录删除之后,其他记录ID不变,RecordStore的大小也不会减小,但是getNumRecords函数的返回值会变小。 【15-3】电话簿中的RMS对象存储 〖实例需求〗 前面章节中我们都提到了将数据保存在RecordStore中,但是保存的是简单数据。在某些特定场合,我们需要保存在RecordStore中的可能不是简单数据。如将用户的通讯记录保存在RecordStore中时,就必须同时保存姓名和电话号码两个字段,这就牵涉到怎样将对象保存在RMS中的技术。本节中,我们将完成如下案例: 有一个Customer类,里面包含了两个属性:cname和phone,要求能够将Customer 类的对象存入RecordStore,然后读入。 〖开发过程〗 第一步:编写Customer类。 在Prj15_1中建立一个Customer类,并增加相应属性,代码如下: Customer.java package prj15_1; public class Customer { private String cname; private String phone; public String getCname() { return cname; } public void setCname(String cname) { https://www.doczj.com/doc/7011219462.html,ame = cname; } x 11 x J2ME移动开发实战教程 public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } } 第二步:了解基本知识。 我们打开文档,来看看javax.microedition.rms.RecordStore类。找到其中的“添加记录”的函数: 该函数中,参数1是一个字节数组,并不能传入一个对象,因此,现在的关键问题是:怎样将一个对象转化为字节数组? https://www.doczj.com/doc/7011219462.html,ng.Object并没有提供将对象变成字节数组的方法。在这里,我们需要另辟蹊径。 这里我们要借助java.io包里面的几个类。 首先讲解怎样将一个对象变成字节数组。将对象变成字节数组相当于将对象中的每个成员变量写入流,然后将流变成字节数组。 打开文档,找到java.io包,在里面有一个类:java.io.ByteArrayOutputStream,在该类中有一个方法: 该方法能够将流中的内容转换为字节数组返回。但是,该方法并不能很方便地将对象中的成员写入流中;在文档中找到另一个类:java.io.DataOutputStream,该类的构造函数为: 能够将ByteArrayOutputStream对象传入,并且,该类中有大量的write方法可以支持将各种类型写入流中。如: 1:写字符串: 2:写整数: 等等。 例如,如果我们要将一个Customer对象cus中的cname和phone字段变成字节数组,就可以用下面的代码: ByteArrayOutputStream baos = new ByteArrayOutputStream(); x 12 x 第15章RMS基础编程 DataOutputStream dos = new DataOutputStream(baos); //通过dos将对象内容写入baos dos.writeUTF(https://www.doczj.com/doc/7011219462.html,ame); dos.writeUTF(cus.phone); baos.close(); dos.close(); 接下来讲解怎样将一个字节数组变为对象。将字节数组变成对象相当于从流中的字节数组中读入每个成员,然后包装为对象。 打开文档,找到java.io包,在里面有一个类:java.io.ByteArrayInputStream,该类有一个构造函数为: 能够将字节数组放入流中。但是,该方法并不能很方便地将字节数组中的数据读入;在文档中找到另一个类:java.io.DataInputStream,该类的构造函数为: 能够将ByteArrayInputStream对象传入。并且,该类中有大量的read方法可以支持从流中读取各种数据类型。如: 1:读字符串: 2:读整数: 等等。 例如,如果我们要将一个字节数组b中的数据从流中读入,然后赋值为Customer对象cus中的cname和phone字段,就可以用下面的代码: ByteArrayInputStream bais = new ByteArrayInputStream(b); DataInputStream dis = new DataInputStream(bais); //从bais读取内容 = new Customer(); Customer cus cus.setCname(dis.readUTF()); cus.setPhone(dis.readUTF()); bais.close(); dis.close(); 第三步:编写代码。 (1)修改Customer代码 Customer类并没有提供将对象变为字节数组和将字节数组转为对象的方法,因此可以在里面自定义。在项目Prj15_1中,打开Customer.java,将代码改为: Customer.java x 13 x J2ME移动开发实战教程 package prj15_1; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; public class Customer{ private String cname; private String phone; //将对象转为字节数组 public byte[] object2ByteArray() throws Exception{ new ByteArrayOutputStream(); = baos ByteArrayOutputStream new DataOutputStream(baos); = DataOutputStream dos //通过dos将对象内容写入baos dos.writeUTF(https://www.doczj.com/doc/7011219462.html,ame); dos.writeUTF(this.phone); baos.close(); dos.close(); //返回字节数组 return baos.toByteArray(); } //将字节数组转为对象 public static Customer byteArray2Object(byte[] b) throws Exception{ new ByteArrayInputStream(b); = ByteArrayInputStream bais new DataInputStream(bais); = DataInputStream dis //从bais读取内容 = new Customer(); cus Customer cus.setCname(dis.readUTF()); cus.setPhone(dis.readUTF()); bais.close(); dis.close(); return cus; } public String getCname() { return cname; } public void setCname(String cname) { https://www.doczj.com/doc/7011219462.html,ame = cname; } x 14 x 第15章RMS基础编程 public String getPhone() { return phone; } public void setPhone(String phone) { this. phone = phone; } } (2)编写MIDlet 在项目Prj15_1中,建立MIDlet4,将代码改为: MIDlet4.java package prj15_1; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; import javax.microedition.rms.RecordStore; public class MIDlet4 extends MIDlet { protected void startApp() throws MIDletStateChangeException { null; = RecordStore rs try{ = RecordStore.openRecordStore("RS1", true); rs new Customer(); = cus Customer cus.setCname("王强"); cus.setPhone("025********"); //转换为字节数组写入 byte[] b1 = cus.object2ByteArray(); b1.length); 0, rs.addRecord(b1, //读 byte[] b2 = rs.getRecord(1); Customer.byteArray2Object(b2); = newCus Customer System.out.println("姓名为:" + newCus.getCname()); System.out.println("电话号码为:" + newCus.getPhone()); }catch(Exception ex){ ex.printStackTrace(); } finally{ try{ x 15 x J2ME移动开发实战教程 rs.closeRecordStore(); }catch(Exception ex){} } } protected void destroyApp(boolean arg0) throws MIDletStateChangeException {} protected void pauseApp() {} } 运行此MIDlet,出现手机界面,控制台打印如下信息: 该结果表明,对象能够存进RMS,并能读入。 【15-4】备忘录编写 〖实例需求〗 在本节中,我们将根据前面所讲解的RMS开发的基本内容,以及对象读写的知识,制作一个简单的备忘录。 用户可能在将来的某个时间要作一件事情,但是怕忘记,因此,可以保存在手机内,等到在那个时候提醒。实际上,闹钟也是一种特殊的备忘录。本项目界面出现,效果如图15-1所示: 图15-1 界面效果 界面上有个Form,标题为:“备忘录界面”,在该界面中,有两个控件。一个是“请您输入内容”,在该控件中可以输入备忘录的内容,如“参加奥运会开幕式”;第2个控件是“请您选择时间”,是一个DateField,可以选择要参加的时间。如选择“2008年8月8日下午8点”之后,界面变为图15-1右边的样子。 x 16 x 第15章RMS基础编程 在界面右下方有一个“保存备忘录”按钮,选择此按钮,能够将该条备忘录记录保存在RMS中。注意,这里简单起见,我们的程序只能保存1条备忘录。 备忘录保存之后,界面关闭。重新打开该程序,界面效果如下: 图15-2 备忘录界面 注意,该界面中应该判断RMS中是否有备忘录,如果有,就显示在界面上;如果没有,则显示图15-1中的界面。 〖开发过程〗 第一步:系统分析。 显而易见,该程序,由于备忘录内容和时间是绑定在一起的,因此最好用到对象读写。而界面还需要能够判断是否有记录存储在RMS中。 不过,这里有一个特殊问题需要阐明。我们知道,对象写入RMS,要转为字节数组,本项目中我们首先应该编写Memo类,代码基本结构如下: public class Memo{ private String content; private Date date; public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getDate() { return date; x 17 x J2ME移动开发实战教程 } public void setDate(Date date) { this.date = date; } } 前面说过,将一个对象变成字节数组时,应该将对象中的每个成员变量写入流,然后将流变成字节数组。将成员变量写入流,我们用到的是一个类:java.io.DataOutputStream,该类中有大量的write方法可以支持将各种类型写入流中。如:写字符串、写整数等等。但是,没有写Date对象的函数! 同样的道理,将一个字节数组变为对象时,实际上就是从流中的字节数组中读入每个成员,然后包装为对象。我们用到的是java.io.DataInputStream,该类中有大量的read方法可以支持从流中读取各种数据类型。如:读字符串、读整数等等。但是就是没有读Date 对象的函数! 怎么办呢? 打开Date文档,会发现里面有一个重要函数: 该函数相当于将一个Date对象转换成long型数据; 而Date的构造函数也有如下形式: 相当于将一个long型数据包装成Date对象;或者: 也相当于用一个long型数据设置Date对象的状态。 因此,对Date的读写,就完全可以转换成对long型数据的读写。而在java.io.DataOutputStream中,有如下函数: 能够向流中写出一个long型数据;在java.io.DataInputStream中,有如下函数: 能够从流中读入一个long型数据。 因此,写一个Memo对象的代码可以采用如下方案: public byte[] object2ByteArray() throws Exception{ = new ByteArrayOutputStream(); baos ByteArrayOutputStream new DataOutputStream(baos); = DataOutputStream dos //通过dos将对象内容写入baos dos.writeUTF(this.content); long time = date.getTime(); dos.writeLong(time); baos.close(); dos.close(); x 18 x