J2ME Guide – Part 5

After reading the first four modules we are now able to create a MIDlet managing its own GUI using HIGH and LOW level APIs. In this module we’ll talk about memory management, a very important feature of small device applications. We’ll deal with two types of memory:

- Volatile memory
- Non-volatile memory (persistent memory)

The first type is the memory the JVM obtains from the heap every time we create an instance of a class or when we use stack variables. We don’t have much control over this type of memory (and often we don’t need it) because every JVM (or KVM in our case) employs the Garbage Collector (GC) process, whose job is to remove every unnecessary object from the memory. This doesn’t mean we don’t have to keep memory usage in mind when we develop our applications. We’re dealing with devices with small footprint and so we must take into account the Garbage Collector, which removes every not referenced instance. If every object – even if it’s not used any more – has an alive reference, the GC can’t delete it, thus causing a Memory Overflow. A rule to keep in mind is to delete every unnecessary object setting all its references to null. This kind of memory is called volatile because we lose it when the application is turned off.

The second kind of memory is called non-volatile or persistent because the information it contains isn’t destroyed even when the application is off. Information stored in a persistent memory is mantained also if we turn the device off. It’s like an internal DataBase that in MIDP 2.0 specification is called Record Management System (RMS), which is the subject of the present module. The APIs linked to RMS are contained in the javax.microedition.rms package of MIDP 2.0. It’s a memory (that in some devices may be very small) which contains data as bytes. All kinds of persistent DB management in MIDP 2.0 applications, as we’ll see in our example Agenda, are wrappers of RMS RecordStore. Since we’ll need to convert some objects into byte arrays, we’ll see a concept similar to serialization which is not natively present in MIDP, as we saw in the first module.

Our Agenda application

In order to explain how to employ the RMS API, we’ll create a very simple Agenda midlet whose task is to insert, delete and find some contacts into our DB. Before starting the development of our RMS application we want to describe our Contact class, which will contain contact information and that will be the one we’ll insert, delete and search. If we read the Contact class code we notice it’s very simple. It has three String properties and no getters/setters methods because we don’t need them. We marked the three properties as volatile to avoid any multithreading problem when accessing them in a concurrent way. What we want to remark is the presence of two static methods we can use to convert a Contact object to and from a byte array. We need them because, when using the RMS RecordStore, we have to deal with this kind of data. Our MIDlet is a class managing navigation between a List and a Form, but here we just want to emphasize the use of the ContactStore class that, as we’ll see later, wraps and hides all RecordStore operations. When dealing with the ContactStore we’ll employ Contact objects, thus hiding the use of the RecordStore and of byte arrays.

Object Serialization in J2ME

If we look at the Contact class we can see there are two methods for converting objects into byte arrays and vice-versa. It’s a very useful pattern we’ll often meet in midlet programming. To convert a Contact object into a byte array we can follow these steps:

- Create a ByteArrayOutputStream to manage byte arrays
- Create a DataOutputStream for writing object properties of different types
- Write every object properties we want to make persistent using the right DataOutputStream method
- Take the result as a byte array from ByteArrayOutputStream

The opposite (creating a Contact object from a byte array) is done using a ByteArrayInputStream and a DataInputStream. This logic is coded into the following static methods:

public static final Contact unmarshal(byte[] record) throws IOException

public static final byte[] marshal(Contact contact) throws IOException


The rule is that every object must manage its own persistence; so, if we have a composite object, it’ll use the marshal/unmarshal methods of the components it contains. This is a way to manage object serialization in a MIDP environment and it will be useful also when we’ll talk about connections.

How to create a RecordStore

As we said before, our persistence logic is hidden inside a ContactStore object that implements the Singleton design pattern. Thus, the programmer hasn’t to manage more than one ContactStore object at any time. To create a ContactStore instance we use this instruction:

ContactStore contactStore = ContactStore.getInstance();


which is the factory method invocation. The first time we do so, the ContactStore builds the corresponding RecordStore using this RMS API:

recordStore = RecordStore.openRecordStore(CONTACT_STORE_NAME,true);


We create a RecordStore instance just giving it a name and specifying that if the RecordStore doesn’t exist we want to create it (that’s the purpose of the boolean parameter). If the RecordStore already exists, this method just returns it. Then, our ContactStore will delegate all our operations to the RecordStore method invocations after converting the Contact object into a byte array.

Searching Record and the RecordFilter interface

Before seeing how to insert and to delete a Contact from our ContactStore, we want to learn how to find a specific Contact. To do so we developed the following method:

public Contact[] searchContact(Contact contact) throws RecordStoreException,IOException


which employs a fundamental concept of RMS, which is described by the RecordFilter interface of the RMS API. This interface defines the operation:

public boolean matches(byte[] record)


and must be implemented by any RecordFilter realization describing the rules about record matching. For our purposes we created the class SearchRecordFilter which tells us when a given Contact matches another. In our case this happens if one Contact’s properties values are contained into another Contact. It’s a kind of search-by-example implementation. The SearchRecordFilter class implements just a matching rule. The important thing is that we can employ it for searching Contacts using this RecordStore method

RecordEnumeration recEn = recordStore.enumerateRecords(recordFilter,recordComparator,true);


which returns a RecordEnumeration implementation containing the matching records. As we can read in the RMS javadoc, this method uses three important parameters. The first is a RecordFilter implementation that, in this case, is described by the SearchRecordFilter class. The second one is a RecordComparator which allows us, as we’ll see later, to sort the records we read from the RecordStore. The last parameter is a boolean telling if the RecordEnumeration must be kept updated with the data into the RecordStore after its creation. A true value means less performance but ensures consistent data.

The RecordEnumeration interface allows us to iterate over a set of RecordStore items accessing them as byte arrays or through their id – an int value. This int information is very useful if we want to delete the record or to access it directly. In the Contact object we declare this property as transient because we don’t want to store it, but we just need its value when the data is read. In our case we convert records into Contact object arrays.
So, using different implementation of the RecordFilter interface, we can filter records in many ways. The implementations of the RecordComparator interface allow us to sort the records as we wish. If we don’t need any filtering or sorting, we can just set the input parameters to null.

In our case the method searchContact is fundamental throughout the ContactStore implementation since we will use it for Contact searching, listing and deleting.

Insert and Delete a Record

Our agenda midlet will insert and delete Contact information. To do so the ContactStore must be able to insert and to delete Contacts. Thus, we implemented the following two methods:

public void saveContact(Contact contact) throws IOException,RecordStoreException

public void deleteContact(Contact contact) throws IOException,RecordStoreException

The first one will use the corresponding method of RecordStore class which is:

public int addRecord(byte[] record,int first, int length);


In our case the first thing to do is to convert our Contact object into a byte array. The other parameters of the addRecord method tell which is the first index of the byte array to use, and the number of bytes to write. The method returns the recId (as a number) of the record just inserted. In our saveContact method implementation we don’t use this information, but we’ll employ it when reading the contact list.
To delete a record from the RecordStore we can use the method

public void deleteRecord(int recId);


with the recId as parameter. This is the reason we wanted to save that information. The record to delete is the one we get from a search operation.

Ordering records and the RecordComparator interface

Previously, we saw how to read a set of records following some filtering rules described in a RecordFilter interface implementation. The RMS API also allows us to specify a way to sort records using a specific implementation of the RecordComparator interface. In our case we created the class ContactRecordComparator whose job is to order records using just the lastname information. The operation described by the RecordComparator is:

public int compare(byte[] first, byte[] second)


whose parameters are the records to compare. The return value of the compare method is the usual output of the Java comparison operation: a negative, zero or positive value if the first record is less than, equal to or greater that the second one. In our case we convert byte arrays into Contact objects using the well known way, and then we use the compare method on String objects.

Conclusion

In this module we learned how to manage information in a persistent way using the RMS API defined in MIDP 2.0. We developed a simple Agenda application to exemplify the key concepts of memory management. .

References

[1] MIDP 2.0 API http://java.sun.com/javame/reference/apis/jsr118/
[2] API MIDP 2.0 documentation

Related Download: module_05.zi

Comments

comments

Leave a Reply

Your email address will not be published.


*