Table of ContentsBuild SystemsConclusion

Multi-Device Builds

If you recall, at the start of the chapter you customized the Star Assault code to use special features available through the Nokia UI. Unfortunately, this means the current version of the game won't work on any devices that don't support these featuresbasically anything other than a Nokia. In this section, you'll see how to use Ant and Antenna to adapt your code to support multiple devices.

There are two main areas you need to cover to build different versionsresource management and device-specific code. Since I just covered the preprocessor, let's tackle the changes to the code first.

Device-Specific Code

The most convenient way to manage different source code versions is to use the preprocessor to embed conditionals based on the device you're building for directly inside the code. Although it would be cleaner (and more organized) to use device sub-classing (different versions of classes for different devices), you really can't afford the extra space these new classes would add to the JAR file.

A simple example of how to use the device preprocessor is the vibration code you saw earlier. You can use a preprocessor condition to wrap up the Nokia-specific code if you're building only a Nokia version. To do this, you first need to set a symbol in the call to the preprocessor that identifies the device for which you're building. In the following task, I'm using the dev property to indicate this.

<!-- preprocess -->
<property name="debug" value="debug"/>
<property name="dev" value="nokia"/>
<wtkpreprocess srcdir="src" destdir="${bd}/psrc" symbols="${debug}, ${dev}"
                verbose="true"/>

The value of dev is now available as a condition for the preprocessor. Here are the changes to the Tools class. Notice I've wrapped all the Nokia-specific code in if blocks.

//#ifdef nokia
import com.nokia.mid.ui.DeviceControl;
//#endif

...

public final static void vibrate(int strength, int time)
{
    if (StarAssault.getApp().isOptionVibrate())
    {
        //#ifdef nokia
            DeviceControl.startVibra(strength, time);
        //#endif
    }
}

You can see that it's easy to add different code versions based on different device values. Another good case is the use of plain old Canvas versus the enhanced Nokia FullCanvas class. For example:

//#ifdef nokia
public class GameScreen extends FullCanvas implements Runnable
//#else
public class GameScreen extends Canvas implements Runnable, CommandListener
//#endif

Unfortunately, mode IDEs aren't particularly happy with this type of code (but they get over it).

Those are the basics of multi-device code; you just keep wrapping the device-specific code inside preprocessor conditionals. Next you'll take a look at how to handle resources for different devices.

Managing Resources

As you saw at the start of this chapter, Nokia has some great features. Some of these, such as transparent PNGs and image reflection and rotation, require you to use different image files for different builds. Handling this manually (by copying files) might be okay for one or two devices, but when you're dealing with many devices or many types of different files it can become a real mess. Thankfully, you can use Ant to automate this process as well.

First you need to organize your resource files a little better. What you do is create a directory for all of the default resourcesversions of the files (mostly images) that you'll use unless a particular device chooses to override themas well as a directory for each device that has its own custom resources. The directory structure is listed in Figure 14.6; to help you understand, I've included the entire build structure as well:

Figure 14.6. The directory structure

graphic/14fig06.gif


Now here's the cool bit. When you build a version of the game, you use Ant to copy all the resources from the default directory (res/default) into the build resource directory for the device you're building (in this case, build/nokia/res). You then copy the device-specific resource over the top of this (res/nokia to build/nokia/res). Here's the Ant build to do this. (I've also included a revised version of the wtkpackage task using the new directory.)

<!-- copy the default resources -->
<copy todir="${bd}/res">
    <fileset dir="res/default"/>
</copy>

<!-- copy over anything specific to this device -->
<copy overwrite="true" todir="${bd}/res">
    <fileset dir="res/${dev}"/>
</copy>

<wtkpackage jarfile="${bd}/${midlet.name}.jar"
             jadfile="${bd}/${midlet.name}.jad"
             preverify="true"
             classpath="${midplib}"
             obfuscate="true">
    <fileset dir="${bd}/classes"/>
    <fileset dir="${bd}/res"/>
</wtkpackage>

You can now move the Nokia-specific image files into the res/nokia directory and the original default version images into the res/default directory.

The Complete Build

All the components of your build process are ready to go now. However, there's still a little problem. In order to build different versions of the game, you would need to change the value of the dev property by editing the build file. This becomes annoying after a while, especially if you're constantly switching between versions for testing purposes (something I recommend you do quite a bit during development). Wouldn't it be nice if you could build a different version simply by using a different target?

To do this, you can set the device parameter and then "call" the device build target. For example:

<target name="build nokia" depends="init">
    <property name="dev" value="nokia"/>
    <property name="midplib"
        value="C:\j2me\Nokia\Devices\Nokia_7210_MIDP_SDK_v1_0\lib\classes.zip"/>
    <antcall target="device build"/>
</target>

Notice you set the midplib set prior to the call. This lets you use alternative libraries for compilation as well.

All right, now you have all the pieces, so let's take a look at the entire build file in one go. This version includes builds for Nokia, Vodafone, and the default device, as well as a convenient "build all" target.

<project name="StarAssault" default="compile" basedir=".">

    <!-- ............................ INIT .................................. -->
    <target name="init" depends="setup">
        <buildnumber file="ext/build.number"/>
        <property name="wtk.home" value="c:\java\wtk104"/>
        <property name="midlet.name" value="StarAssault"/>
        <property name="debug" value="debug"/>
    </target>

Build target specifically for a device using the Nokia UI. Note the use of a different midplib file.

    <!-- ........................... DEVICE: NOKIA .......................... -->
    <target name="build nokia" depends="init">
        <property name="dev" value="nokia"/>
        <property name="midplib"
value="C:\j2me\Nokia\Devices\Nokia_7210_MIDP_SDK_v1_0\lib\classes.zip"/>
        <antcall target="device build"/>
    </target>

Executes the Nokia 7210 emulator.

<target name="run nokia" depends="build nokia">
    <exec executable="C:\j2me\Nokia\Devices\Nokia_7210_MIDP_SDK_v1_0\bin\7210.exe">
        <arg line="build/${dev}/${midlet.name}.jad"/>
    </exec>
</target>

Build target for the default MIDP 1 toolkit emulator.

<!-- .......................... DEVICE: DEFAULT ......................... -->
<target name="build default" depends="init">
    <property name="dev" value="default"/>
    <property name="midplib" value="${wtk.home}/lib/midpapi.zip"/>
    <antcall target="device build"/>
</target>

<target name="run default" depends="build default">
    <exec executable="C:\java\WTK104\bin\emulator.exe">
        <arg line="-Xdescriptor:build/${dev}/${midlet.name}.jad"/>
    </exec>
</target>

The following task is a method of building all of the targets in one go. This is something you might do when packaging up lots of different builds for a publisher.

<!-- ............................ BUILD ALL ............................. -->


<target name="build all" depends="init">
    <antcall target="build nokia"></antcall>
    <antcall target="build default"></antcall>
    <antcall target="build deftrans"></antcall>
</target>



<!-- ............................... TOOLS ...............................-->

This is the bulk of the build process and is called by the different tasks to carry out a build on a specific device.

    <!-- ............................ DEVICE BUILD ......................... -->

    <target name="device build" depends="init">
        <property name="bd" value="build/${dev}"/> <!-- build dir -->
        <delete dir="${bd}/psrc"/>
        <delete dir="${bd}/classes"/>
        <mkdir dir="${bd}/psrc"/>
        <mkdir dir="${bd}/classes"/>
        <!-- preprocess-->
        <wtkpreprocess srcdir="src" destdir="${bd}/psrc" symbols="${dev},${debug}"
                        verbose="true"/>
        <!-- compile -->
        <javac destdir="${bd}/classes" srcdir="${bd}/psrc"
                bootclasspath="${midplib}" debug="true" target="1.1"/>
        <!-- create JAD file -->
        <wtkjad jadfile="${bd}/${midlet.name}.jad"
                jarfile="${bd}/${midlet.name}.jar"
                name="${midlet.name}" vendor="Martin Wells" version="1.0.0">
            <midlet name="${midlet.name}" class="${midlet.name}"/>
            <attribute name="MIDxlet-API" value="VSCL-1.0.1"/>
        </wtkjad>

        <!-- copy resources then package it up -->
        <copy todir="${bd}/res">
            <fileset dir="res/default"/>
        </copy>               <!-- copy default resource -->
        <copy overwrite="true" todir="${bd}/res">
            <fileset dir="res/${dev}"/>
        </copy><                             !-- copy overrides -->

        <wtkpackage jarfile="${bd}/${midlet.name}.jar" jadfile="${bd}/${midlet.name}.jad"
preverify="true"
          classpath="${midplib}"><!--obfuscate="true"-->
            <fileset dir="${bd}/classes"/>
            <fileset dir="${bd}/res"/>
        </wtkpackage>
    </target>

    <!-- ................................ SETUP ............................ -->
    <target name="setup">
        <!-- Antenna Setup -->
        <taskdef name="wtkjad" classname="de.pleumann.antenna.WtkJad"/>
        <taskdef name="wtkbuild" classname="de.pleumann.antenna.WtkBuild"/>
        <taskdef name="wtkpackage" classname="de.pleumann.antenna.WtkPackage"/>
        <taskdef name="wtkmakeprc" classname="de.pleumann.antenna.WtkMakePrc"/>
        <taskdef name="wtkrun" classname="de.pleumann.antenna.WtkRun"/>
        <taskdef name="wtkpreverify" classname="de.pleumann.antenna.WtkPreverify"/>
        <taskdef name="wtkobfuscate" classname="de.pleumann.antenna.WtkObfuscate"/>
        <taskdef name="wtksmartlink" classname="de.pleumann.antenna.WtkSmartLink"/>
        <taskdef name="wtkpreprocess" classname="de.pleumann.antenna.WtkPreprocess"/>
    </target>
</project>

    Table of ContentsBuild SystemsConclusion