Technical Article

Managing the MIDlet Life-Cycle with a Finite State Machine

By C. Enrique Ortiz, August 2004

A MIDlet class may manage its own logic and life-cycle, but often a better choice is for it to delegate these responsibilities to a finite state machine (FSM). An FSM provides a simple and effective means to control your MIDlet's life-cycle and overall application behavior. This article shows you how.

Background

Each MIDlet's life-cycle is managed by the application management system (AMS), the software in the device that manages the download, installation, execution, and removal of applications and other resources on the device.

Once the user selects the MIDlet for execution, the AMS instantiates it, which starts its life-cycle in the Paused state. The AMS then moves the MIDlet into the Active state and notifies it of this transition by invoking the MIDlet's startApp() method. Similarly, its pauseApp() and destroyApp() methods tell the MIDlet when it is paused or destroyed, respectively.

Beginner developers commonly pay too little attention to the MIDlet life-cycle, and as a consequence don't handle state transitions correctly. For example, the MIDlet may be suspended - paused by the AMS - when a call is received, or the user may unintentionally terminate execution of the MIDlet. A specific product requirement may oblige the application to handle such cases, and even to return to the state it was in before the interruption, as if it had never exited.

Implementing a finite state machine can simplify a MIDlet's handling of its life-cycle. Let's first look at some basic FSM definitions, then some code examples.

Basic FSM Theory

A finite state machine is a computation model consisting of a set of finite or constant states that define behavior, a start state or initial state, an application-specific input alphabet used to determine the transition to make, a transition function that uses application rules or conditions to determine the next state to go to - the state transitions - and input events that trigger the state transitions.

The FSM defines functions such as acceptors that accept or reject an input, recognizers that categorize the input, and transducers that generate an output from a given input. Typically acceptors and recognizers are implemented by the same entity.

A finite state machine may be deterministic or non-deterministic. In a deterministic state machine there can be at most one transition for each input state - the state before the transition - while non-deterministic machines can have more than one. Because the MIDlet life-cycle has at most one transition for each input state, this article explores a deterministic state machine, which can be defined as follows:

A deterministic finite state machine is multi-tuple:

FSM = ( ∑, S, T, s, A), where:

  • ( ) is the alphabet of symbols, application-specific, that are used by the transition function.
  • ( S) is the set of states. A state is a condition of the state machine at a certain time.
  • (s∈S) is the start state.
  • (T:scx∑→sn) is the transition function. Based on the current state (sc), and an input symbol, it computes the next state (sn), and thus the transition to perform. The current state is defined as sc∈S. The next state is defined as sn∈S. The very first time, sn = s.
  • (A⊆S) is the set of accept states.

The state machine begins in the start state, and the transition function moves it from one state to the next based on two factors: the current state of the FSM and an input symbol to the transition function:

Figure 1: Finite State Machine Transition Function

Figure 1: Finite State Machine Transition Function

The FSM itself is typically represented by a state diagram like the one in Figure 2. A simple way to implement a transition function is with a switch statement, which implements the acceptor, recognizer, and transducer that decode an input state and cause the transition to a new state. A state itself is implemented by the contents of a variable or an object.

Figure 2: Representing States and State Transitions Using a State Diagram

Figure 2: Representing States and State Transitions Using a State Diagram

Seeing the MIDlet Life-cycle as a Finite State Machine

As Figure 3 illustrates, the MIDlet life-cycle follows an FSM pattern, where the set of states comprises Paused, Active, and Destroyed, and the start state is Paused (when the AMS instantiates the MIDlet); the input alphabet includes startApp, pauseApp, and destroyApp; and the transition function is the AMS itself. Here is the definition of the deterministic finite state machine for the MIDlet life-cycle from the AMS perspective:

Formal Definition of a MIDlet Life-Cycle Finite State Machine

M = (S, ∑, T, s, A) :

  • = { startApp, pauseApp, destroyApp}
  • S = { Paused, Active, Destroyed}
  • s = Paused. The very first time sn = s.
  • A = { Paused, Active, Destroyed}

The transition function T is defined as follows:

T:scx∑→sn:

  • T ( Paused, startApp) = Active
  • T ( Paused, destroyApp) = Destroyed
  • T ( Active, destroyApp) = Destroyed
  • T ( Active, pauseApp) = Paused

Figure 3: MIDlet Life-Cycle State Diagram

Figure 3: MIDlet Life-Cycle State Diagram

Using a Finite State Machine to Manage a MIDlet

The sample code you'll soon see defines a somewhat more elaborate FSM, that lets us handle the MIDlet life-cycle, and some application-specific transitions as well, including returning to an earlier application state after an application has exited.

In our example, we'll keep the state machine simple, mainly to make it smaller. To that end we'll implement the FSM as a single method that is little more than a single switch statement, rather than a more complicated construct, such as a state-transition matrix. This method will serve as the acceptor, recognizer, and transducer. We will make the transition function's alphabet ( ) symbol the state to transition to ( sn). The transition function will be recursive, and its arguments will be the current state of the FSM ( sc), and the next state ( sn), as determined by the application or an earlier invocation of the FSM:

M = (S, ∑, T, s, A):

  • S = { Started, Restarted, Paused, Destroyed, Ready, State1, State2}
  • s = Started. The very first time sn = s.
  • The recursive transition function T:scx∑→sn is defined as follows:
    1. T (Started, " previous-state") = { Ready, State1, State2}, where " previous-state" is the current state of the application ( sc), and State1 and State2 are application-specific states.
    2. T ( Paused, Restarted) = Restarted
    3. T (" current-state", Destroyed) = Destroyed
    4. T ("current-state", Paused) = Paused
    5. T ( Ready, State1) = State1
    6. T ( Ready, State2) = State2
    7. T (Restarted, " current-state") = { Ready, State1, State2}, where " current-state" is the current state of the application ( sc), and State1 and State2 are application-specific states.

    A transition to a given state performs appropriate application business logic such as loading screens, validating data, or accessing a server over the network.

To enable the application to restart after an exit, and to get back to its earlier state regardless of how it exited, the FSM implements state persistence using the Record Management System (RMS).

Not described in this article but of interest is the State pattern described in the book Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Applications of this pattern alter the internal behavior of an object in response to state changes - possibly using a finite state machine like the one described in this article.

The MIDlet Source Code

Our MIDlet delegates life-cycle management and other processing to the FSM, making the MIDlet class itself very small:

Listing 1. Delegating to the Finite State Machine



                            
package j2medeveloper;

import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class FsmMIDlet extends MIDlet {

    private Display display;
    private FSM fsm;

    /************************************************************/
    /*  Start of MIDlet life-cycle methods                     */
    /************************************************************/

    /**
     * Entry point for MIDlet start or restart
     * @throws javax.microedition.midlet.MIDletStateChangeException
     */
    public void startApp() throws MIDletStateChangeException {

        // First time indicates started state, otherwise restarted.
        if (display == null) {
            display = Display.getDisplay(this);
            fsm = new FSM(this, display);
            fsm.transition(FSM.FSM_STATE_UNDEFINED,
                FSM.FSM_STATE_STARTED);
        } else {
            /* Re-start app - from paused state. */
            fsm.transition(FSM.FSM_STATE_RESTARTED,
                FSM.FSM_STATE_RESTARTED);
        }
    }

    /**
     * Paused state. Release resources (connection, threads, etc).
     */
    public void pauseApp() {
        fsm.transition(FSM.currentState, FSM.FSM_STATE_PAUSED);
    }

    /**
     * Destroyed state. Release resources (connection, threads, etc).
     * @param uc If true when this method is called, the MIDlet must
     * clean up and release all resources. If false the MIDlet may  
     * throw MIDletStateChangeException to indicate it does not want to  
     * be destroyed at this time.
     * @throws javax.microedition.midlet.MIDletStateChangeException
     * to indicate it does not want to be destroyed at this time.
     */
    public void destroyApp(boolean uc)
        throws MIDletStateChangeException {
        fsm.transition(FSM.currentState, FSM.FSM_STATE_DESTROYED);
    }
    /************************************************************/
    /*  End of MIDlet life-cycle methods                        */
    /************************************************************/
}


A side benefit of such a small MIDlet is that the the application's size after obfuscation will be smaller than you might expect. Because the MIDlet class is the entry point, it's not typically obfuscated. Shifting much of the work to classes that can be obfuscated will reduce overall size.

The FSM Transition Function

In Listing 2 the class FSM implements our deterministic finite state machine, the transition function, and the overall application logic. Please note that Listing 2 shows only the basic structure of the transition function; it omits important details such as dispatching long operations to their own threads.

The transition() method implements the transition function, handling the states Started, Ready, Restarted, Paused, and Destroyed, and two more: State1 and State2, which you can replace with states specific to your application.

Listing 2. The Finite State Machine, Transition Function, and Application Logic



                            
package j2medeveloper;

import java.io.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStore;

public class FSM implements CommandListener {

    MIDlet midlet;
    Display display;
    private String FSM_RSTORE = "FSM_RSTORE";

// Finite states     static public final int FSM_STATE_UNDEFINED  = -1;    
 static public final int FSM_STATE_STARTED       = 1;    
 static public final int FSM_STATE_PAUSED        = 2;     
 static public final int FSM_STATE_DESTROYED     = 3;    
  static public final int FSM_STATE_RESTARTED     = 4;     
  static public final int FSM_STATE_READY         = 5;     
  static public final int FSM_STATE1              = 6;    
   static public final int FSM_STATE2              = 7;    
   protected static int mPreviousState = FSM_STATE_UNDEFINED;     
   protected static int mCurrentState = FSM_STATE_UNDEFINED; 
    // Application Screens
    private javax.microedition.lcdui.List readyScreen;
    private javax.microedition.lcdui.List state1Screen;
    private javax.microedition.lcdui.List state2Screen;

    //  Screen commands
    public static Command CMD_READY_SCREEN_EXIT = null;
    public static Command CMD_READY_SCREEN_GOSTATE1 = null;
    public static Command CMD_READY_SCREEN_GOSTATE2 = null;
    public static Command CMD_STATE1_SCREEN_OK = null;
    public static Command CMD_STATE2_SCREEN_OK = null;

    /**
     * Constructor
     */
    public FSM(MIDlet midlet, Display display){
        this.midlet = midlet;
        this.display = display;
    }
   /************************************************************/    
    /*  Start of FSM transition function                       */
  /************************************************************/

    /**
     * This is the finite state machine (FSM) for the MIDlet.
     * This machine supports returning to a previous state from
     * the paused and destroyed states.
     * @param currentState current FSM state.
     * @param transitionState transition FSM state.
     */
    public void transition(int currentState, int transitionState) {
        System.out.println("FSM.transition");
        System.out.println(" currentState is    " + currentState);
        System.out.println(" transitionState is " + transitionState);

        try {
            // The next switch statement implements the acceptor, 
            // recognizer, and transducer, for decoding an input state 
            // and transitioning to a new state.
            switch (transitionState) {
                    case FSM_STATE_STARTED:      ;
                    System.out.println("FSM_STATE_START");
                    // The started state is a fresh state, and requires
                    // proper application initialization such as
                    // allocating any required objects.

                    // Here initialize any required objects
                    // ...

                    // Initialize screens.
                    initializeScreens();
                    //  Load current state from record store.
                        loadState();                      
                    //  Transition to previous state.                     
                    if (mCurrentState == FSM_STATE_UNDEFINED)
                    transition(transitionState, FSM_STATE_READY);
                    else                         
                    transition(mPreviousState, mCurrentState);

                    return; // need not remember this state.
                    case FSM_STATE_READY:
                    System.out.println("FSM_STATE_READY");
                    // Here do application-specific ready processing.
                    // In our example we just display the initial
                    // application screen.
                    display.setCurrent(readyScreen);
                    break;
                    case FSM_STATE_RESTARTED:
                    System.out.println("FSM_STATE_RESTART");
                    // This state is invoked when from paused state.
                    // All prev. objects should still be around, so
                    // there is no need to reinitialize. Just load the
                    // state, and return to previous state.
                                       //  Load current state from record store.
                                       loadState();                      
                                       //  Transition to previous state.                     
                                       transition(mPreviousState, mCurrentState);

                    return; // no need to remember; so to return
                            // go to real previous state.
                                   case FSM_STATE_PAUSED:
                    System.out.println("FSM_STATE_PAUSED");
                    // Typically a transition to the paused state
                    // indicates the application is suspended, and
                    // resources remain in memory as is, so returning
                    // from paused only requires going back to previous
                    // state.
                    return;
                                   case FSM_STATE_DESTROYED:
                    System.out.println("FSM_STATE_DESTROYED");
                    // Here do application cleanup such as closing
                    // connections, exiting threads, and so on.
                    // ...

                    return; // No need to remember destroyed state.
                                   case FSM_STATE1:
                    System.out.println("FSM_STATE1");
                    // Here do application-specific processing.
                    // In this example we just display a screen.
                    display.setCurrent(state1Screen);
                    break;
                                   case FSM_STATE2:
                    System.out.println("FSM_STATE2");
                    // Here do application-specific processing.
                    // In this example we just display a screen.
                    display.setCurrent(state2Screen);
                    break;

                default:
                    return; // Unrecognized symbols; reject.

            } // switch (transitionState)
                               
            // Complete state transition...             
            mPreviousState = currentState;            
             mCurrentState  = transitionState;              
                               
            // Preserve application state  persistState();

        }  catch (Exception e) {
            System.out.println("FSM Exception: " + e);
        }
    }
/************************************************************/     
/*  End of FSM Transition Function                         */
 /************************************************************/
    /**
     * Command Listener.
     * @param c is the LCDUI Command.
     * @param d is the source Displayable.
     */
    public void commandAction(final Command c, Displayable d) {

        try {
            ///////////////////////
            // Main Screen Commands
            ///////////////////////
            if (c == CMD_READY_SCREEN_EXIT) {
                transition(mCurrentState, FSM_STATE_DESTROYED);
            } else if (c == CMD_READY_SCREEN_GOSTATE1) {
                transition(mCurrentState, FSM_STATE1);
            } else if (c == CMD_READY_SCREEN_GOSTATE2) {
                transition(mCurrentState, FSM_STATE2);
            } else if (c == CMD_STATE1_SCREEN_OK) {
                transition(mCurrentState, FSM_STATE_READY);
            } else if (c == CMD_STATE2_SCREEN_OK) {
                transition(mCurrentState, FSM_STATE_READY);
            }
        } catch (Exception e) {
        }
    }

    private void initializeScreens() {

        CMD_READY_SCREEN_EXIT = new Command("Exit", Command.SCREEN, 1);
        CMD_READY_SCREEN_GOSTATE1 = new Command("Go to State 1",
            Command.SCREEN, 1);
        CMD_READY_SCREEN_GOSTATE2 = new Command("Go to State 2",
            Command.SCREEN, 1);
        CMD_STATE1_SCREEN_OK = new Command("Back", Command.SCREEN, 1);
        CMD_STATE2_SCREEN_OK = new Command("Back", Command.SCREEN, 1);

        readyScreen = new javax.microedition.lcdui.List("Ready Screen",
            List.IMPLICIT);
        readyScreen.append("Ready Screen Item 1", null);
        readyScreen.append("Ready Screen Item 2", null);
        readyScreen.append("Ready Screen Item n", null);
        readyScreen.addCommand(CMD_READY_SCREEN_EXIT);
        readyScreen.addCommand(CMD_READY_SCREEN_GOSTATE1);
        readyScreen.addCommand(CMD_READY_SCREEN_GOSTATE2);
        readyScreen.setCommandListener(this);

        state1Screen = new javax.microedition.lcdui.List(
            "State1 Screen", List.IMPLICIT);
        state1Screen.append("State 1 Screen Item 1", null);
        state1Screen.append("State 1 Screen Item 2", null);
        state1Screen.append("State 1 Screen Item n", null);
        state1Screen.addCommand(CMD_STATE1_SCREEN_OK);
        state1Screen.setCommandListener(this);

        state2Screen = new javax.microedition.lcdui.List(
            "State2 Screen", List.IMPLICIT);
        state2Screen.append("State 2 Screen Item 1", null);
        state2Screen.append("State 2 Screen Item 2", null);
        state2Screen.append("State 2 Screen Item n", null);
        state2Screen.addCommand(CMD_STATE2_SCREEN_OK);
        state2Screen.setCommandListener(this);

    }

    private void persistState() {
        // See Listing 3
        // ...
    }

    private void loadState() {
        // See Listing 4
        // ...
    }

} /* End of FSM.java */
                

Preserving the MIDlet's State

To enable the application to return to its prior state no matter how it exited, we must make the finite state persistent. We use RMS to store the state every time the transition function is invoked, in these lines after the switch in the transition() method:



                            
...
// Complete state transition...
mPreviousState = currentState;
mCurrentState  = transitionState;

// Preserve application state
persistState();
...


We use two attributes, mPreviousState and mCurrentState, to keep track of status. Another way would be to use a Stack data structure, which would let us maintain state history and perform state transition validation. The Stack would hold the state history, with the current state at the top.

Listing 3. Preserving the Finite State

The persistState() method does the actual job of saving the state in an RMS record store:



                            
private void persistState() {
    RecordStore pStateStore = null;
    DataOutputStream dos;
    ByteArrayOutputStream baos;
    try {
        RecordStore.deleteRecordStore(FSM_RSTORE);
    } catch (RecordStoreException e) {
    }
    try {
        pStateStore = RecordStore.openRecordStore(FSM_RSTORE, true);
        baos = new ByteArrayOutputStream();
        dos = new DataOutputStream(baos);
        dos.writeInt(mPreviousState);
        dos.writeInt(mCurrentState);
        pStateStore.addRecord(baos.toByteArray(), 0, baos.size());
        System.out.println("App. State Saved...");
    } catch (IOException ioe) {
        //... Handle the exception
        System.out.println("IOException " + ioe);
    } catch (RecordStoreException rse) {
        //... Handle the exception
        System.out.println("RecordStoreException " + rse);
    } catch (Exception e) {
        //... Handle the exception
        System.out.println("Exception " + e);
    } finally {
        try {
            if (pStateStore != null) {
                pStateStore.closeRecordStore();
            }
        } catch (Exception ignore) {
            //  Ignore
            System.out.println("Exception is " + ignore);
        }
    }
}


The method loadState(), invoked in the Started and Restarted states, loads state information saved earlier in the record store:

Listing 4. Retrieving the Stored Finite State



                            
private void loadState() {
    RecordStore pStateStore = null;
    DataInputStream dis;
    ByteArrayInputStream bais;
    try {
        pStateStore = RecordStore.openRecordStore(FSM_RSTORE, false);
        /* Deserialize data */
        bais = new ByteArrayInputStream(pStateStore.getRecord(1));
        dis = new DataInputStream(bais);
        mPreviousState = dis.readInt();
        mCurrentState  = dis.readInt();
        System.out.println("App. State Loaded...");
    } catch (IOException ioe) {
        //... Handle the exception
        System.out.println("IOException " + ioe);
    } catch (RecordStoreException rse) {
        //... Handle the exception
        System.out.println("RecordStoreException " + rse);
    } catch (Exception e) {
        //... Handle the exception
        System.out.println("Exception " + e);
    } finally {
        try {
            if (pStateStore != null) {
                pStateStore.closeRecordStore();
            }
        } catch (Exception ignore) {
            //  Ignore
            System.out.println("Exception is " + ignore);
        }
    }
}


Summary

This article has shown how to use a simple deterministic finite state machine to manage a MIDlet's application life-cycle and overall application logic, including returning to a previous state regardless of how the application has exited. The FSM enables the MIDlet to handle the paused state properly, such as when incoming calls are received, or the destroyed state, such as when the user has exited the application inadvertently.

References

Acknowledgments

Thanks to Ariel Levin for his contributions to this article.

About the author

C. Enrique Ortiz has more than 14 years of software engineering and product development experience, and has been involved with mobile, wireless, and embedded technologies since 1998. He specializes in end-to-end mobile and location-based enterprise applications, and has led teams at Aligo Inc and AGEA Corporation that have developed and launched mobile products. He has held software engineering positions at Pervasive Software, Intelligent Reasoning Systems, and IBM. Enrique is a co-designer of Sun Microsystems' Mobile Java Developer Certification Exam, he co-authored one of the first books on J2ME, the Mobile Information Device Profile for J2ME, and is author of other publications. He is an active participant in the mobile Java technology community and in various J2ME expert groups. Enrique holds a B.S. in Computer Science from the University of Puerto Rico.