HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

In September 2002, Sun released the J2ME Personal Profile 1.0. Unlike the MIDP, which is the core technology for Java-enabled wireless phones based on Connected Limited Device Configuration (CLDC), Personal Profile is based on the Connected Device Configuration (CDC). The CDC provides a virtual machine that includes a full Java 2 Virtual Machine feature set. Compared to CLDC, it assumes more memory and higher availability on network connections.

The Personal Profile contains a full set of AWT APIs that support a graphical user interface (GUI), including support for applets and Xlets, and provides a complete application environment for the high-end PDA market. It expands the J2ME territory to include devices that require a full GUI and a high degree of compatibility with the PersonalJava and J2SE application environment.

The Xlet application model, which is inherited from the Personal Basis Profile, is one of its most important features. What is an Xlet? Like an applet in J2SE, it's an application that must be run in an application manager. In other words, it does not have a main() method and cannot be run in standalone mode. However, it implements an interface that the application manager can use to manage its state.

Xlets potentially may play a more important role in J2ME compared to an applet in J2SE. Downloading third-party Xlets provides a way for a PDA to dynamically expand its functionality. One Xlet can even provide services to other Xlets through Inter-Xlet Communication, which makes it easy to develop client/server style applications that consist of multiple Xlets with fine modularity.

In this article, I'll talk about the Xlet life cycle and how to write an Xlet.

Xlet Life Cycle
An Xlet must implement four methods defined in the javax.microedition.xlet.Xlet interface:

public interface Xlet {

public void initXlet(XletContext ctx)
throws XletStateChangeException;
public void startXlet()
throws XletStateChangeException;

public void pauseXlet();

public void destroyXlet(boolean
unconditional) throws
XletStateChangeException;
}

Xlets, like applets, have a life cycle. The Xlet application manager uses these four methods to interact with an Xlet to manage its state. It's impractical to talk about Xlet programming without mentioning the Xlet life cycle. So before going into the programming details, let's take some time to understand an Xlet's life cycle.

An Xlet has four states:

  • Loaded: The Xlet is loaded from local storage or network and its no argument constructor is called. It can enter the paused state if the Xlet's initXlet() method is called.
  • Paused: The Xlet is initialized and ready to be active. It's like the ready state of a process: ready to run in the CPU at any time. It can enter the active state after the Xlet's startXlet() is called.
  • Active: The Xlet is running normally. It can enter the destroyed state if its destroyXlet() method is called. It may also return to the paused state if its pauseXlet() method is called.
  • Destroyed: This is the terminal state. Once it's entered, it cannot return to other states. All its resources are subject to be claimed.

    In addition, an Xlet may enter the destroyed state from any other state. The possible state changes are demonstrated in Figure 1.

    Figure 1

    Implementing the Xlet Interface
    Now that we have a better idea of the Xlet life cycle, let's take a closer look at the Xlet interface. The methods defined in this interface are also called life-cycle methods. Keep in mind that user applications, including the Xlet, should not directly invoke life-cycle methods. (Even if they do, state change will not happen.) It is the Xlet application manager that invokes these methods to notify an Xlet to change its state. In a sense those methods are more like event handlers.

    initXlet(XletContext ctx)
    This method initializes the Xlet after it's loaded and instantiated. Note that it takes the XletContext as a parameter. This is a very important parameter as it's the only way for the Xlet to get hold of its running context. In particular, the XletContext class provides methods to retrieve the parameters that are passed to the Xlet when it's loaded and a container in which the Xlet can put AWT components. More important, through XletContext an Xlet can initiate state change itself or communicate with other Xlets.

    In Listing 1, an Xlet prints all its arguments in a TextField in its initXlet() method. Note that XletContext.getContainer() may throw UnavailableContainerException. This happens if the implementation has only one displayable container and multiple Xlets are trying to access it simultaneously. In our example, upon catching such an exception the Xlet throws an XletStateChangeException to notify the Xlet application manager that it cannot initialize and needs to be destroyed.

    startXlet() and pauseXlet()
    The startXlet() method notifies the Xlet to start providing service and moves it to the active state. The pauseXlet() method does the opposite: it asks the Xlet to stop providing service and moves it back to the paused state.

    The difference between initXlet() and startXlet() is that the former can be called only once, while the latter can be called many times in its life cycle whenever the Xlet application manager wants the Xlet to enter or resume the active state. startXlet() is a reentry method. All initialization work or things that cannot be done twice should be put in initXlet() instead of startXlet(), otherwise you risk having problems with the Xlet.

    Normally, you would expect that the business logic is implemented in the startXlet() method. However, the Personal Profile specification states that all the methods defined in the Xlet interface are meant to signal state changes and the Xlet application manager expects those methods to return quickly. If a method fails to return within a certain amount of time that is implementation dependent, the Xlet manager may destroy the Xlet thinking that something is wrong with it. It's important that you don't put a lengthy implementation of business logic inside any of the life-cycle methods. If the Xlet provides some service that could go on and on, you may start a thread that provides the actual service and communicate with the thread, for example, by setting certain flags in the life-cycle methods.

    destroyXlet(boolean unconditional)
    This method signals the Xlet to terminate and enter the destroyed state. The Xlet must release all resources. The parameter "unconditional" is interesting; it's set by the Xlet application manager to signal whether it wants the Xlet to be destroyed unconditionally. If unconditional is set to false, the Xlet may throw a StateChangeException to indicate that it does not want to be destroyed. However, it's still up to the Xlet application manager to decide whether to accept or deny such a request. In other words, even though throwing such an exception is a valid response, the fate of the Xlet is still at the mercy of the Xlet application manager. If the request is accepted, the Xlet application manager will give the Xlet some time before it calls the destroy() method again, most likely with unconditional set to true. If unconditional is set to true, the Xlet application manager will ignore any XletStateChangeException and go ahead and destroy the Xlet once destroyXlet() returns.

    Exceptions in Life-Cycle Methods
    There are only two kinds of exceptions that can be thrown from life-cycle methods: XletStateChangeException and uncaught RuntimeException or error.

    Any uncaught RuntimeException or error thrown from the life-cycle methods will immediately cause the Xlet application manager to invoke the method destroyXlet(true) of the corresponding Xlet and put it in the destroyed state. That means normally RuntimeException or error should be caught by the Xlet to prevent it from being destroyed.

    On the other hand, the Xlet may intentionally throw XletStateChangeException in startXlet()to indicate that it's not ready for the state change yet.

    Requesting State Change Using XletContext
    As mentioned earlier, the life-cycle methods in the Xlet interface are used by the Xlet application manager to communicate to the Xlet that it wants the Xlet to change state. But what if the Xlet wants to change its own state? For example, what if an end user wants to terminate an Xlet and he doesn't want to wait for the Xlet application manager to do this?

    The XletContext provides three methods that can be used by the Xlet to initiate state change: notifyDestroyed(), notifyPaused(), and resumeRequest(). notifyDestroyed() notifies the Xlet application manager that the Xlet wants to terminate. Before calling notifyDestroyed(), it should release all resources, as it will in the event of a call to destroyXlet(). The Xlet unconditionally and immediately enters the destroyed state after this call.

    notifyPaused() puts an Xlet in the paused state. An Xlet may call notifyPaused() to make room for other Xlets to run. Normally, a subsequent call to resumeRequest() will make the Xlet return to active state. However, as the method's name suggests, it is a request and there's no guarantee that this request will be accepted. The intended use of this method is to notify the Xlet application of its intention to return to active state. It's up to the Xlet application manager to grant and schedule such a return. It may wait for a long time before it returns, or it may not return at all; the Xlet application manager may call destroyXlet() to terminate a paused Xlet due to a lack of certain resources.

    Putting It All Together: A Simple Digital Clock
    Let's demonstrate the concepts and principles we've discussed so far through a sample Xlet. This Xlet displays a simple digital clock that shows the hour, minute, and second of the current time. The clock can be paused or completely terminated. I designed the Xlet in a way that the clock can be controlled not only by its own buttons, but also by the Xlet application manager via the Xlet's life-cycle methods. If you run the Xlet in Sun's XletRunner, which is the Xlet application manager for the Personal Profile reference implementation, you'll be able to control the Xlet through XletRunner's interactive menus that were originally designed to manage the Xlet's life cycle. For example, if you want to pause the clock, instead of clicking the clock's pause button, you may go to the XletRunner's "ClockXlet" menu and choose "pause". Listing 2 provides the complete code.

    Let's first look at the MyClock class. Its main function is to display the current time in a TextField. This class extends Thread and its main body is implemented in Thread's run() method. It also has two flags to indicate whether pause or stop is requested. If neither is requested, the clock runs normally and displays the current time once every second. If pause is requested, the clock puts itself in an indefinite wait until the pause flag is unset from outside, which awakens the thread. If stop is requested, the thread simply terminates.

    MyClock is controlled by ClockXlet. The initXlet() method creates a TextField that's used by MyClock and buttons that are used to control the clock. It then starts MyClock as a thread. startXlet() and pauseXlet() methods are quite simple and return quickly. (Remember, we said that the life-cycle methods should return quickly.) They basically set or unset MyClock's pause flag, which in turn pauses or resumes the execution of the MyClock thread. The destroyXlet() method sets MyClock's stop flag, which in turn terminates the thread. This step releases resources, where the resource is the CPU.

    The actionPerformed() method translates the end user's actions to corresponding state change requests to the Xlet application manager. Clicking the clock's pause button will pause the clock. It will also send a request to the Xlet application manager to enter the paused state by calling XletContext.notifyPaused(). (Our ClockXlet is being nice here by not only pausing the thread, but also going to the paused state to give other Xlets a chance to run.) Clicking the clock's resume button will cause the Xlet to request a return to the active state. Once this request is accepted (hopefully!) by the Xlet application manager, it will call the Xlet's startXlet() method, where the clock will be started again. That is why we don't need to directly interact with MyClock in handling this event. Clicking the stop button will stop the clock and send a request to the Xlet application manager to terminate the Xlet.

    Use the following command to run the Xlet in Sun's reference implementation (you can download Sun's Personal Profile Runtime Environment from http://java.sun.com/products/ personalprofile/download.html):

    ./cvm com.sun.xlet.XletRunner \
    -name ClockXlet -path $MyClassPath

    where $MyClassPath is the path where ClockXlet class will be loaded.

    What you see on the screen will be similar to Figure 2. Use the buttons next to the TextField or "ClockXlet" menu in XletRunner to control the clock. Have fun!

    Figure 2

    Summary
    Xlets, like applets, can only run in an application manager. Xlets have a life cycle that includes four states: loaded, paused, active, and destroyed. To write an Xlet, you must implement the Xlet interface. The Xlet application manager changes the state of an Xlet through the life-cycle methods defined in the Xlet interface. Moreover, Xlets can initialize state change by using the XletContext API.

    Copyright 2003 Sun Microsystems, Inc. All rights reserved. Used by permission. Sun, Sun Microsystems, the Sun logo, Java, J2ME, PersonalJava, and J2SE are trademarks or registered trademarks of Sun Microsystems, Inc., in the United States and other countries.

    About The Author
    Xiaozhong Wang is a Sun Certified Java programmer and developer. Currently, he is a software engineer at Sun Microsystems. He developed the test suite for Xlet in the Personal Profile Technology Compatibility Kit 1.0, and his work has helped to refine the Xlet specification in Personal Profile 1.0. [email protected] [email protected]

    "Xlet: A Different Kind of Applet for J2ME"
    Vol. 8, Issue 7, p. 54

    	
    
    
    
    Listing 1 Xlet argument
    
    public void initXlet(XletContext ctx)
            throws XletStateChangeException {
        Container c;
        try {
            c = ctx.getContainer();
        } catch (UnavailableContainerException e) {
            throw new XletStateChangeException(e.getMessage());
        }
        TextField tx = new TextField(30);
        String[] args = (String[]) ctx.getXletProperty(XletContext.ARGS);
        String s = "";
        for (int i = 0; i < args.length; i++) {
            s = s + args[i] + " ";
        }
        tx.setText(s);
        c.setSize(200, 200);
        c.setVisible(true);
        c.add(tx);
    }
    
    
    
    Listing 2 A simple digital clock
    
    import javax.microedition.xlet.*;
    import java.util.*;
    import java.awt.*;
    import java.awt.event.*;
    
    public class ClockXlet implements Xlet, ActionListener {
        
        TextField display;
        MyClock clock;
        Button pauseButton = new Button("Pause");
        Button stopButton = new Button("Stop");
        Button resumeButton = new Button("Resume");
        XletContext context;
        public void initXlet(XletContext ctx)
                throws XletStateChangeException {
            Container c;
            context = ctx;
            try {
                c = ctx.getContainer();
            } catch (UnavailableContainerException e) {
                throw new XletStateChangeException(e.getMessage());
            }
            display = new TextField(30);
            clock = new MyClock(display);
            pauseButton.addActionListener(this);
            resumeButton.addActionListener(this);
            resumeButton.setEnabled(false);
            stopButton.addActionListener(this);
            c.setSize(200, 200);
            c.setVisible(true);
            c.add(display);
            c.add(pauseButton);
            c.add(resumeButton);
            c.add(stopButton);
            clock.start();
        }
    
        public void startXlet() {
            clock.setPaused(false);
            resumeButton.setEnabled(false);
            pauseButton.setEnabled(true);
        }
     
        public void pauseXlet() {
            clock.setPaused(true);
            resumeButton.setEnabled(true);
            pauseButton.setEnabled(false);
       }
    
        public void destroyXlet(boolean unconditional) {
            clock.setStopped(true);
        }
    
        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == stopButton) {
                clock.setStopped(true);
                context.notifyDestroyed();
            } else if (e.getSource() == pauseButton) {
                clock.setPaused(true);
                resumeButton.setEnabled(true);
                pauseButton.setEnabled(false);
                context.notifyPaused();
            } else if (e.getSource() == resumeButton) {
                context.resumeRequest();
            }
        }
    }
    
    class MyClock extends Thread {
    
        boolean paused, stopped;
        TextField display;
    
        public MyClock(TextField t) {
            display = t;
        }
    
        String getTime() {
            Calendar rightNow = Calendar.getInstance();
            String hour = String.valueOf(rightNow.get(Calendar.HOUR_OF_DAY));
            String min = String.valueOf(rightNow.get(Calendar.MINUTE));
            if (min.length() == 1) {
                min = "0" + min;
            }
            String sec = String.valueOf(rightNow.get(Calendar.SECOND));
            if (sec.length() == 1) {
                sec = "0" + sec;
            }
            return hour + ":" + min + ":" + sec;
        }
    
        public synchronized boolean isStopped() {
            return stopped;
        }
    
        public synchronized void setStopped(boolean value) {
            stopped = value;
            notifyAll();
        }
    
        public synchronized boolean isPaused() {
            return paused;
        }
    
        public synchronized void setPaused(boolean value) {
            paused = value;
            notifyAll();
        }
    
        public void run() {
            while (!isStopped()) {
                try {
                    if (!isPaused()) {
                        Thread.sleep(1);
                        display.setText(getTime());
                    } else {
                        synchronized (this) {
                            wait();
                        }
                    }
                } catch (InterruptedException e) {
                }
            }
        }
    }
    
     
    

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.
      E-mail: [email protected]

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.