×
Namespaces

Variants
Actions

Persistent Data in Java ME

From Nokia Developer Wiki
Jump to: navigation, search
Article Metadata
Article
Created: grahamhughes (15 Jan 2010)
Last edited: hamishwillee (30 Jul 2013)

Contents

Introduction

This article demonstrates two methods for storing persistent information in Java ME.

FileConnection vs. RecordStore

There are two options for storing persistent information.

FileConnection API

  • Provides access to files shared with other applications
  • Provides access to user items, such as images and music
  • Is optional: not supported by all devices
  • Is security-restricted: you may not have write access, and access (either read or write) might be limited to certain folders

RecordStore API

  • AKA Record Management System (RMS)
  • Is part of MIDP-1.0, and so is available on all MIDP devices
  • Is not subject to security restrictions: no signing needed, no security prompts
  • Is ideal for information used only by one application
  • Data is usually private, inaccessible to other applications
  • Can be shared between MIDlets in the same suite (JAR)
  • Can be shared between MIDlets in different suites (JARs), but this may be subject to security restrictions and may require signing
  • May be limited in size, depending on device (MIDP-1.0 specifies a minimum of 8k, Nokia Series 40s have minimum 20k (depending on model, most have more), Nokia S60s impose no limit)


Most applications will want to store some kind of persistent information, like user preferences. For this, RMS is the best choice.

For games, RMS is ideal for user preferences (sound on/off, etc.) and high-scores.

Because of the security issues, the FileConnection API is best avoided unless you need its functionality. Most accesses to the file system will result in a security prompt to the user. Options for disabling prompts, and the level of access available to the file system, will vary from device to device, resulting in a large need for testing if you're targeting a range of devices.

Moreover, files saved via the FileConnection API may be accessible to the user outside of the application, and may end in unwanted places, like the user gallery.

Using RMS

Records in RMS are identified by a record-ID. Record IDs are allocated by the API and are not under your control.

  • Records store a byte[] in each record
  • The first record added is always indexed 1 (one)
  • Each record has an ID one greater than the previous record added
  • Record IDs never change, so the record with ID "4" always has the ID "4"
  • Record IDs are never reused, so when a record is deleted, its ID disappears with it


This means that if you add four records, they will have IDs 1, 2, 3 and 4. There is never a record zero. If record #3 is deleted, you will have record IDs 1, 2, and 4. Record stores are not like Vectors, IDs don't "move up" when an item is deleted. This is important: it allows you to store the record ID of one record, inside another record (like using a primary/foreign key relationship in a database) to link records together. This would become impossible of record IDs changed.

When using RMS, RecordStores must be closed after they have been opened. RecordStores that remain open when the application exits may lose data or become corrupt. For this reason, make use of finally{} to ensure that RecordStores are closed in all circumstances.

Some devices have buggy implementations of RMS. These devices may suffer from record store corruption after many adds, deletes and updates. This is especially the case when records are deleted or updated from the middle of the store. One technique is always to use the same record (only have one record in a record store).

public void saveData(String recordStoreName, byte[] data) throws RecordStoreException {
RecordStore rs = RecordStore.openRecordStore(recordStoreName, true);
try {
if (rs.getNumRecords() == 0) {
// first time: add new record
rs.addRecord(data, 0, data.length);
} else {
// subsequently: update existing record
rs.setRecord(1, data, 0, data.length);
}
} finally {
// this MUST ALWAYS HAPPEN, no matter what
rs.closeRecordStore();
}
}

Another technique is to delete the existing record store when updating (using deleteRecordStore()), and re-create it, saving the new data. This technique can safely be used with multiple records.

/*
In this example, the Vector holds the data from the record store as the raw
byte arrays. These methods could easily be modified to convert between byte
arrays and application-domain data objects, so that conversion is encapsulated
here. For example, the loop body in saveData() would become:
 
Employee emp = (Employee) data.elementAt(i);
byte[] record = emp.toByteArray();
rs.addRecord(record, 0, record.length);
 
and in loadData():
 
byte[] record = rs.getRecord(i);
Employee emp = new Employee(record);
data.addElement(emp);
*/

 
public void saveData(String recordStoreName, Vector data) throws RecordStoreException {
try {
RecordStore.deleteRecordStore(recordStoreName);
} catch (RecordStoreNotFoundException rsnfe) {
// ignore this exception only
}
RecordStore rs = RecordStore.openRecordStore(recordStoreName, true);
try {
int size = data.size();
for (int i = 0; i < size; i++) {
byte[] record = (byte[]) data.elementAt(i);
rs.addRecord(record, 0, record.length);
}
} finally {
rs.closeRecordStore();
}
}
 
public Vector loadData(String recordStoreName) throws RecordStoreException {
Vector data = new Vector();
RecordStore rs = RecordStore.openRecordStore(recordStoreName, true);
try {
int size = rs.getNumRecords();
for (int i = 1; i <= size; i++) {
data.addElement(rs.getRecord(i));
}
} finally {
rs.closeRecordStore();
}
return data;
}

As well as helping reliability, RecordStore performance is also enhanced by using fewer, larger records. In tests using a Nokia Series 40 device, reading 100 records of 100 bytes each took 80 times longer than reading a single record of 10,000 bytes (the same amount of data in total).

Multiple units of data can easily be written to a single byte[], using a ByteArrayOutputStream and a DataOutputStream.

/*
storing an array of Strings into a single byte[]
*/

 
String[] names = getNamesFromSomewhere();
 
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
 
dout.writeInt(names.length);
for (int i = 0; i < names.length; i++) {
dout.writeUTF(names[i]);
}
 
byte[] data = bout.toByteArray();

This process can easily be reversed.

byte[] data = getRecordFromRecordStore();
 
ByteArrayInputStream bin = new ByteArrayInputStream(data);
DataInputStream din = new DataInputStream(bin);
 
String[] names = new String[din.readInt()];
 
for (int i = 0; i < names.length; i++) {
names[i] = din.readUTF();
}

Even when just using a single item of data, this is often the best technique. When storing String data, writeUTF() and readUTF() ensure that text is written in a unicode-safe format on all devices. Beware of using String.getBytes(), which is not always unicode-safe.

Using FileConnection

See:

This page was last modified on 30 July 2013, at 08:20.
141 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×