BlackBerry 应用程序开发者指南 第二卷:高级--第8章 存储持久数据
作者:Confach 发表于 2006-04-28 22:28 pm
版权信息:可以任意转载, 转载时请务必以超链接形式标明文章原始出处 和作者信息.
http://www.cnblogs.com/confach/articles/387952.html
8
第8章 存储持久数据
持久数据选项 管理持久数据 内存管理以及持久对象 管理客户对象 |
持久数据选项
在BlackBerry设备上,可以通过下面的方法来存储数据:
- 使用MIDP记录存储.
- 使用BlackBerry持久存储模型.
如果想让你的应用程序可以在多个与Java ME兼容的设备上运行,那么采用MIDP的实现.如果你编写的应用程序仅仅在BlackBerry设备运行,使用BlackBerry持久存储模型,因为它提供了一个更为灵活有效的方式来存储数据,
MIDP存储记录
javax.microedition.rms包提供了MIDP记录存储的实现.持久数据存储在RecordStore对象里.一个记录存储最大可以为64KB.
数据的离散单元称为记录.一个记录是一个字节数组,赋给它一个唯一标志数.
创建一个记录存储
调用openRecordStore().指定true来描述当记录存储不存在时应该创建此记录存储.
RecordStore store = RecordStore.openRecordStore("Contacts", true); |
注:当从BlackBerry设备删除一个应用程序时,所有此应用程序创建的记录存储都会删除.每个在MIDlet包(suite)的记录存储都有一个唯一名.MIDlet仅可以访问一个在相同包里的由MIDlet创建的记录存储.
增加一个记录
调用addRecord().
int id = store.addRecord(_data.getBytes(), 0, data.length()); |
获取一个记录
调用getRecord(int, byte[], int).给本方法提供一个记录ID,字节数组,以及一个偏移作为参数.
byte[] data = new byte[store.getRecordSize(id)]; store.getRecord(id, data, 0); String dataString = new String(data); |
获取所有记录
打开存储,然后获取其迭代.
RecordStore store = RecordStore.openRecordStore("Contacts", false); RecordEnumeration e = store.enumerateRecords(null, null, false); |
enumerateRecords(RecordFilter filter, RecordComparator comparator, Boolean keepUpdated) 方法有如下参数:
参数 |
描述 |
filter |
此参数指定一个RecordFilter对象获取记录存储结果的子集(如果为null,将返回所有记录存储). |
comparator |
此参数指定一个RecordComparator对象决定返回记录所在的顺序位置(如果为null.将返回无序的记录). |
keepUpdated |
此参数决定对于记录存储,迭代保持当前的改变. |
BlackBerry持久存储
在MIDP中的记录存储(RecordStore)与BlackBerry持久模型(PersistentStore)有2处主要的区别.
特性 |
描述 |
数据存储 |
MIDP记录仅以字节数组存储数据.相比之下,BlackBerry API允许你在持久存储中保存任何对象.这样,查询存储数据就会比记录模型更快一些.为了存储一个自定义的对象类型,自定义类型的类必须要实现Persistable接口. |
数据共享 |
在MIDP中,每个RecordStore属于单个MIDlet包,并且MIDlet也只能访问由相同包的MIDlet创建的记录存储.尽管如此,在BlackBerry持久模型中,数据可以在应用程序之间共享,在创建数据的离散应用程序中共享.代码签名指定只有被认证的应用程序才可以访问这些数据. |
注:BlackBerry持久性API在手持设备软件3.6或后期版本可用.对于早期的版本,你必须使用MIDP记录存储.
保留存储空间
BlackBerry的存储空间是有限的.你应该小心设计你的程序,将需要存储持久数据的闪存数量最小化.
在一般的BlackBerry里,对于一个标准的BlackBerry应用程序,不需要的存储空间必须在所有应用程序之间共享,用来存储用户数据,包含日历约会,联系人,以及消息.
如果BlackBerry设备在一个小内存情况下操作,它有可能完成下面的动作释放内存空间:
- 从BlackBerry上删除以前的消息.
- 从BlackBerry 设备上删除超过一个星期的日历约会(如果启动了无线日历同步).
如果因低内存而BlackBerry设备删除了消息或者日历约会,那么在桌面消息程不会删除数据.为获的更多信息,参看96页的”内存管理以及持久对象”.
备份与恢复
在net.rim.device.api.synchronization包中,同步(synchronization)API允许你备份以及恢复BlackBerry设备上的持久数据.为获取更多信息,参看104页的”增加支持备份持久数据”.
安全
缺省的,BlackBerry上由RIM数字签名的应用程序可以访问持久存储上的数据,联系RIM获取关于控制数据访问的信息.
管理工具
在BES 3.5 Microsoft® Exchange SP2或 BES 2.2 M® Lotus® Domino®里,系统管理员可以使用IT策略控制第三方应用程序访问持久存储.
管理员可以设置应用程序控制项ALLOW_USE_PERSISTENT_STORE为TRUE或FALSE.缺省的,第三方应用程序可以使用持久存储(ALLOW_USE_PERSISTENT_STORE为TRUE)
注:这个策略对MIDP记录存储没有影响.
数据完整性
注:因低内存VM完成一个紧要的垃圾回收时,数据完整性会折中.在这样的情况下,当BlackBerry设备提交时,部分完成的事务会提交.在正常垃圾回收下未提交的事务不会提交.
管理持久数据
持久数据类型
如果一个自定义数据类型类实现了Persistable类接口,那么此数据类型可以持久保存.小面原生数据类型也可以持久存储.
- java.lang.Boolean
- java.lang.Byte
- java.lang.Character
- java.lang.Integer
- java.lang.Long
- java.lang.Object
- java.lang.Short
- java.lang.String
- java.util.Vector
- java.util.Hashtable
创建一个持久化数据库
每个应用程序一般可以创建单个PersistentObject.此对象是应用程序的持久化数据以及索引的根数据库.应用程序将保存数据到此PersistentObject中.
一个唯一的long键标志了每个PersistentObject.此键一般为一个全权包名的哈希
创建一个唯一的long键
1. 在BlackBerry IDE里,输入一个字符串,例如com.rim.samples.docs.userinfo.
2. 选择此字符串.
3. 右击,然后单击Convert 'com.rim.samples.docs.userinfo' to long. long值将会出现.
注:在你的代码加入注释表明用来生成long键的字符串..
static PersistentObject store; static { store = PersistentStore.getPersistentObject( 0xa1a569278238dad2L ); } |
持久存储数据
为了将数据保存到持久存储里,调用PersistentObject上的setContents().此方法用新的值替代已存在的值.调用commit()保存到持久存储里,
注:如果在提交的过程中发生一个错误,已经完成的更新不会提交.在PersistentObject里的数据从最后一次提交中获取值,以保持数据的完整性.
String[] userinfo = {username, password}; synchronized(store) { store.setContents(userinfo); store.commit(); } |
如果你有许多对象需要提交到存储里,你可以以一个批事务的形式提交它们.为了实现此,调用PersistentStore.getSynchObject()方法获取持久存储监视器紧锁对象.然后同步对象,如果必要,调用commit()方法.当你释放监视对象的同步时,你的所有事务一次性被提交.如果批处理有任何提交失败,整个批提交也失败.当你同步监视器对象时,如果你调用forceCommit(),这个对象立即提交,并且它不再时批事务中的一部分了.
获取持久数据
调用 PersistentObject 上的getContents() 方法.
将 PersistentObject.getContents()返回的对象显式的转化为你需要的类型.
synchronized(store) { String[] currentinfo = (String[])store.getContents(); if(currentinfo == null) { Dialog.alert(_resources.getString(APP_ERROR)); } else { currentusernamefield.setText(currentinfo[0]); currentpasswordfield.setText(currentinfo[1]); } } } |
注:当一个应用程序第一次访问数据据,它应该验证任何索引的顺序,如果出现一个问题,重新创建索引.应用程序应该能够识别并更正任何毁坏的或者丢失的数据的问题.为获得更对信息,参看91页的”数据完整性”.
删除一个数据库
为了删除一个数据库,调用PersistentStore.destroyPersistentObject().提供一个PersistentObject的唯一键作为参数,
为了删除单独的数据,把它们简单的当成普通数据看待,并删除这些数据的引用.垃圾数据会自动回收。
代码实例
本代码实例描述了如何为用户创建一个应用程序来查看它们当前的用户名和密码,输入一个新的用户名和密码,然后保存变化.
例: UserInfo.java
/**
* UserInfo.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.docs.userinfo;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.system.*;
import net.rim.device.api.util.*;
import java.util.*;
import net.rim.device.api.i18n.*;
import com.rim.samples.docs.baseapp.*;
import com.rim.samples.docs.resource.*;
public class UserInfo extends BaseApp
implements UserInfoResource,KeyListener, TrackwheelListener {
private static PersistentObject store;
private static ResourceBundle _resources;
private AutoTextEditField usernamefield;
private PasswordEditField passwordfield;
private AutoTextEditField currentusernamefield;
private AutoTextEditField currentpasswordfield;
static {
_resources = ResourceBundle.getBundle(
UserInfoResource.BUNDLE_ID, UserInfoResource.BUNDLE_NAME);
store = PersistentStore.getPersistentObject(0xa1a569278238dad2L);
}
private MenuItem saveItem = new MenuItem( _resources.getString(MENUITEM_SAVE), 110, 10) {
public void run() {
String username = usernamefield.getText();
String password = passwordfield.getText();
String[] userinfo = {username, password};
synchronized(store) {
store.setContents(userinfo);
store.commit();
}
Dialog.inform(_resources.getString(APP_SUCCESS));
usernamefield.setText(null);
passwordfield.setText(null);
}
};
private MenuItem getItem = new MenuItem( _resources.getString(MENUITEM_GET), 110, 11 ) {
public void run() {
synchronized(store) {
String[] currentinfo = (String[])store.getContents();
if(currentinfo == null) {
Dialog.alert(_resources.getString(APP_ERROR));
}
else {
currentusernamefield.setText(currentinfo[0]);
currentpasswordfield.setText(currentinfo[1]);
}
}
}
};
public static void main(String[] args) {
UserInfo app = new UserInfo();
app.enterEventDispatcher();
}
public UserInfo() {
MainScreen mainScreen = new MainScreen();
mainScreen.setTitle(new LabelField(
_resources.getString(APPLICATION_TITLE)));
usernamefield = new AutoTextEditField(
_resources.getString(FIELD_NAME), "");
passwordfield = new PasswordEditField(
_resources.getString(FIELD_PASSWORD),"");
currentusernamefield = new AutoTextEditField(
_resources.getString(FIELD_CURRENTNAME), "");
currentpasswordfield = new AutoTextEditField(
_resources.getString(FIELD_CURRENTPASSWORD),"");
SeparatorField separator = new SeparatorField();
mainScreen.add(usernamefield);
mainScreen.add(passwordfield);
mainScreen.add(separator);
mainScreen.add(currentusernamefield);
mainScreen.add(currentpasswordfield);
mainScreen.addKeyListener(this);
mainScreen.addTrackwheelListener(this);
pushScreen(mainScreen);
}
public void makeMenu( Menu menu, int instance ) {
menu.add(saveItem);
menu.add(getItem);
super.makeMenu(menu, 0);
}
public void onExit() {
Dialog.alert(_resources.getString(APP_EXIT));
}
}
<!--[if !vml]-->
内存管理和持久化对象
在BlackBerry设备上有固定数量的持久化对象句柄以及对象句柄.
闪存 |
持久化对象句柄 |
对象句柄 |
8 MB |
12,000 |
24,000 |
16 MB |
27,000 |
56,000 |
32 MB |
65,000 |
132,000 |
BlackBerry设备上的每个持久化对象都会要求一个持久化对象句柄以及一个对象句柄.例如,一个记录包含了10个String字段,那么它将要求11个对象句柄-一个为记录,一个为每个String.如果记录是持久化的,它将需要额外的11个持久化对象句柄.
使用下面的技术限制应用程序需要的持久化对象句柄数:
- <!--[endif]-->如果可能,使用原始类型代替对象.一个原始类型,例如一个int或者一个char,它们不需要一个对象句柄.
- 使用对象分组API (net.rim.device.api.system.ObjectGroup)将对象分组.一个分组的对象仅需要一个对象句柄.
为获取更多信息,参看”BlackBerry JDE最佳内存实践”白皮书.
管理自定义对象
创建一个数据库
创建一个Vector对象存储多个对象.创建一个PersistentObject作为应用程序的根数据库.
private static Vector _data; PersistentObject store; static { store = PersistentStore.getPersistentObject( 0xdec6a67096f833cL ); //key is hash of test.samples.restaurants _data = (Vector)store.getContents(); synchronized (store) { if (_data == null) { _data = new Vector(); store.setContents(_data); store.commit(); } } } |
持久存储数据
可以持久化实现了Persistable接口的对象.
下面的代码实例作为inner类实现Persistable接口.它定义了一个带有4个Object的数组来存储餐厅名,地址,电话号码,以及特色.并且定义了方法来获取和设置Object元素值.
private static final class RestaurantInfo implements Persistable { private String[] _elements; public static final int NAME = 0; public static final int ADDRESS = 1; public static final int PHONE = 2; public static final int SPECIALTY = 3; public RestaurantInfo() { _elements = new String[4]; for ( int i = 0; i < _elements.length; ++i) { _elements[i] = new String(""); } } public String getElement(int id) { return _elements[id]; } public void setElement(int id, String value) { _elements[id] = value; } } |
创建扩展的对象
下面的方法允许你加入字段到对象中:
- 在一个int中,存储Boolean值做为bit.保留多余的位供将来使用.
- 直接存储String,但是使用一个键/值对的Vector或Hashtable,这样附加的(或很少使用的)的字段也可以增加.
- 如果你由一个表的索引,将他们存储在一个Vector或数组里,这样你可以增加未来的索引了.
保存一个对象
定义一个对象
下面的代码实例创建一个RestaurantInfo对象,并且使用它的设置方法来定义他的组件.
RestaurantInfo info = new RestaurantInfo(); info.setElement(RestaurantInfo.NAME, namefield.getText()); info.setElement(RestaurantInfo.ADDRESS,addressfield.getText()); info.setElement(RestaurantInfo.PHONE, phonefield.getText()); info.setElement(RestaurantInfo.SPECIALTY, specialtyfield.getText()); |
将一个对象加到一个Vector
调用addElement().
_data.addElement(info); |
保存一个更新的对象
调用PersistentObject的setContents(),然后调用commit()方法保存一个更新的对象
synchronized(store) { store.setContents(_data); store.commit(); } |
注: 当你做出改变时,同步一个持久对象,这样其他的线程在同一时间就不能做出改变.
获取一个对象
为了获取最近保存的对象,调用_data.lastElement().
public void run() { synchronized(store) { _data = (Vector)store.getContents(); if (!_data.isEmpty()) { RestaurantInfo info = (RestaurantInfo)_data.lastElement(); namefield.setText(info.getElement(RestaurantInfo.NAME)); addressfield.setText(info.getElement(RestaurantInfo.ADDRESS)); phonefield.setText(info.getElement(RestaurantInfo.PHONE)); specialtyfield.setText(info.getElement( RestaurantInfo.SPECIALTY)); } } } |
代码实例
本实例描述了如何创建一个应用程序,它允许用户存储一个关于喜爱的餐厅信息.
本实例也允许用户保存多个餐厅的信息,并且可以查看最新保存的餐厅信息.
例: Restaurants.java
/* Restaurants.java
* Copyright (C) 2004-2005 Research In Motion Limited.
*/
package com.rim.samples.docs.restaurants;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.system.*;
import net.rim.device.api.util.*;
import java.util.*;
import net.rim.device.api.i18n.*;
import net.rim.blackberry.api.invoke.*;
import net.rim.blackberry.api.browser.*;
import com.rim.samples.docs.baseapp.*;
import com.rim.samples.docs.resource.*;
public class Restaurants extends BaseApp
implements RestaurantResource,KeyListener, TrackwheelListener {
private AutoTextEditField namefield;
private AutoTextEditField addressfield;
private EditField phonefield;
private EditField websitefield;
private EditField specialtyfield;
private static Vector _data;
private static PersistentObject store;
private static ResourceBundle _resources;
private MenuItem saveItem = new MenuItem(_resources.getString(MENUITEM_SAVE), 110, 10) {
public void run() {
RestaurantInfo info = new RestaurantInfo(); info.setElement(RestaurantInfo.NAME, namefield.getText()); info.setElement(RestaurantInfo.ADDRESS, addressfield.getText()); info.setElement(RestaurantInfo.PHONE, phonefield.getText()); info.setElement(RestaurantInfo.WEBSITE, phonefield.getText()); info.setElement(RestaurantInfo.SPECIALTY, specialtyfield.getText()); _data.addElement(info); synchronized(store) { store.setContents(_data); store.commit(); } Dialog.inform(_resources.getString(APP_SUCCESS)); namefield.setText(null); addressfield.setText(null); phonefield.setText(""); websitefield.setText(""); specialtyfield.setText(""); } }; private MenuItem getItem = new MenuItem(_resources.getString(MENUITEM_GET), 110, 11) { public void run() { synchronized(store) { _data = (Vector)store.getContents(); if (!_data.isEmpty()) { RestaurantInfo info = (RestaurantInfo)_data.lastElement(); namefield.setText(info.getElement(RestaurantInfo.NAME)); addressfield.setText(info.getElement(RestaurantInfo.ADDRESS)); phonefield.setText(info.getElement(RestaurantInfo.PHONE)); websitefield.setText(info.getElement(RestaurantInfo.WEBSITE)); specialtyfield.setText(info.getElement(RestaurantInfo.SPECIALTY)); } } } }; private MenuItem phoneItem = new MenuItem(_resources.getString(MENUITEM_PHONE), 110, 12) { public void run() { synchronized(store) { String phoneNumber = phonefield.getText(); if ( phoneNumber.length() == 0) { Dialog.alert(_resources.getString(ALERT_NO_PHONENUMBER)); } else { PhoneArguments call = new PhoneArguments(PhoneArguments.ARG_CALL, phoneNumber); Invoke.invokeApplication(Invoke.APP_TYPE_PHONE, call); } } } }; private MenuItem browserItem = new MenuItem(_resources.getString(MENUITEM_BROWSER), 110, 12) { public void run() { synchronized(store) { String websiteUrl = websitefield.getText(); if (websiteUrl.length() == 0) { Dialog.alert(_resources.getString(ALERT_NO_WEBSITE)); } else { BrowserSession visit = Browser.getDefaultSession(); visit.displayPage(websiteUrl); } } } }; static { _resources = ResourceBundle.getBundle( RestaurantResource.BUNDLE_ID, RestaurantResource.BUNDLE_NAME); store = PersistentStore.getPersistentObject(0xdec6a67096f833cL); // Key is hash of test.samples.restaurants. synchronized (store) { _data = (Vector)store.getContents(); if (_data == null) { _data = new Vector(); store.setContents( _data ); store.commit(); } } } public static void main(String[] args) { Restaurants app = new Restaurants(); app.enterEventDispatcher(); } private static final class RestaurantInfo implements Persistable { // Data. private String[] _elements; // Fields. public static final int NAME = 0; public static final int ADDRESS = 1; public static final int PHONE = 2; public static final int WEBSITE = 3; public static final int SPECIALTY = 4; public RestaurantInfo() { _elements = new String[4]; for ( int i = 0; i < _elements.length; ++i) { _elements[i] = new String(""); } } public String getElement(int id) { return _elements[id]; } public void setElement(int id, String value) { _elements[id] = value; } } public Restaurants() { MainScreen mainScreen = new MainScreen(); mainScreen.setTitle(new LabelField( _resources.getString(APPLICATION_TITLE))); namefield = new AutoTextEditField( _resources.getString(FIELD_NAME), ""); addressfield = new AutoTextEditField( _resources.getString(FIELD_ADDRESS), ""); phonefield = new EditField( _resources.getString(FIELD_PHONE), "", Integer.MAX_VALUE, BasicEditField.FILTER_PHONE); websitefield = new EditField( _resources.getString(FIELD_WEBSITE), "", Integer.MAX_VALUE,BasicEditField.FILTER_URL); specialtyfield = new EditField( _resources.getString(FIELD_SPECIALTY), "", Integer.MAX_VALUE, BasicEditField.FILTER_DEFAULT); mainScreen.add(namefield); mainScreen.add(addressfield); mainScreen.add(phonefield); mainScreen.add(websitefield); mainScreen.add(specialtyfield); mainScreen.addKeyListener(this); mainScreen.addTrackwheelListener(this); pushScreen(mainScreen); } public void makeMenu( Menu menu, int instance ) { menu.add(saveItem); menu.add(getItem); menu.add(phoneItem); menu.add(browserItem); super.makeMenu(menu, instance); } public void onExit() { Dialog.alert(_resources.getString(APP_EXIT)); } } Last Updated:2007年2月1日