Kevin Vance - A Simple J2ME Program and Build File

Entries | Archive | Friends | Friends' Friends | User Info

11:26 pm

A Simple J2ME Program and Build File

Saturday, July 23rd, 2005
Previous Entry Share Next Entry
(I said I'd write some notes on my J2ME adventures today for zztzed, and I got a little carried away...)

Here's a quick actually pretty detailed rundown on how I got started writing J2ME apps, or MIDlets. I'm assuming you already know how to write regular Java programs and that you understand XML syntax. I'll go over installing the tools and libs, creating a proper set of JAR and JAD files, making a build file for Ant, and using the emulator.

Installing the Tools

When you go to Sun's J2ME site, it is not very clear what you need to download from there. For example, I downloaded and installed CLDC 1.1 and MIDP 2.0. That was close, but WRONG! The correct answer is the J2ME Wireless Toolkit 2.2 (WTK).

Note: If you plan on using the emulator, you need to make sure the directory tree you installed the WTK to is writable by your user. I originally installed it into /opt/sun-wtk-2.2 as root, and had to chown it to kvance.

Now, we have to update the environment to include the WTK tools and libraries. I put the following in my .zshrc:

SUN_WTK=/opt/sun-wtk-2.2
export PATH=$PATH:$SUN_WTK/bin
export CLASSPATH=$CLASSPATH:$SUN_WTK/lib/cldcapi10.jar:$SUN_WTK/lib/midpapi20.jar

I'm including cldcapi10.jar for CLDC 1.0 and midpapi20.jar for MIDP 2.0. To simplify, CLDC has the base libraries you'll be using, and MIDP has libraries for user interface functions. Take a look in $SUN_WTK/lib at the other versions available. You may want to use MIDP 1.0 for greater compatibility.

There is a slightly convoluted build process needed to get to the final result, so we'll want to use an automated build system. I chose Apache Ant. If you have ever compiled a big java program, you probably already have a copy of Ant. If not, I'll wait while you install Ant 1.6.3 or higher.

Ready? Okay, let's get started.

Writing the MIDlet

Unlike a regular Java app which must define a static main() function, your MIDlet must extend the MIDlet class. To do that, it must have three functions: startApp(), pauseApp(), and destroyApp(). I'm going to make a project directory ~/projects/Hello containing a source code directory src. In , I'll create a nice skeleton application called Hello.java:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class Hello extends MIDlet
{
  Display display;
  Form mainForm;

  public Hello()
  {
    mainForm = new Form("Hello!");
  }

  public void startApp()
  {
  }

  public void pauseApp()
  {
  }

  public void destroyApp(boolean unconditional)
  {
    notifyDestroyed();
  }
}

Ah, java. I can't think of another language that makes you write as many lines to do absolutely nothing ;) There isn't much going on here. We define a Form (which we can collect GUI elements on) and a Display (which we can attach the form to). In the constructor, we initialize the Form with the string "Hello!", which will appear in the title bar of the app. In destroyApp(), we call notifyDestroyed() to make sure the phone knows we're done.

You could run this, but it wouldn't do anything. We have to attach the form to the screen. And while we're in there, let's add a simple GUI element. Update startApp().

  public void startApp()
  {
    display = Display.getDisplay(this);
    display.setCurrent(mainForm);

    StringItem helloString = new StringItem("HELLO WORLD", "how are you");
    mainForm.append(helloString);
  }

We use the static function getDisplay() to retrieve the current Display object. The we call display.setCurrent() to select our Form, mainForm. As for the GUI element, it's a StringItem. StingItems have two parts, a label and the text. Our label is "HELLO World", and our text is "how are you". We attach it to the form with mainForm.append(). This will become clear when you see the result.

Okay, that's all for source code. There are two more files we'll need to write in order for the phone to recognize our app: the Manifest, and the JAD file. The manifest gets included in the JAR (the entire app is housed in a JAR file), and the JAD just has some... descriptive info about the app. In src, let's make a new file called Manifest.mf:

MIDlet-Name: Hello
MIDlet-Version: 1.0.0
MIDlet-Vendor: Weichsoft

All self-explanitory. And again in src, a file called input.jad:

MIDlet-1: Hello, , Hello
MIDlet-Name: Hello
MIDlet-Version: 1.0.0
MIDlet-Vendor: Weichsoft
MIDlet-Jar-URL: Hello.jar
MIDlet-Jar-Size: $size
MicroEdition-Profile: MIDP-2.0
MicroEdition-Configuration: CLDC-1.0

Now this could use some explanation. The MIDlet-1 parameter takes 3 options separated by commas. The first is the name to display on the phone's menu. The second (which we left blank) can be the name of an icon image (stored in the JAR). The third is the name of the class to run.

$size, the value for MIDlet-Jar-Size is actually not going to work. That's because we need to create the JAR file first, measure the file size of the JAR, and replace $size with it. Every time the app is built.

Make sure the MIDP and CLDC versions match the libs in your CLASSPATH.

Manually Building the JAR and JAD Files

Note: This entire chapter is NOT NECESSARY. It's only for people like me who need to run through the whole thing manually before they stumbit to automation. Scroll down to the next one.

Okay, disclaiming aside, let's make a new directory to work in. Call it ~/projects/Hello/build. First, compile Hello.java:

javac -d build src/Hello.java

That should produce Hello.class in the build directory. The next step is called preverification. We perform some checks on the class file so the phone doesn't have to later. To that end, we make a new directory for the results: ~/projects/Hello/verified and run:

preverify -d verified build

The verified classes will be copied to the directory verified when preverify finishes. Now, we'll work in the verified directory. Copy over your manifest and JAD files, and make a JAR:

cd verified
cp ../src/Manifest.mf .
cp ../src/input.jad ./Hello.jad
jar cvfm Hello.jar Manifest.mf Hello.class

A jar command looks a lot like a tar command, except that the Manifest must be the first file in the list (that's the m option). The only other file we include is our app class. Now that we have our JAR file, we can update the size in our JAD file:

% ls -l Hello.jar 
-rw-r--r--  1 kvance users 1073 Jul 23 22:01 Hello.jar
% vim Hello.jad
...
MIDlet-Jar-Size: 1073
...

Woooooooooo$@!&*$@! We have a JAR file and a JAD file. Lot of work though, huh? Anyway, you can now test it with the emulatior:

emulator -Xdescriptor:Hello.jad

If today has been a typo-free day, the emulator will start and you can launch your app. Bask in it. Then delete it, because we still need to make a build file to automate this mess!

cd ..
rm -rf build verified

Automating the Build

Note: This is the first and only Ant buildfile I've written. It might be totally wrong stylistically, but it does work.

So even if you just scrolled past the last chapter, you can see why we need some automation here. It's time for Ant. An Ant buildfile is kinda like an XML version of a Makefile. It's made up of a bunch of simple, easy-to-write parts that combine to form a hideous blob of unreadable gibberish like all XML files. Let's take it slow. In ~/projects/Hello, build.xml:

<project name="Hello" default="all">

  <target name="all" depends="compile,preverify,dist" />

  <target name="clean">
    <delete dir="dist" />
    <delete dir="docs" />
    <delete dir="obj" />
    <delete dir="verified" />
  </target>

</project>

The entire build file lives in the <project> tag. We set the default target to "all". Below, we define "all" as a collection of other targets to execute in order. We also make one extra target called clean. It should be apparent how clean works: it completely wipes out several directories. Ant has many built-in tags like <delete>. These are called tasks.

Let's add the required targets, one at a time. All inside the <project> tag. First, compile:

  <target name="compile">
    <!-- Compile all java files -->
    <mkdir dir="obj" />
    <javac srcdir="src" destdir="obj" />
  </target>

The <mkdir> and <javac> tasks are pretty simple. We create a directory obj to store the classes in, and javac will compile all of the java files in src to there. Now, on to preverify:

  <target name="preverify">
    <!-- Execute "preverify" on all class files -->
    <mkdir dir="verified" />
    <exec executable="preverify">
      <arg line="-d verified" />
      <arg line="obj" />
    </exec>
  </target>

This is the first time we explicitly choose a program to execute. The executable name is preverify, and for clarity each of the arguments is in its own arg tag. This simply runs preverify -d verified obj, verifying all the classes and putting the result in the directory verified. Finally, dist. This is going to be more complicated:

  <target name="dist">
    <mkdir dir="dist"/>

    <!-- Copy templates over -->
    <copy file="src/Manifest.mf" tofile="obj/Manifest.mf" overwrite="true" />
    <copy file="src/input.jad" tofile="obj/Hello.jad" overwrite="true" />

    <!-- Make JAR -->
    <jar basedir="verified" jarfile="dist/Hello.jar"
         manifest="obj/Manifest.mf" />

    <!-- Update JAD with JAR size -->
    <length property="size" file="dist/Hello.jar" />
    <replace file="obj/Hello.jad" token="$size" value="${size}" />
    <copy file="obj/Hello.jad" todir="dist" />

  </target>

Generally, we create the JAR file in the directory dist, and update the JAD file with the JAR's size. The updated JAD file is also copied to dist. Specifically...

We copy the Manifest and JAD templates over to the obj directory to work on. (There is actually no work to do on the Manifest, but there could be. More on that later.) The <jar> task creates dist/Hello.jar out of all the files in verified, and uses our Manifest file.

With dist/Hello.jar created, we use the <length> task to determine its file size. The result is stored in the property "size", which we can refer to later as "${size}". We do that in the next line, replacing the $size placeholder in our JAD file with the proper size of the JAR. Finally, the completed JAD is copied into the directory dist.

To bulid the project, run:

ant

Woooooooooo$@!&*$@! We have a JAR file and a JAD file. You can now test it with the emulator:

cd dist
emulator -Xdescriptor:Hello.jad

This is a pretty long commandline. To save time, I put this is my .zshrc:

function midp {
        $SUN_WTK/bin/emulator -Xdescriptor:"$@"
}

That way, I can just run midp Hello.jad.

Closing Remarks

If you got this far, then you're about as far as I am with J2ME. Now it's just a matter of reading the docs. There are a few other things, though. Remember how we copied the Manifest.mf over to our working directory, but didn't modify it? To make the build script more flexible, you could replace ALL of the name, author, version, etc. values with $placeholders. Then before you create the JAR file, use a <replace> task on Manifest.mf and Hello.jad to fill in all the values. Excercise for the reader :)

One more word of advice. The next thing you'll probably want to add to your Hello app is an Exit command you can select with a softbutton. The class you're looking for is called Command. You'll have to make your Hello class implement CommandListener, and add a new Command to your mainForm. Good luck, and have fun!

-- kvance
23 July 2005

References

Link )Reply )