ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

The Mobile Information Device Profile and MIDlets, Part 4
Pages: 1, 2, 3, 4

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:



  • When started is false, startApp( ) creates and starts the MIDlet timer and the MIDlet's background thread.

  • When started is true, startApp( ) does not need to concern itself with the timer, because it is not canceled by the pauseApp( ) method. It does, however, create a new background thread, because the original thread will be stopped when the MIDlet is paused.

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.

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.

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:

  • A background thread printing a message every second
  • A timer responsible for pausing and resuming the MIDlet periodically

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:

  • In fixed-rate scheduling, the next execution of task A is scheduled relative to the time its previous execution should have started. In this case, task A should have begun execution at T+1 second. Under fixed rate scheduling, it will next run at T+2 seconds, as it would have had task B not delayed it.

  • With fixed-delay scheduling, the next execution of task A is timed relative to the time its previous execution actually took place. Since task A last ran at T+1100ms, it will next run at T+2100ms, then at T+3100ms, and so on.

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.