Sun Java Solaris Communities My SDN Account Join SDN
 
Wireless Tech Tips

J2ME Tech Tips:July 25, 2001

 

Tech Tips archive

July 25, 2001

WELCOME to the Java Developer Connection (JDC) Java 2 Platform, Micro Edition (J2ME) Tech Tips, for April 16, 2001. This issue covers:

The J2ME Tech Tips are written by Eric Giguere (http://www.ericgiguere.com), an engineer at iAnywhere Solutions, inc. Eric is the author of the book "Java 2 Micro Edition: Professional Developer's Guide" and co-author of the upcoming "Mobile Information Device Profile for Java 2 Micro Edition," both books in John Wiley & Sons' Professional Developer's Guide series.

DRAWING FLICKER-FREE GRAPHICS WITH THE MIDP

The Mobile Information Device Profile (MIDP) defines low-level user interface classes for drawing directly on a device's display. The March 19, 2001 J2ME Tech Tip "Using the MIDP Low-Level User Interface API" covered how to use these classes to do basic drawing. Today's Tech Tip builds on that foundation and discusses how to use double buffering to draw flicker-free graphics.

The term "double buffering" refers to a common technique used in computer graphics. If you consider a device's display to be a memory buffer into which drawing primitives write (the drawing primitives are the basic drawing methods such as drawLine and drawArc), with double buffering you draw into a second, offscreen memory buffer and then copy the entire contents of the second buffer into the display buffer. Copying from one buffer to another is a very fast operation on most devices, so that the display changes almost instantaneously. By comparison, directly drawing to a display sometimes causes users to see a flicker, as individual parts of the display are updated. Double buffering avoids this flickering by combining multiple individual drawing operations (that is, those to the offscreen buffer) into a single copy operation to the display buffer.

It's easy to do double buffering in the context of the Mobile Information Device Profile. You can use the Image class (all classes mentioned in this tip are in the javax.microedition.lcdui package) to create an offscreen memory buffer. You use the Graphics class, the same class used to draw on the display, to draw to the offscreen buffer. You also use the Graphics class to copy the contents of the offscreen buffer onto the display. Double buffering is implemented with just a few adjustments to your painting routines.

The first thing you need to do is determine if double buffering is even necessary. On some implementations, double buffering is automatically supported by the system. In other words, when the system calls your Canvas object's paint method, the Graphics object passed to the method is that of an offscreen buffer managed by the system; the object is not from the display buffer. The system then takes care of copying the offscreen buffer to the display. Checking if double buffering is supported is easy -- all you do is call the isDoubleBuffered method, like this:

public class MyCanvas extends Canvas {

    private Image offscreen = null;
    private int   height;
    private int   width;
        
    public MyCanvas(){
        height = getHeight();
        width = getWidth();

        if( !isDoubleBuffered() ){
            offscreen = Image.createImage( width, height );
        }
                
        ..... // other initialization as appropriate
    }
        
    ...... // other code, including paint method
}

Notice how if isDoubleBuffered returns false, the constructor creates an offscreen buffer of the same width and height as the canvas. If the display is double buffered, isDoubleBuffered returns true and the offscreen buffer is not created.

The offscreen buffer is created by calling one of the Image.createImage methods. There are four such methods, each of which does the following:

  • Loads images from the MIDlet suite's JAR file
  • Makes a copy of an existing image
  • Creates an image from raw binary data
  • Creates a blank image of a specific height and width

The last of these createImage methods is the one used for double buffering. The other three createImage methods cannot be used for double buffering because they create images that are immutable, that is, images that cannot be changed. Only the last createImage method, the one that takes width and height parameters, can be used to create mutable images. Once you have a mutable image, you can call its getGraphicsmethod to obtain a Graphics object that you can use to draw into the image's buffer, just like drawing on the display.

Of course, the real work occurs in the paint method. A simple paint routine might look like this:

protected void paint( Graphics g ){
    g.setColor( 255, 255, 255 );
    g.fillRect( 0, 0, width, height );
}

Most paint routines are much more complicated, especially if animation is involved. To implement double buffering, add a few lines of code before and after the existing painting code, like this:

protected void paint( Graphics g ){
    Graphics saved = g;
        
    if( offscreen != null ){
        g = offscreen.getGraphics();
    }
    
    g.setColor( 255, 255, 255 );
    g.fillRect( 0, 0, width, height );
        
    if( g != saved ){
        saved.drawImage( offscreen, 0, 0,
                         Graphics.LEFT | Graphics.TOP );
    }
}

Basically all you're doing is obtaining the Graphics object for the offscreen buffer and using it to do the painting. At the end, the entire content of the offscreen buffer is copied to the display. Notice that this is done only if double buffering is not automatically supported. You can easily determine this by checking to see if an offscreen buffer has been allocated. If double buffering is automatically supported, you simply draw directly onto the display as usual.

Double buffering is not without its price. If you're only making small changes to the display, it might be slower to use double buffering. In addition, image copying isn't very fast on some systems; on those systems flicker can can happen even with double buffering. And there is a memory penalty to pay for double buffering: the offscreen memory buffer can consume a large amount of memory, memory that you might not be able to spare. Keep the number of offscreen buffers to a minimum. You could free the offscreen buffer whenever the canvas is hidden, for example, and allocate it again when the canvas is shown again. This is easy to do by overriding the canvas' hideNotify and showNotify methods.

PARSING XML IN CLDC-BASED PROFILES

XML, the Extensible Markup Language, is a portable, text-based way of representing structured data. More and more applications are using XML to exchange information, and at some point your CLDC-based applications (those that run on CLDC-based profiles such as the Mobile Information Device Profile) are going to need to process XML documents. Over time, even HTML pages will likely migrate to an XML-based format called XHTML. This means that fetching a page from a web server in order to extract data from it will require XML parsing technology.

Finding a Java-based XML parser is not hard, especially since there are several XML-based initiatives being defined as part of the Java Community Process. However, most of these parsers do not work in the limited environment provided by the CLDC. The parsers either use too much memory or use J2SE classes that simply are not available in the CLDC. There are two open source XML parsers that work well with the CLDC, however: kXML and NanoXML. Their strengths are different -- this tip discusses both of them and leaves it up to you to decide which best fits your application's requirements. You should also consider alternatives to XML, though, because XML documents are very verbose. If you have control over the server, it might make more sense to use your own binary format for exchanging data. This is fairly easy to do if the Java-based client is talking to a Java-based server application. In this case, use the DataInputStream and DataOutputStream classes to read and write data in a portable way.

There are two basic types of XML parsers: validating and non-validating. A validating parser checks an XML document against a document type definition or schema to ensure that the contents of the document are what the application expects. This requires work and slows down the processing. A non-validating parser skips this step and just ensures that an XML document is well-formed, in other words, that it conforms to the general rules that all XML documents must follow. Both kXML and NanoXML are non-validating parsers.

XML parsers can also be classified by how they process and represent an XML document. NanoXML is a single-step parser. Given a document, NanoXML parses it in a single operation and returns the document as a tree of objects. kXML, by comparison, is an incremental parser: it parses documents a piece at a time. There are advantages and disadvantages to either approach. If you're dealing with large documents, the single-step approach uses much more memory because the entire document is held in memory. But a single-step approach might make the most sense if you need to traverse through the document multiple times. The multi-step approach can easily deal with large documents, but you have do more bookkeeping to track of where you are in the document.

To use kXML, download the kXML source code from http://www.kxml.org, and include the kXML classes with your application. Not all the classes are required, so you might want to just download the ZIP file that contains the minimal implementation of kXML. After you install the files, add the following import statements to your application:

import org.kxml.*;
import org.kxml.parser.*;

When you're ready to parse a document, create an instance of the XmlParser class, which takes a character stream as its only argument:

try {
    Reader    r = .....;
    XmlParser parser = new XmlParser( r );
}
catch( java.io.IOException e ){
    // handle exception....
}

If your document is stored as a string, for example, you can read it by converting the string to a byte array and then combining InputStreamReader and ByteArrayInputStream:

// Read from string (exception handling omitted)
String    xml = "<a>some xml</a>";
ByteArrayInputStream bin = 
            new ByteArrayInputStream( xml.getBytes() );
XmlParser parser = new XmlParser( new InputStreamReader( bin ) );

The more likely scenario, however, is to receive a document over the network, for example, with the CLDC's Generic Connection Framework (GCF). You do this using the MIDP's built-in support for the HTTP protocol. You then take the input stream returned by the GCF and convert it to a character stream:

// Read from web (exception handling omitted)
HttpConnection    conn = .....;
InputStreamReader doc = 
         new InputStreamReader( conn.openInputStream() );
XmlParser parser = new XmlParser( doc );

After the parser has been created, you call its read method to read the individual pieces of the document. The read method returns a ParseEvent object for each element of the document:

try {
    boolean keepParsing = true;
        
    while( keepParsing ){
        ParseEvent event = parser.read();
 
        switch( event.getType() ){
            case Xml.START_TAG:
                ..... // handle start of an XML tag
                break;
            case Xml.END_TAG:
                ..... // handle end of an XML tag
                break;
            case Xml.TEXT:
                ..... // handle text within a tag
                break;
            case Xml.WHITESPACE:
                ..... // handle whitespace
                break;
            case Xml.COMMENT:
                ..... // handle comment
                break;
            case Xml.PROCESSING_INSTRUCTION:
                ..... // handle XML PI
                break;
            case Xml.DOCTYPE:
                ..... // handle XML doctype
                break;
            case Xml.END_DOCUMENT:
                ..... // end of document;
                keepParsing = false;
                break;
        }
    }
}
catch( java.io.IOException e ){
}

The ParseEvent class defines a number of methods for returning information about an element. The getType method, for example, returns the element type, such as whether it's the start of a tag, a comment, and so on. Other methods provide additional information, such as the text within a tag or the attributes of a tag. The parsing stops when the END_DOCUMENT event is reached.

kXML makes it fairly easy to do recursive-descent style parsing of a document, where the state of the parsing is implicitly maintained by calling methods recursively in response to a new parsing event. In simple cases you can do this just by keeping track of the last tag you saw.

To use NanoXML, you must also download some source code. The official NanoXML web site is http://nanoxml.sourceforge.net. A modified version of NanoXML that is compatible with the CLDC (the original NanoXML is for J2SE-based systems) is found at http://www.ericgiguere.com/nanoxml. As with kXML, you must include the source for NanoXML with your application. You then add the following import statement to your application:

import nanoxml.*;

To parse a document, create an instance of the kXMLElement class, and invoke one of parseFromReader, parseString, or parseCharArray:

HttpConnection    conn = .....;
InputStreamReader doc = 
         new InputStreamReader( conn.openInputStream() );
kXMLElement       root = new kXMLElement();

try {
    root.parseFromReader( doc );
}
catch( kXMLParseException pe ){
}
catch( IOException ie ){
}

Since NanoXML is a single-step parser, this parses the entire document and transforms it into a tree of XML elements. The root of the tree is the instance of kXMLElement that you created, and each node of the tree is another instance of kXMLElement. You can us methods such as getChildren, getTagName, and getContents to navigate through the document tree.

To demonstrate how parsing can be done, here is an MIDP application that fills a List component with a set of strings. The application uses kXML. To parse with NanoXML, uncomment the line that calls parseUsingNanoXML, and comment the line that calls parseUsingkXML. A complete project is also available for this application at http://www.ericgiguere.com/techtips/XMLTest.zip You can run this project using the J2ME Wireless Toolkit.

package com.ericgiguere.techtips;

import java.io.*;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import nanoxml.*;
import org.kxml.*;
import org.kxml.parser.*;

/**
 * Simple MIDlet that demonstrates how an XML document can be
 * parsed using kXML or NanoXML.
 */

public class XMLTest extends MIDlet {

    // Our XML document -- normally this would be something you
    // download.
    
    private static String xmlDocument =
        "<list><item>apple</item>" +
              "<item>orange</item>" +
              "<item>pear</item></list>";
              
    private Display display;
    private Command exitCommand = new Command( "Exit",
                                               Command.EXIT, 1 );
                                               
    public XMLTest(){
    }
    
    protected void destroyApp( boolean unconditional )
                       throws MIDletStateChangeException {
        exitMIDlet();
    }
    
    protected void pauseApp(){
    }
    
    protected void startApp() throws MIDletStateChangeException {
        if( display == null ){ // first time called...
            initMIDlet();
        }
    }
    
    private void initMIDlet(){
        display = Display.getDisplay( this );
        
        String [] items;
        
        //items = parseUsingNanoXML( xmlDocument );
        items = parseUsingkXML( xmlDocument );
        
        display.setCurrent( new ItemList( items ) );
    }
    
    public void exitMIDlet(){
        notifyDestroyed();
    }
    
    // Parses a document using NanoXML, looking for
    // "item" nodes and returning their content as an
    // array of strings.
    
    private String[] parseUsingNanoXML( String xml ){
        kXMLElement root = new kXMLElement();
        try {
            root.parseString( xml );
            
            Vector list = root.getChildren();
            Vector items = new Vector();
            
            for( int i = 0; i < list.size(); ++i ){
                kXMLElement node = 
                     (kXMLElement) list.elementAt( i );
                String      tag = node.getTagName();
                
                if( tag == null ) continue;
                if( !tag.equals( "item" ) ) continue;
                
                items.addElement( node.getContents() );
            }
            
            String[] tmp = new String[ items.size() ];
            items.copyInto( tmp );
            return tmp;
        }
        catch( kXMLParseException ke ){
            return new String[]{ ke.toString() };
        }
    }
    
    // Parses a document using kXML, looking for "item"
    // nodes and returning their content as an
    // array of strings.
    
    private String[] parseUsingkXML( String xml ){
        try {
            ByteArrayInputStream bin =
                            new ByteArrayInputStream( 
                                     xml.getBytes() );
            InputStreamReader in = new InputStreamReader( bin );
            XmlParser parser = new XmlParser( in );
            Vector    items = new Vector();
            
            parsekXMLItems( parser, items );
            
            String[] tmp = new String[ items.size() ];
            items.copyInto( tmp );
            return tmp;
        }
        catch( IOException e ){
            return new String[]{ e.toString() };
        }
    }
    
    private void parsekXMLItems( XmlParser parser, Vector items )
                                     throws IOException {
        boolean inItem = false;
        
        while( true ){
            ParseEvent event = parser.read();
            switch( event.getType() ){
                case Xml.START_TAG:
                    if( event.getName().equals( "item" ) ){
                        inItem = true;
                    }
                    break;
                case Xml.END_TAG:
                    if( event.getName().equals( "item" ) ){
                        inItem = false;
                    }
                    break;
                case Xml.TEXT:
                    if( inItem ){
                        items.addElement( event.getText() );
                    }
                    break;
                case Xml.END_DOCUMENT:
                    return;
            }
        }
    }
    
    // Simple List UI component for displaying the list of
    // items parsed from the XML document.
    
    class ItemList extends List implements CommandListener {
    
        ItemList( String[] list ){
            super( "Items", IMPLICIT, list, null );
            addCommand( exitCommand );
            setCommandListener( this );
        }
        
        public void commandAction( Command c, Displayable d ){
            if( c == exitCommand ){
                exitMIDlet();
            } 
        }
    }
}

— Note —

Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun MicrosystemsTM purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page, uncheck the appropriate checkbox, and click the Update button.

As of May 22, 2001, Sun Microsystems updated its Privacy Policy to give you a better understanding of Sun's Privacy Policy and Practice. If you have any questions, contact privacy@sun.com.

— Subscribe —

To subscribe to a JDC newsletter mailing list, go to the Subscriptions page, choose the newsletters you want to subscribe to, and click Update.

— Feedback —

Comments? Send your feedback on the J2ME Tech Tips to:

jdc-webmaster@sun.com

— Archives —

You'll find the J2ME Tech Tips archives at:

http://java.sun.com/jdc/J2METechTips/index.html

— Copyright —

Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This Document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html

- LINKS TO NON-SUN SITES

The J2ME Tech Tips may provide, or third parties may provide, links to other Internet sites or resources. Because Sun has no control over such sites and resources, You acknowledge and agree that Sun is not responsible for the availability of such external sites or resources, and does not endorse and is not responsible or liable for any Content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such Content, goods or services available on or through any such site or resource.

J2ME Tech Tips July 25, 2001

Sun, Sun Microsystems, Java, Java Developer Connection, and J2ME, J2SE and are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.