Master J2ME for live data delivery

Support multiple applications on multiple devices with this J2ME framework

Wireless applications that must contact a remote server for their data present special problems for the developer. Protocol support in J2ME (Java 2 Platform, Micro Edition) is much more limited than in J2SE (Java 2 Platform, Standard Edition), and some devices might require multiple access methods. Also, radio communications are much less reliable than the landline connections most Internet standards assume. Standalone J2ME applications that don't require server connectivity lack these problems. Because of these difficulties, you need to modify standard architectures to accommodate wireless development's special needs.

This article introduces some architectural solutions to the problems wireless applications face and presents a general framework for hosting multiple J2ME applications. To illustrate how to use the framework, I describe and implement two applications: Live Chess and Event Alert. Figure 1 shows an emulator running Live Chess. This sample application lets the user watch a live chess game with a mobile phone or other Java-enabled device. By studying the framework and how these applications are implemented, you can appreciate the key factors in designing an architecture for live data delivery—one of wireless technology's most exciting capabilities.

Figure 1. J2ME enables dynamic graphical applications on mobile phones

To understand the material presented here, you should already be familiar with J2ME technologies, such as MIDP (Mobile Information Device Profile) and related protocols, including WAP/WML (Wireless Application Protocol/Wireless Markup Language). See Resources for more information on these subjects.

Development tools

Although this article is primarily about J2ME architectures, I will briefly describe the tools I used to develop the sample applications. Working with J2ME and specialized devices means dealing with additional development complexities, such as the need to use emulators. Also, you need to preverify your classfiles when writing for a CLDC (Connected Limited Device Configuration) device. Preverification allows for a smaller and simpler device security verifier. For full information on J2ME compilation and tools, see Resources.

I used Sun Microsystems' J2ME Wireless Toolkit to develop the client packages for Live Chess and Event Alert. This toolkit integrates with Sun's Sun ONE Studio (formerly, Forte for Java) environment and with JBuilder's MobileSet extension. Using the integrated toolkit, you can compile, preverify, and debug your program while simultaneously using an emulator. This interactive debugging can be useful for complex interfaces.

Instead of using an integrated environment, I opted for a special tool in Sun's toolkit called the KToolbar. The KToolbar is a mini-development environment that performs the key tasks in creating a MIDlet suite. I developed the source files for my projects separately in JBuilder and then used the KToolbar to generate the MIDlet suites. Using the KToolbar is somewhat awkward because it expects a fixed directory structure, so unless you are willing to change your source tree conventions to match KToolbar's, you must create a separate source tree and copy files back and forth. You can do limited debugging with KToolbar because it dumps System.out to its console window as your application runs in the emulator, and it has some built-in tracing capabilities.

Experienced J2ME developers will want to use an integrated environment for greater convenience, but alternatives—such as using KToolbar, or even compiling, preverifying, and debugging from the command line—are completely workable. For wireless applications, how you organize your architecture end-to-end is much more important than your development tools in determining a given application's overall maintainability. This is why basing your projects on a sound design is so important.

Overview of live data requirements

To architect J2ME applications that require live data updates from a server, you must first analyze the functionality range you desire. Figure 2 shows the typical case.

Figure 2. The framework's overall plan creates a two-tier structure on the server that isolates the application logic from the server functionality. This is especially important for J2ME support because the server might support various protocols.

On the client side, MIDlets support any Java-enabled device. The device's MIDlets divide conceptually into three separate code regions along the lines of the Model-View-Controller (MVC) pattern:

  • Model: Preferences, constraints, and application data
  • View: Display logic
  • Controller: Communication and application logic

The controller should support both data polling and listening for notifications. Currently, the MIDP specification does not support listening, but the upcoming MIDP 2.0 will. In addition, some devices, such as RIM's BlackBerry, currently have extensions to support listening for server push. You must prepare the display logic to render appropriate interfaces over a range of resolutions. The data model includes user preferences, application constraints, and the presentation data.

The idea behind application constraints is that different data model versions can be appropriate for different device types. For example, if the device does not support sound, then the server should not waste bandwidth by including application sound files in its data transmissions. Likewise, the server might send different logo and image sets depending on whether the device has a medium-sized display (160 by 160 pixels) or a small display (120 by 120 pixels).

On the server side, the architecture should be prepared to deliver its data via a variety of interfaces, including WML and servlets. Under normal circumstances, servlets are the preferred method of communicating with a MIDlet. With WML presentation capability, you have a backup in case the user cannot install the client for some reason or has trouble operating it. Being able to implement the framework with J2EE interfaces is also an important server-side consideration because most commercial clustering systems only work with Enterprise JavaBeans (EJBs).

Servers should also have a notification engine that lets them send and receive out-of-band messages, such as SMS (short message service) messages and email. This capability can be an integral part of your application and can also act as a backup system. If you have users who do not have Java-enabled devices, your notification engine might be the only way to reach them.

The client framework

Now that you have an idea of the overall architecture's elements, let's consider the client design in more detail. Figure 3 shows how to structure a client that primarily displays server-generated information. Most importantly, you should isolate and abstract the data package or packages that are traded with the server. You should not process this raw data in different parts of the MIDlet. When the package arrives, it must first be validated and then used to populate the data model. Other MIDlet parts use the data model exclusively. Validation is necessary because wireless networks are lossy and the client often receives fragmentary transmissions, in which case, it needs to repeat its request.

Figure 3. In the client architecture, the server's data packets hydrate the data model used by the MIDlet for all its operations. The controller has two possible inbound interface mechanisms—one for polling, the other for listening. MIDP 2.0 will support the listener.

The controller manages communication with the server and activation of the MIDlet's displayables. A standard way to implement multiple communication interfaces (e.g., both polling and notification) on the controller is to create a base class with common functionality and then subclass it with the appropriate interface. Listing 1 shows how we do this in the case of the Live Chess MIDlet:

Listing 1. Live Chess

class ServerCommunicator {
      //Override these methods to provide functionality
      boolean zPacketArrived( DataPacket dp ){ return false; }
      boolean zPacketSend( DataPacket dp ){ return false; }
class ServerCommunicator_Polling extends ServerCommunicator {
      private final static int POLLING_INTERVAL_MS = 10000;
      private final static String SERVER_URL = 
      void vStart(){
            PollingContainer poller = new PollingContainer(this);
            java.util.Timer timerPoller = new java.util.Timer();
            timerPoller.schedule(poller, 0, POLLING_INTERVAL_MS);
      String getURL(){ return SERVER_URL; }
      boolean zPacketArrived(DataPacket dp){
            if( !LiveChessClient.zValidatePacket(dp) ) return false;
      boolean zPacketSend( DataPacket dp ){
            try {
                  int iDataLength = dp.length();
                  HttpConnection con =
                        "Profile/MIDP-1.0 Configuration/CLDC-1.0" );
                  con.setRequestProperty("Content-Language", "en-US" );
                  con.setRequestProperty("Accept", "application/octet-stream" );
                  con.setRequestProperty("Connection", "close" );
                        Integer.toString( iDataLength ) );
                  OutputStream os = con.openOutputStream();
                  os.write( dp.getBytes() );
                  return true;
            } catch( Exception ex ) {
                  return false;
class PollingContainer extends java.util.TimerTask {
      ServerCommunicator_Polling mParent;
      public PollingContainer( ServerCommunicator_Polling parent ){
          mParent = parent;
      public final void run(){
          String sURL = mParent.getURL();
            try {
                  vPollServer( sURL );
            } catch(Exception ex) {}
      public void vPollServer ( String sURL ) throws IOException {
            HttpConnection con = (HttpConnection);
                  "Profile/MIDP-1.0 Configuration/CLDC-1.0" );
            con.setRequestProperty("Content-Language", "en-US" );
            con.setRequestProperty("Accept", "application/octet-stream" );
            con.setRequestProperty("Connection", "close" );
            byte[] abData;
            if( con.getResponseCode() == HttpConnection.HTTP_OK ){
                  InputStream isData = con.openInputStream();
                  int iDataLength = (int)con.getLength();
                  if( iDataLength == -1 ){ //Read one char at a time
                        ByteArrayOutputStream baos = 
                              new ByteArrayOutputStream();
                        int iData;
                        while( (iData = != -1 )
                        abData = baos.toByteArray();
                  } else { //Length is known, read all at once
                        abData = new byte[iDataLength];
               abData );
            DataPacket dpArrived = new DataPacket_LiveChess(abData);
            mParent.zPacketArrived( dpArrived );

The framework's view module must fit the data model to the display. Depending on the device characteristics, the controller might request different data classes. The view module needs to inform the controller which data class it wants—small, medium, or large, for instance. However, the view should work with the model in exactly the same way no matter what data class populates the model.

The server framework

Perhaps the server framework's most interesting feature is that it uses the same data-handling model and application control logic as the client. This is why you should keep the application control logic strictly separate from the communication interfaces. You can reuse these client parts with a different view implementation to render your application's WML version. In this case, the data packet simply moves around inside the server instead of being sent to the client.

Figure 4 shows the framework's server half. When a request arrives, the application gateway routes the request to the correct application according to its URL. Each application container implements a set of handlers to process requests for both WML and MIDP responses. The gateway exposes an API that lets the applications make asynchronous notifications. The gateway isolates the applications from managing the details of the external communication protocols and handles all requests consistently. For example, if requests need to be logged, the gateway can do it for all applications. The gateway also acts as a load balancer if the applications are located on separate machines or clusters.

Figure 4. In the framework's server half, the gateway acts like a switchboard to route data packages to the correct application. Note that each application supports two output renderers: one for MIDP and one for WML.
1 2 Page 1
Page 1 of 2