The MIDP and MIDlets, Part 3

All MIDlets are derived from the abstract base class javax.microedition.midlet.MIDlet, which contains methods that the MIDP platform calls to control the MIDlet's lifecycle, as well as methods that the MIDlet itself can use to request a change in its state. A MIDlet must have a public default constructor (that is, a constructor that requires no arguments), which may be one supplied by the developer if there is any initialization to perform or, when there are no explicit constructors, the empty default constructor inserted by the Java compiler. This is what a skeleton MIDlet class might look like:

At any given time, a MIDlet is in one of three states: Paused, Active, or Destroyed. A state diagram that shows how these states are related and the legal state transitions is shown in Figure 3-4.

Figure 3-4. The lifecycle of a MIDlet.

When a MIDlet is loaded, it is initially in the Paused state. The usual class and instance initialization is then performed--that is, static initializers are called the first time the MIDlet class is loaded, all instance initializers are invoked when the MIDlet instance is created, and its public, no-argument constructor is then invoked. If the MIDlet throws an exception during the execution of its constructor, the MIDlet is destroyed. If the MIDlet does not throw an exception, it is scheduled for execution at some later time. Its state is changed from Paused to Active, and its startApp( ) method is called. The MIDlet class declares this method as follows:

protected void startApp( ) throws MIDletStateChangeException;

That this method is abstract means that you must implement it in your MIDlet, and that it is protected implies that it will be called either from the MIDlet class itself or from another class in the javax.microedition.midlet package. In the reference implementation, the MIDlet lifecycle methods are called from a class in this package called Scheduler, but there is nothing in the MIDP specification that requires this class be used. Licensees may provide their own scheduler implementations, provided that it supports the MIDlet lifecycle as described in this section. It is very common for MIDlet developers to redefine the startApp( ) method as public, which is certainly a safe option, but this should not be necessary because vendor implementations must continue to work even if these methods are declared as protected.

The startApp( ) method may complete normally, in which case the MIDlet is allowed to run, or it may inform the MIDP platform that the MIDlet does not want to run at this point. There are several ways to achieve the latter:

If the startApp( ) method detects an error condition that stops it from completing, but which might not exist later (i.e., a transient error condition), it should throw a MIDletStateChangeException. This moves the MIDlet back to the Paused state, so that another attempt to start it can be made later.

If the startApp( ) method detects an error condition from which recovery is likely never to be possible (a nontransient error condition), it should call its notifyDestroyed( ) method, which is described a little later.

Finally, the MIDlet may throw an exception other than MIDletStateChangeException, either deliberately or because a method that it invokes throws the exception, and the startApp( ) method does not catch it. In this case, it is assumed that a fatal error has occurred, and the MIDlet is destroyed by calling its destroyApp( ) method (described later).

If the MIDlet does none of these things, it is in the Active state and will be allowed to run until it is either paused or destroyed. A MIDlet returns after completing its startApp( ) method, and it does not have a method that contains the main logic to which control could be passed, so where is the MIDlet's code placed? Usually, a MIDlet has a user interface and executes code as a result of events generated by key presses or pointer movements. MIDlets can also start separate background threads to run code that does not depend on the user interface, or they can use a timer to schedule work periodically, as will be shown later. If you take these approaches, it is important to manage the background threads and/or timers appropriately when the MIDlet itself is paused or destroyed.

At any time, the MIDP platform can put a MIDlet into the Paused state. On a cell phone, for example, this might happen when the host software detects an incoming call and needs to release the phone's display so the user can answer the call. When a MIDlet is paused, its pauseApp( ) method is called:

protected abstract void pauseApp( );

As with startApp( ), a MIDlet is required to provide an implementation for this method. The appropriate response to this state change depends on the MIDlet itself, but, in general, it should release any resources it is holding and save the current state so it can restore itself when it is reactivated later.

The main consequence of being moved to the Paused state is that the MIDlet no longer has access to the screen; any threads that it created are not automatically terminated, and timers remain active. A MIDlet may choose to terminate any open network connections or background threads and cancel active timers when told to pause, but it is not obliged to do so.

If the host platform decides to resume a paused MIDlet, because the incoming call has terminated, for example, the MIDlet's startApp( ) method is invoked again to notify the MIDlet that it has access to the screen. As a consequence, a MIDlet's startApp( ) method should be written carefully to distinguish, if necessary, between the first time that it is called, which signifies that the MIDlet is being started for the first time, and subsequent calls notifying resumption from the Paused state, to prevent resources from being allocated multiple times. Of course, if a MIDlet reacts to being moved to the Paused state by releasing all of its resources, it would probably be appropriate to execute the same initialization code in startApp( ) to reallocate the resources upon resumption. However, a properly written MIDlet would still take special action in the startApp( ) method to restore the user interface and its internal state to the way it was before it was paused, rather than show the initial screen again.

The fact that the startApp( ) method can be invoked more than once in the lifetime of a MIDlet raises the question of whether initialization should be performed here or in the MIDlet's constructor. The developer is free to choose the more convenient location to allocate resources and prepare the MIDlet's state. In general, resources that will be released in pauseApp( ) should be allocated in startApp( ). Other resources can be allocated in either startApp( ) or the constructor, with care being taken to ensure that allocations performed in startApp( ) are not repeated following resumption from the Paused state.

An important difference between the startApp( ) method and the constructor is that, according to the MIDP specification, the MIDlet is guaranteed to be able to access the Display object that corresponds to the screen (see Chapter 4) only from the point at which startApp( ) is invoked for the first time. Under a strict interpretation of the specification, therefore, initialization that involves a Display object cannot be performed in the constructor. Of course, actual MIDP implementations may not enforce this apparent restriction, but portability may be compromised if the MIDlet accesses the Display object in its constructor.

A MIDlet may refuse a request to be resumed from the Paused state by throwing a MIDletStateChangeException when its startApp( ) method is called, as described earlier.

When the host platform needs to terminate a MIDlet, it calls the MIDlet's destroyApp( ) method:

In the destroyApp( ) method, the MIDlet should release all the resources that it has allocated, terminate any background threads, and stop any active timers. When the MIDlet is terminated this way, the unconditional argument has the value true, to indicate that the MIDlet cannot prevent the process from continuing. Under some circumstances, however, it is useful to give the MIDlet the option to not terminate, perhaps because it has data that it needs to save. In this case, the destroyApp( ) method can be invoked with the argument false, in which case the MIDlet can indicate that it wants to continue by throwing a MIDletStateChangeException. The following code illustrates how this technique can be used to implement the conditional shutdown of a MIDlet:

This code might be used to respond to an Exit button in the MIDlet's user interface. It begins by directly invoking the MIDlet's own destroyApp( ) method so that resources are released. If the MIDlet is not in an appropriate state to terminate, and destroyApp( ) is called with argument false, the MIDlet should throw a MIDletStateChangeException. The calling code should catch this exception and do nothing, as shown here. On the other hand, if the MIDlet is prepared to be terminate, it should complete the destroyApp( ) method normally, in which case the calling code uses the MIDlet notifyDestroyed( ) method to tell the MIDP platform that the MIDlet wants to be terminated.

This example also illustrates the use of the notifyDestroyed( ) method, which is used by a MIDlet to voluntarily terminate. It is important to understand the relationship between the destroyApp( ) and notifyDestroyed( ) methods and when they are used:

When the MIDlet is being destroyed by the platform, most likely because the user has requested it, the MIDlet's destroyApp( ) method is called with the argument true, and the MIDlet is destroyed when this method completes. It is not necessary in this case for the MIDlet to invoke its notifyDestroyed( ) method.

When the MIDlet itself wants to terminate, typically because it has no more useful work to do or the user has pressed an Exit button, it can do so by invoking its notifyDestroyed( ) method, which tells the platform that it should be destroyed. In this case, the platform does not call the MIDlet's destroyApp( ) method; it assumes that the MIDlet is already prepared to be terminated. Most MIDlets invoke their own destroyApp( ) method to perform the usual tidy up before calling notifyDestroyed( ), as shown earlier.

Note that calling notifyDestroyed( ) is the only way for a MIDlet to terminate voluntarily. MIDlets cannot terminate by calling the System or Runtimeexit( ) methods, because these throw a SecurityException.

There are two other methods that a MIDlet may invoke to influence its own lifecycle:

public final void notifyPaused( );
public final void resumeRequest( );

The notifyPaused( ) method informs the platform that the MIDlet wishes to be moved to the Paused state; this has the same effect as if the platform had invoked the MIDlet's pauseApp( ) method. When the MIDlet calls notifyPaused( ), the platform does not invoke its pauseApp( ) method, in the same way that it does not call destroyApp( ) in response to notifyDestroyed( ), because it assumes that the MIDlet has prepared itself to be paused. A MIDlet often, therefore, precedes an invocation of notifyPaused( ) with a call to pauseApp( ) so that the appropriate steps are taken before the MIDlet is suspended.

The resumeRequest( ) method is the reverse of notifyPaused( ); it tells the platform that a MIDlet in the Paused state wishes to return to the Active state. At some future time, the platform may resume the MIDlet by calling its startApp( ) method. The resumeRequest( ) method typically is called by a background thread or from a timer that the MIDlet left active while it was paused, an example of which is shown in the next section.

Next time, you learn how to develop 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.