ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: J2ME in a Nutshell

The Mobile Information Device Profile and MIDlets, Part 4

by Kim Topley

To illustrate the MIDlet lifecycle and how it can be controlled, we'll create a very simple MIDlet that does the following:

Related Articles:

The Mobile Information Device Profile and MIDlets, Part 5
This is the final excerpt in a series on MIDP and MIDlets from J2ME in a Nutshell, focusing on the delivery and installation of MIDlets.

The Mobile Information Device Profile and MIDlets, Part 3
Part three of a five-part book excerpt from O'Reilly's J2ME in a Nutshell by Kim Topley. This installment focuses on a MIDlet's three states: paused, active, and destroyed.

The Mobile Information Device Profile and MIDlets, Part 2
This is the second of a five part book excerpt series based on O'Reilly's J2ME in a Nutshell by Kim Topley. Part 2 focuses on MIDlets and their suites.

The Mobile Information Device Profile and MIDlets, Part 1
This is the first of a five part book excerpt series based on O'Reilly's J2ME in a Nutshell by Kim Topley. Part one is an overview of the Mobile Independent Device Profile and the MIDP Java platform.

Since you haven't yet seen how to create user interfaces, this example MIDlet communicates by writing messages to its standard output stream. On a real device, you can't see what is written to standard output or standard error (unless you are using debug facilities provided by the device vendor), but most device emulators provide a way to monitor the content of these streams. There are several products available that allow you to build and test MIDlets either in an emulated environment or on the real device; some of these products are described in Chapter 9. Here, we'll use the Wireless Toolkit, which is available free of charge from Sun.

Building a MIDlet with the Wireless Toolkit

The Wireless Toolkit provides an implementation of MIDP together with an emulator that can be customized to look and behave somewhat like a number of real cell phones. It can also be used in conjunction with a third-party emulator that allows you to see how your MIDlets would behave on handhelds that are based on PalmOS. It is not, however, a complete development enviroment, because it does not provide an integrated editor to allow you to create, view, and modify source code. Consequently, if you want to use the Wireless Toolkit as part of a complete development cycle, you will need a text editor or IDE to manage the source code. At the time of writing, the Wireless Toolkit can be installed to integrate with Forte for Java, which is available for download from Sun's web site, and Borland JBuilder, but any IDE will do.

J2ME in a Nutshell

Related Reading

J2ME in a Nutshell
By Kim Topley

The first step when using the Wireless Toolkit is to create a project, which manages the source code, classes, and resources corresponding to a MIDlet suite. To do this, start the KToolbar and press the New Project button to open the New Project dialog, which is shown in Figure 3-5. For this example, the name of the MIDlet's main class should be ora.ch3.ExampleMIDlet, and the project name can be anything you like.

Dialog box.
Figure 3-5. Creating a new project with the Wireless Tooklit.

When you press the Create Project button in the dialog, the Wireless Toolkit opens another window, shown in Figure 3-6; it contains a set of tabs that allow you to provide the attributes used to generate the manifest for the MIDlet's JAR and the JAD file. You can edit these attributes by clicking the cell that you want to change and typing the new value. The fields on the Required tab contain the attributes shown in Table 3-2 that are marked as mandatory. Most of the values supplied by default can be used without modification. For example, the MIDlet-Name field (which is actually the name that will be used for the MIDlet suite, not for any individual MIDlet) matches the project name, and the name of the JAR that will be created is also derived from the project name. The only field you might want to change on this tab is MIDlet-Vendor, which is initially set to Sun Microsystems by default.

Settings dialog.
Figure 3-6. Setting required attributes for a MIDlet suite.



To define the MIDlets that should be included in the MIDlet suite, select the MIDlets tab. Initially, this contains a single row whose content is constructed from the name of the project. In this example, the suite contains a single MIDlet called ExampleMIDlet in the package ora.ch3, so you should press the Edit button and edit the values for the MIDlet-1 attribute on this tab so that it looks like this:

Key

Name

Icon

Class

MIDlet-1

ExampleMIDlet

/ora/ch3/icon.png

ora.ch3.ExampleMIDlet

In this example, the name assigned to the MIDlet matches the class name (ignoring the package prefix), but this need not be the case. Notice also that although the class name is specified in the usual way, with the parts of the name separated by periods, the location of the icon is specified as a filename, in which the path components are separated by a "/" character. If an icon is present, an absolute pathname must be provided here. If the MIDlet does not have an associated icon, this field should be left blank.

For a MIDlet suite with more than one MIDlet, you add an extra line for each MIDlet. It is important that consecutive numbers are used in the key field, so the next MIDlet to be added in this example would need to have the key MIDlet-2. Other required class files must be included in the JAR, but they should not be included in the MIDlets list.

For this example, we are also going to use a user-defined attribute. A user-defined attribute is a private attribute that can be set in the manifest and/or the JAD; its value can be retrieved at runtime by any MIDlet in the MIDlet suite. These attributes provide a mechanism similar to the setting of system properties in J2SE and allow the operation of the MIDlet to be customized without the need to recompile source code. In this example, we'll use a user-defined attribute to specify the length of a timer. To set the value of the attribute, select the User Defined tab and press the Add button. In the dialog box that appears, supply the property name as Timer-Interval and press OK. This creates a new entry in the table on the User Defined tab. Click in the Value cell, and type the required value, which, in this case, should be 3000. The property name is case-sensitive and, to avoid confusion with reserved attribute names, should not begin with "MIDlet-". The property value is always a string that is interpreted by the MIDlet. In this case, it represents the timer interval in milliseconds, so the value given here results in a timer that has a three-second interval. You'll see shortly how the MIDlet retrieves the values of user-defined attributes.

This completes the setting of the MIDlet's attributes. To save them, press the OK button at the bottom of the dialog. You can change these settings (perhaps to add extra MIDlets) at any time by pressing the Settings . . . button on the main KToolbar window, which is shown in Figure 3-7.

Dialog box.
Figure 3-7. The main window of the Wireless Tooklit KToolbar

The next step is to place the source code and the icon for the MIDlet where the Wireless Toolkit can get access to them. Most IDEs allow you to choose where your project source files are kept, but the Wireless Toolkit uses a fixed filesystem layout for each project, based beneath the directory in which the Toolkit was originally installed. The name of the top-level directory for a project is derived from the name given to the project when it was created. If, for example, you installed the Windows Toolkit in the directory c:\J2MEWTK, all the files for the Chapter3 project need to be placed below the directory c:\J2MEWTK\apps\Chapter3. When the Chapter3 project was created, the toolkit created the following four directories below the main directory for the project:

src
Holds the source code for the MIDlets and any shared classes

res
Holds any resources required by the MIDlets, such as icons

lib
Holds JAR or ZIP files for third-party libraries that the MIDlets need

bin
Holds the JAR, JAD and manifest files

Before building the project, you need to place the appropriate files in the src, res and lib subdirectories. This example has one source file and a single icon, which can both be found in the directory ora\ch3 of the source code for this book. The package structure used by the MIDlet must be reflected in the directory layout as seen by the Wireless Toolkit, as it would be by an IDE. Therefore, to install the files where the Wireless Toolkit can use them, you should copy them as follows, creating the ora\ch3 subdirectory beneath both the src and res directories while doing so:

Source

Destination

ora\ch3\ExampleMIDlet.java

c:\J2MEWTK\apps\Chapter3\src\ora\ch3\ExampleMIDet.java

ora\ch3\icon.png

c:\J2MEWTK\apps\Chapter3\res\ora\ch3\icon.png

Once the files have been placed in the correct directories, the next step is to build the project by pressing the Build button on the KToolbar main window. The build process performs the following steps:


TIP:   The source code for this book is actually stored in two different directory hierarchies, one for standard IDEs, the other for the J2ME Wireless Toolkit. This example showed you how to create a project from scratch using existing source files. A quicker way to use the book's source code is to copy the content of the directory wtksrc into c:\J2MEWTK\apps. This will give you subdirectories called Chapter3, Chapter4, etc., that contain all the source code and resources for each chapter's examples in the format expected by the J2ME Wireless Toolkit. To use each set of examples, select Open Project on the KToolBar main screen instead of Create Project, and then select the project from the dialog box that appears.




Running a MIDlet

At this stage, the JAR file has not been created, but you can nevertheless test the MIDlet suite by selecting an appropriate target device on the KToolbar main window and pressing the Run button. This loads the MIDlet classes, its resources, and any associated libraries from the classes, res, and lib subdirectories. If you select the default gray phone and press the Run button, the emulator starts and displays the list of MIDlets in this suite, as shown in Figure 3-8.

Screen shot.
Figure 3-8. The Wireless Toolkit emulator.

When the MIDlet suite is loaded, the device's application management software displays a list of the MIDlets that it contains and allows you to select the one you want to run. In this case, even though the suite contains only one MIDlet, the list is still displayed, as shown in Figure 3-8. Given the current lack of security for MIDlets imported from external sources, it would be dangerous for the device to run a MIDlet automatically, and, by giving the device user the chance to choose a MIDlet, it allows him the opportunity to decide not to run any of the MIDlets if, for any reason, they are thought to be a security risk or otherwise unsuitable. It is not obvious, though, on what basis such a decision would be made, since the user will see only the MIDlet names at this stage, but requiring the user to confirm that a MIDlet should be run transfers the ultimate responsibility to the user. In this case, the device displays the MIDlet name and its icon (the exclamation mark) as taken from the MIDlet-1 attribute in the manifest file. The device is not obliged to display an icon, and it may use its own icon in preference to the one specified in the manifest.

When you run the MIDlet suite this way, the Wireless Toolkit compiles the source code with the option set to save debugging information in the class files, and it does not create a JAR file. If you want to create a JAR, you can do so by selecting the Package item from the Project menu. This rebuilds all the class files without debugging enabled, which reduces the size of the class files, a measure intended to keep the time required to download the JAR to a cell phone or PDA as small as possible. It also extracts the content of any JARs or ZIP files it finds in the lib subdirectory and includes them in the MIDlet JAR, after running the preverifier over any class files that it finds in these archives. The JAR can be used, along with the JAD file, to distribute the MIDlet suite for installation into a device over a network, as will be shown in the later section "Delivery and Installation of MIDlets.

Further information on the Wireless Toolkit and other MIDlet development environments can be found in Chapter 9.

A Simple MIDlet

Now let's look at the implementation of the ExampleMIDlet class you have just built and packaged with the Wireless Toolkit. This simple MIDlet demonstrates the lifecycle methods that were described in the earlier section "MIDlet Execution Environment and Lifecycle, and it also illustrates how the MIDlet's foreground activity interacts with background threads, as well as how to create and use timers. The code for this example in shown in Example 3-1. For clarity, the timer-related code has not been included in the code listing; you'll see how that works when timers are discussed later in this chapter.

Example 3-1: A Simple MIDlet

package ora.ch3;
 
import java.util.Timer;
import java.util.TimerTask;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
 
public class ExampleMIDlet extends MIDlet {
    
    // Flag to indicate first call to startApp
    private boolean started = false;
    
    // Background thread
    private Thread thread;
    
    // Timer interval
    private int timerInterval;
    
    // Timer
    private Timer timer;
    
    // Task to run via the timer
    private TimerTask task;
    
    // Required public constructor. Can be omitted if nothing to do and no
    // other constructors are created.
    public ExampleMIDlet(  ) {
        System.out.println("Constructor executed");
        
       // Get the timer interval from the manifest or JAD file.
        String interval = getAppProperty("Timer-Interval");
        timerInterval = Integer.parseInt(interval); 
        System.out.println("Timer interval is " + interval);
    }
        
    protected void startApp(  ) throws MIDletStateChangeException {
        if (!started) {
            // First invocation. Create and start a timer.
            started = true;            
            System.out.println("startApp called for the first time");
            startTimer(  );
        } else {
            // Resumed after pausing. 
            System.out.println("startApp called following pause");
        }
        
        // In all cases, start a background thread.
        synchronized (this) {
            if (thread == null) {
                thread = new Thread(  ) {
                    public void run(  ) {
                        System.out.println("Thread running");
                        while (thread == this) {
                            try {
                                Thread.sleep(1000);
                                System.out.println("Thread still active");
                            } catch (InterruptedException ex) {
                            }
                        }
                        System.out.println("Thread terminating");
                    }
                };
            }
        };
         thread.start(  )
    }
 
    protected void pauseApp(  ) {
        // Called from the timer task to do whatever is necessary to pause
        // the MIDlet.
        // Tell the background thread to stop.
        System.out.println("pauseApp called.");
        synchronized (this) {
            if (thread != null) {
                thread = null;
            }
        }
    }
 
    protected void destroyApp(boolean unconditional) 
                            throws MIDletStateChangeException {
        // Called to destroy the MIDlet.
        System.out.println("destroyApp called - unconditional = " 
                            + unconditional);
        if (thread != null) {
            Thread bgThread = thread;
            thread = null;      // Signal thread to die
            try {
                bgThread.join(  );
            } catch (InterruptedException ex) {
            }
        }
        stopTimer(  );
    }
    
    // Timer code not shown here
}

This simple MIDlet does two things:

The code listing shows the implementation of the MIDlet's constructor and its startApp( ), pauseApp( ) and destroyApp( ) methods. A MIDlet is not required to do anything in its constructor and may instead defer initialization until the startApp( ) method is executed. In this example, the constructor prints a message so that you can see when it is being executed. It also performs the more useful function of getting the interval for the timer that will be used to change the MIDlet's state. It is appropriate to put this code in the constructor because this value needs to be set only once. The timer value is obtained from the Timer-Interval attribute that was specified in the settings dialog of the Wireless Toolkit and subsequently written to the JAD file. Here is what the JAD file created for this MIDlet suite actually looks like:

MIDlet-1: ExampleMIDlet, /ora/ch3/icon.png, ora.ch3.ExampleMIDlet
MIDlet-Jar-Size: 100
MIDlet-Jar-URL: Chapter3.jar
MIDlet-Name: Chapter3
MIDlet-Vendor: J2ME in a Nutshell
MIDlet-Version: 1.0
Timer-Interval: 3000

A MIDlet can read the values of its attributes using the following method from the MIDlet class:

public final String getAppProperty(String name);

This method looks for an attribute with the given name; it looks first in the JAD file, and then, if it was not found there, in the manifest file. Attributes names are case-sensitive and scoped to the MIDlet suite, so every MIDlet in the suite has access to the same set of attributes. The getAppProperty( ) method can be used to retrieve any attributes in the JAD file or the manifest, so the following line of code returns the name of the MIDlet's suite, in this case Chapter3:

String suiteName = geAppProperty("MIDlet-Name");

The timer interval for this MIDlet is obtained as follows:

String interval = getAppProperty("Timer-Interval");
timerInterval = Integer.parseInt(interval);

Once the value in the form of a string has been retrieved, the next step is to convert it to an integer by calling the Integer parseInt( ) method. If the Timer-Interval attribute is not included in the JAD file or manifest (or if its name is misspelled), getAppProperty( ) returns null, and the parseInt( ) method throws an exception. A similar thing happens if the attribute value is not a valid integer. Notice that the constructor does not bother to catch either of these exceptions. The main reason for catching an exception is to display some meaningful information to the user and possibly allow recovery, but, strictly speaking, the MIDlet is not allowed to use the user interface in the constructor, so attempting to post a message would not necessarily work. The most appropriate thing to do in a real MIDlet is to install a default value for the timer interval and arrange to notify the user from the startApp( ) method, when access to the user interface is possible. In this simple example, we allow the exception to be thrown out of the constructor, which causes the MIDlet to be destroyed. Additionally, the version of MIDP in the Wireless Toolkit does, in fact, display the exception on the screen, but vendor implementations are not bound to do so.

Once the constructor has completed execution, the device eventually calls the MIDlet's startApp( ) method, which allocates any resources that the MIDlet needs. The startApp( ) method is also called when the MIDlet is resumed after being in the Paused state. In that case, however, it should allocate only the resources that were released by pauseApp(). A boolean variable called started, which is false only when startApp( ) is entered for the first time, is used to distinguish these two cases:

Since the timer is going to be active throughout the lifetime of the MIDlet, it could have been allocated in the constructor. We deferred creating the timer until startApp( ) executes for the first time, however, because it isn't actually needed until that point; it is better, in an environment with such limited memory, to delay allocating resources until they are needed. The decision whether to commit resources in the constructor or in the startApp( ) method depends on the MIDlet and must therefore be made on a case-by-case basis.

The pauseApp( ) method is relatively simple. Its job is to release any resources that the MIDlet does not need while it is not in the Active state. The MIDlet is making use of only two resources:

Clearly, we can't stop the timer when the MIDlet is paused, because the timer is responsible for resuming it later. Therefore, the only resource the pauseApp( ) method can release is the background thread, by arranging for it to stop execution.

How is the pauseApp( ) method going to stop the background thread? The J2SE Thread class has two methods that might help: stop( ) and interrupt( ). Neither of these methods is available in the CLDC version of Thread, however, so it is not possible to act directly on the background thread to stop it. Instead, we use a common mechanism, a shared variable that the thread inspects from time to time to find out whether it has been asked to stop. In this case, the MIDlet class keeps a reference to the Thread instance in a variable called thread. In order to stop the thread, the pauseApp( ) method sets this variable to null, while the main loop of the background thread checks its value on each pass:

public void run(  ) {
    System.out.println("Thread running");
    while (thread == this) {
        try {
            Thread.sleep(1000);
            System.out.println("Thread still active");
        } catch (InterruptedException ex) {
        }
    }
    System.out.println("Thread terminating");
}

You'll notice that this code actually checks not whether the thread variable is null, but whether it is pointing to the background thread itself. This prevents a race condition in which the pauseApp( ) method clears thread to null, and the timer thread resumes the MIDlet before the background thread restarts following the sleep( ) call and checks its value. In this case, the startApp( ) method has started a new thread and stored its reference in thread, which therefore will not be null when the previous code checks it.

Finally, the destroyApp( ) method needs to stop the background thread and stop and release the timer. The thread can be stopped just as it is in the pauseApp( ) method. However, destroyApp( ) also waits for the thread to terminate so that it can guarantee that the MIDlet is not using any resources when it returns. It does this by calling the Thread.join( ) method, which blocks until the thread terminates (and returns immediately if it has already terminated). The stopTimer( ) method, which destroyApp( ) calls to stop and release the timer, is described in the next section.

If you now launch the MIDlet from the emulator, you'll see the results in the Wireless Toolkit's console window, an extract of which follows:

Constructor executed
Timer interval is 3000
startApp called for the first time
Timer started.
Thread running
Thread still active
Thread still active
Timer scheduled
>> Pausing MIDlet
pauseApp called.
Thread still active
Thread terminating
Timer scheduled
>> Resuming MIDlet
startApp called following pause
Thread running

As you can see, the constructor is executed first; it reads the value of the timer interval from the JAD file. Then startApp( ) is called, and it detects that it is being called for the first time and starts both the timer and the background thread. The "Thread running" and "Thread active" messages are printed by the background thread itself and show that the thread executes its loop twice before the timer fires. The code that executes when the timer expires, which will be shown in the next section, alternately pauses and resumes the MIDlet. In this case, as you can see, pauseApp( ) is called, which signals the background thread to stop running; the "Thread terminating" message indicates that the thread detects that it has been told to stop. Three seconds later, the timer expires again and resumes the MIDlet, causing its startApp( ) method to be invoked again to recreate the background thread. This process continues through two cycles, at which point the timer code destroys the MIDlet.

Timers and TimerTasks

Code to be executed when a timer expires should be implemented as a TimerTask and scheduled by a Timer. The Timer class provides the ability to execute sequentially one or more TimerTasks in a dedicated background thread. Usually, a MIDlet creates a single Timer to schedule all its TimerTasks, but it is possible to have more than one Timer active, each running its assigned TimerTasks in its own thread.

TimerTask is an abstract class with three methods:

public abstract void run(  );
public boolean cancel(  );
public long scheduledExecutionTime(  );

You create a unit of work to be scheduled by a Timer by subclassing TimerTask and implementing the run( ) method. You can schedule the run( ) method to be executed just once or to be executed repeatedly at either a fixed interval or a fixed rate. You can use the TimerTask cancel( ) method to stop future execution of a specific TimerTask. You may invoke it from the run( ) method, in which case the current execution of the task is allowed to complete, or you make invoke it from somewhere else. This method returns true if the task was scheduled to run either once or repeatedly and has been canceled; it returns false if the task was not associated with a Timer or if it had had been scheduled to be run once and has already run. The scheduledExecutionTime( ) method gets the time at which the task was most recently executed by its associated Timer. If called from within the run( ) method, it returns the time at which the run( ) method began execution. The value returned by this method is the number of milliseconds since midnight, January 1, 1970, which is the same as that returned by the System currentTimeMillis( ) method. If this method is called before the task is scheduled for the first time, its return value is undefined.

The Timer class has two methods that can be used to arrange for a task to be run exactly once:

public void schedule(TimerTask task, Date time);
public void schedule(TimerTask task, long delay);

The first of these methods schedules the task at the given time or as soon as possible afterwards; the second runs the task when a given time interval, specified in milliseconds, has passed. There are four methods that schedule a task for repeated execution:

public void schedule(TimerTask task, Date time, long period);
public void schedule(TimerTask task, long delay, long period);
public void scheduleAtFixedRate(TimerTask task, Date time, long period);
public void scheduleAtFixedRate(TimerTask task, long delay, 
     long period);

The difference between these methods is that the first two apply a fixed delay between successive executions of the task, and the last two attempt to execute the task at a fixed rate. In both cases, the desired interval between task executions is given by the period parameter. Figure 3-9 shows how fixed-delay and fixed-rate scheduling differ.

Diagram.
Figure 3-9. Fixed-delay (top) and fixed-rate scheduling of TimerTasks

In this example, task A is scheduled to run once every second; task B runs once, starting 900 milliseconds along the time line shown in the diagram. Task A first runs at T+0, followed by task B, which begins its execution at T+900ms. Task B takes 200 milliseconds to complete, however, which means that it is still running at T+1 second, when task A is supposed to run for the second time. Since a Timer can schedule only one TimerTask at a time, the execution of task A is delayed until task B finishes. Task A's second run begins, therefore, at T+1100ms. The difference between fixed-delay and fixed-rate scheduling is what happens as a result of this delay:

With fixed-delay scheduling, therefore, any delay affects all future executions of the task. With fixed-rate scheduling, however, an attempt is made to "ignore" the delay and schedule the task again where it would have run had there been no delay.

In some cases, additional executions of a fixed-rate task may be required to ensure that it runs the correct number of times when viewed over a long period. When this is necessary, the task may be run two or more times in succession to catch up with the number of times that it should have been run. For example, fixed-rate scheduling would be appropriate if you were using a timer to trigger redrawing the second hand of a clock displayed on the screen. Delayed execution of the redrawing task would cause the second hand to move more slowly, but the extra executions would ensure that it eventually moved forward to catch up with the real time. By contrast, using fixed delay execution in this case would result in the clock losing time that it would never make up, because execution delays are never corrected.

You may be able to reduce timing delays by using more than one Timer and dividing tasks among the Timers, because each Timer uses its own Thread. This only works, however, if the platform has more than one processor (which is unlikely in a J2ME environment), or if it has preemptive thread scheduling and chooses to suspend the thread of the Timer scheduling the long-running task B in favor of the thread for task A's Timer. The most reliable way to obtain predictable timer scheduling, however, is to ensure that code to be executed by a TimerTask executes as quickly as possible and does not block.

Like TimerTask, the Timer class has a cancel( ) method:

public void cancel(  );

This method cancels all the TimerTasks associated with the Timer. The Timer's thread stops executing when it has no more TimerTasks to be scheduled and there are no live references to it.

Example 3-2 shows the timer-related code for our example MIDlet.

Example 3-2: Using a MIDlet Timer

// Starts a timer to run a simple task
private void startTimer(  ) {
    
    // Create a task to be run
    task = new TimerTask(  ) {
        private boolean isPaused;
        private int count;
        
        public void run(  ) {
            // Pause or resume the MIDlet.
            System.out.println("Timer scheduled");
            if (count++ == 4) {
                // Terminate the MIDlet
                try {
                    ExampleMIDlet.this.destroyApp(true);
                } catch (MIDletStateChangeException ex) {
                    // Ignore pleas for mercy!
                }
                ExampleMIDlet.this.notifyDestroyed(  );
                return;
            }
            if (isPaused) {
                System.out.println(">> Resuming MIDlet");
                ExampleMIDlet.this.resumeRequest(  );
                isPaused = false;
            } else {
                System.out.println(">> Pausing MIDlet");
                isPaused = true;
                ExampleMIDlet.this.pauseApp(  );
                ExampleMIDlet.this.notifyPaused(  );
            }                
        }
    };
        
    // Create a timer and schedule it to run
   timer = new Timer(  );
    timer.schedule(task, timerInterval, timerInterval); 
    System.out.println("Timer started.");
}
    
// Stops the timer
private void stopTimer(  ) {
    if (timer != null) {
        System.out.println("Stopping the timer");
        timer.cancel(  );
    }
}

The startTimer( ) method, which is called during the first invocation of startApp( ), creates a TimerTask and schedules it to be run by a Timer object with the initial delay and repeat period given by the Timer-Interval attribute obtained from the application descriptor. The stopTimer( ) method is called from destroyApp( ). It cancels the TimerTask and the Timer by calling the Timer's cancel( ) method.

The code that is executed when the timer expires is worth looking at because it demonstrates how to control the lifecycle of a MIDlet. The intent of this code is to pause the MIDlet if it is active when the timer expires and resume if it is paused. However, there is no method that allows a MIDlet to find out whether it is in the Paused state, so the timer code has to retain this state for itself using an instance variable called isPaused. The code used to suspend the MIDlet looks like this:

isPaused = true;
ExampleMIDlet.this.pauseApp(  );
ExampleMIDlet.this.notifyPaused(  );

The notifyPaused( ) method tells the MIDlet scheduler that the MIDlet wants to be moved into the Paused state. As stated earlier, when the MIDlet calls this method, it is assumed that it is ready to be suspended, so its pauseApp( ) method is not called to give it a chance to release resources. For this reason, the timer code calls the MIDlet's pauseApp( ) method directly before suspending it. Moving a MIDlet to the Paused state simply means that it no longer has access to the screen and so does not receive user interface events in response to key presses or pointer movements. Timers and background threads belonging to a suspended MIDlet continue to be scheduled, provided that they are not stopped by the MIDlet itself in its pauseApp( ) method.

Moving the MIDlet from the Paused state to the Active state is a little easier:

ExampleMIDlet.this.resumeRequest(  );
isPaused = false;

The resumeRequest( ) call notifies the scheduler that the MIDlet would like to be made Active. In response to this, the MIDlet's startApp( ) method will be called at some future time to allow it to reallocate resources that were released when it was paused. If another MIDlet is currently in the foreground, the resumed MIDlet has to wait until the foreground MIDlet is paused or terminates before it becomes eligible to become the foreground MIDlet and recover use of the screen and input devices.

Finally, after two suspend/resume cycles are completed, the timer code destroys the MIDlet by calling notifyDestroyed( ):

// Terminate the MIDlet
 try {
    ExampleMIDlet.this.destroyApp(true);
} catch (MIDletStateChangeException ex) {
    // Ignore pleas for mercy!
}
ExampleMIDlet.this.notifyDestroyed(  );

As is the case with notifyPaused( ), the MIDlet's destroyApp( ) method is not invoked as a result of a call to notifyDestroyed( ), so the timer code explicitly invokes it in order to allow the MIDlet to release its resources. Because this is an involuntary termination, the destroyApp( ) method is called with its unconditional argument set to true. However, care is taken to catch a MIDletStateChangeException in case the destroyApp( ) method ignores this argument. It is important to note that notifyDestroyed( ) does not actually terminate the MIDlet or any of its threads; it simply arranges for the MIDlet never to be scheduled as the foreground MIDlet and removes it from the list of active MIDlets. It is the MIDlet's responsibility to stop its active threads and timers in its destroyApp( ) method. Failure to do this may cause the Java VM to continue running and consuming memory when it has no useful work to do, which is unacceptable given the resource constraints of the typical MIDP device.

In the final installment next time, learn delivery and installation of MIDlets.

Kim Topley has more than 25 years experience as a software developer and was one of the first people in the world to obtain the Sun Certified Java Developer qualification.


View catalog information for J2ME in a Nutshell

Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.