Using RMS on Motorola J2ME handsets
July 21, 2004
WHITE PAPER
Using RMS on Motorola handsets By Motocoder
E
ven though the RMS API’s are fairly simple to use and haven't changed much since MIDP 1.0, a developer may see some unexpected results when porting their application across different platforms. This guide outlines some tips to ensure portability and general usability for the end user, and gives guidance on how to cope with common problems such as data corruption or data loss.
RMS introduction “The Mobile Information Device Profile provides a mechanism for MIDlets to persistently store data and later retrieve it. This persistent storage mechanism is modeled after a simple record oriented database and is called the Record Management System.” [1] Using the RMS to store persistent data in your application may seem simple at a first glance. Applying some of the guidelines below may increase the user experience of your application and help you to prevent common mistakes when building an application for Motorola phones.
Performance There are numerous devices on the market supporting MIDP and they have different hardware and software affecting how the RMS acts in different situations. Performance is one of the areas where devices vary a lot. When developing applications using RMS to store persistent data, developers should always keep in mind that the target device might not be as fast as the PC emulator or even another phone. Just storing a single byte to persistent memory may seem to be a simple task, but there are a lot of hardware and software factors that affects the performance of this operation. In the article “J2ME devices: Real-world performance” at www.javaworld.com [2] shows that the performance of RMS operations can vary from microseconds to several seconds depending on the target device. Performance measurement depends not only on hardware and Virtual Machine implementation, but also on the application doing the benchmarking. Some operations may be performed in different ways to suit the underlying implementation better or worse and may have significant effect on performance results. The above reference is provided only to emphasize the difference in performance of various device platforms. Due to the varying performance of RMS operations across platforms, developers are strongly encouraged to display a dialog of some sort informing the end user that the application is busy while managing the persistent storage.
Data Loss Some user actions such as removing the battery while an application is running may result in data loss of an application’s RecordStores. A well written application can recover from such a situation and continue functioning. For Motorola devices specifically, developers should be aware that keeping a RecordStore object in the open state throughout the application lifetime greatly increases the chance of encountering this issue. To resolve this, developers are encouraged to close the RecordStore object as soon as the block of read and/or write operations are done.
Threading and Thread safety Many applications use threads in order to have concurrent execution of tasks and to improve the user experience of the application. However, this may also increase the complexity involved in managing an application’s RMS data. The paragraph quoted below is from the MIDP specification, highlighting the need for synchronizing RMS access within an application’s threads. “No locking operations are provided in this API. Record store implementations ensure that all individual record store operations are atomic, synchronous, and serialized, so no corruption will occur with multiple accesses. However, if a MIDlet uses multiple threads to access a record store, it is the MIDlet's responsibility to coordinate this access or unintended consequences may result. Similarly, if a platform performs transparent synchronization of a record store, it is the platform's responsibility to enforce exclusive access to the record store between the MIDlet and synchronization engine.” [1] Another section from the MIDP specification often overlooked is one about implementing a CommandListener and its commandAction() method. “The specification does not require the platform to create several threads for the event delivery. Thus, if a CommandListener method does not return or the return is not delayed, the system may be blocked. So, there is the following note to application developers: the CommandListener method should return immediately.” [3] This in conjunction with performance differences on platforms, an unfortunate mix of a CommandListener and RMS operations could block the system and make the application very user unfriendly. Below is a code snippet showing how you can trigger an RMS operation from a CommandListener. public class RMSTest extends MIDlet implements CommandListener { static final int RMS_ACTION_ADD = 1; static final int RMS_ACTION_LIST = 2; private RMSThread m_rmst; /* Do main class and form initialization here */ public void commandAction(Command c, Displayable s) { if (c == exitCommand) { destroyApp(false); } else if( c == addRms ) { m_rmst = null; System.gc(); m_frm.deleteAll(); m_rmst = new RMSThread( RMSTest.RMS_ACTION_ADD ); m_rmst.start();
} else if( c == listRms ) { m_rmst = null; System.gc(); m_frm.deleteAll(); m_rmst = new RMSThread( RMSTest.RMS_ACTION_LIST ); m_rmst.start(); } } public void dbgout( String s ) { m_frm.append( s+"\n" ); } class RMSThread extends Thread { private int m_action; public RMSThread( int action ) { m_action = action; } private void addRecord() { RecordStore db = null; RecordEnumeration enum = null; String writestr = "RMSTest"; byte record[]; try { dbgout( "open recordstore and write one record" ); db = RecordStore.openRecordStore( "test", true ); db.addRecord( writestr.getBytes(), 0, writestr.length() ); db.closeRecordStore(); dbgout( "done" ); } catch( Throwable t ) { dbgout( t.toString() ); } } private void listRecords() { RecordStore db = null; RecordEnumeration enum = null; String writestr = "RMSTest"; byte record[]; try { dbgout( "List records in RecordStore" ); db = RecordStore.openRecordStore( "test", false ); enum = db.enumerateRecords( null, null, false ); while( enum.hasNextElement() ) { record = enum.nextRecord(); dbgout( new String( record ) ); } enum.destroy(); db.closeRecordStore(); dbgout( "done" ); } catch( Throwable t ) { dbgout( t.toString() ); } }
public void run() { switch( m_action ) { case RMSTest.RMS_ACTION_ADD: addRecord(); break; case RMSTest.RMS_ACTION_LIST: listRecords(); break; } } } }
There are a number of ways you can launch a new thread to perform your RMS operation or other type of task, the above example is just to show you the concept of it. In fact the example above can be optimized so that it doesn’t create a new thread for each of these tasks, but instead adds the task to a queue in the existing RMSThread, which is then served when the Thread is ready for it. That would reduce some overhead in creating, starting and stopping new threads.
Motorola SDK Emulator notes Under certain circumstances an application run on the Emulator7.2 or Emulator 7.5 environment may experience strange errors when using RMS functions. This is usually caused by some temporary files in Emulator7.x\bin\1\RSC not being deleted after a previous run of a different application. Removing the entire 1\RSC directory structure will resolve this problem. This should ONLY be done if you are experiencing problems with your application using RMS operations such as creating a RecordStore even though the application doesn’t have any previous RecordStore objects created.
This is ONLY applicable to the emulator!
Conclusion By following the guidelines below, the user experience of an application can be greatly improved together with the portability of an application across various MIDP platforms. Make sure the MIDlet displays a “please wait” dialog during RMS operations or other lengthy task. Make sure the MIDlet can cope with corrupted RMS data (i.e. gracefully recover and reconstruct the database). Make sure the MIDlet synchronizes access of RMS operations from multiple threads. CommandListener implementations should create a separate thread to perform any RMS operations.
Don’t keep RecordStore objects open. Emulator7.x based emulators may generate false RecordStoreException when creating databases.
References 1. JSR 37, JSR 118, RecordStore class description. http://java.sun.com/products/midp/ 2. J2ME devices: Real-world performance. http://www.javaworld.com/javaworld/jw-10-2002/jw-1025j2mebenchmark.html 3. JSR 37, JSR 118, CommandListener interface description. http://java.sun.com/products/midp/