Skip Masthead Links developers.sun.com   » search tips  |  Search:     
The Source for Developers
A Sun Developer Network Site
Products and Technologies
Technical Topics
Sun Microsystems
 
 
Technical Articles and Tips
Obfuscating MIDlet Suites with ProGuard
 



One challenge of MIDP development is keeping applications small and light. While an obfuscator is designed to make applications harder to reverse-engineer, a happy side effect is that it also makes them as small as possible. ProGuard is an open source obfuscator that does an excellent job of reducing the size of MIDlet suite JARs. This tech tip explains how to use ProGuard on the command line, with Ant, and with the J2ME Wireless Toolkit.

To learn more about obfuscators in general, see:

ProGuard is freely available under the GNU General Public License. If you can live without the support that accompanies a payware product, ProGuard is an excellent addition to your MIDlet development toolbox.

Downloading and Installing ProGuard

The current version of ProGuard is 1.2. It is distributed as either a .tar.gz file or a .zip file. Once you download and unpack the archive you prefer, you'll have a proguard1.2 directory with several subdirectories. The obfuscator itself is contained in lib/proguard.jar.

Creating a Configuration File

ProGuard accepts arguments either on the command line or packaged in a configuration file. Several examples are shown in the examples directory of the ProGuard installation. Like most obfuscators, ProGuard needs only a few pieces of information from you. For MIDlet suites it needs:

  • The location of libraries. ProGuard needs to know where to find the MIDP APIs.
  • The location of the input classfiles. ProGuard expects these to be packaged in one or more JARs.
  • A destination for the obfuscated classfiles. ProGuard generates a second JAR.
  • Classes that should not be obfuscated. Specify the MIDlet classes themselves; if they're obfuscated, the application manager software won't be able to find them.

For the PeekAndPick 2.0 MIDlet suite I used the following configuration file, peekandpick.pro:

-libraryjars /wtk104/lib/midpapi.zip
-injars proguard/PeekAndPick20-input.jar
-outjar proguard/PeekAndPick20-output.jar
-keep public class * extends javax.microedition.midlet.MIDlet

These configuration files are pretty powerful. Notice how the * character tells ProGuard to leave all MIDlet classes untouched, sparing me the need to name each MIDlet explicitly.

Running ProGuard From the Command Line

When you run ProGuard, you must supply several pieces of information.

  • Your CLASSPATH must include the ProGuard classes.
  • The name of the ProGuard class is proguard.ProGuard.
  • You need to tell ProGuard the name of your configuration file.

The following example (from Windows) runs ProGuard using peekandpick.pro. Note that you could specify configuration options on the command line instead.

C:\>set proguard_jar="\Program Files\proguard1.2\lib\proguard.jar"

C:\>java -jar %proguard_jar% @peekandpick.pro
ProGuard, version 1.2
Reading library jar [/wtk104/lib/midpapi.zip]
Reading program jar [proguard/PeekAndPick20-input.jar]
Writing output jar [proguard/PeekAndPick20-output.jar]...

C:\>

Running ProGuard From an Ant Script

Ant is a great tool for building projects. It enables you to combine separate steps like compiling, preverifying, and packging into a single straightforward build process. Ant makes it easy to insert extra steps like obfuscation into the MIDP development cycle. For more information on Ant, see Managing Wireless Builds with Ant.

Running ProGuard from Ant is similar to running it from the command line. The same information is needed, but the syntax is different. The following example, from PeekAndPick's build.xml script, shows the obfuscation step of the build process. Here I pass arguments directly on the command line instead of using an external configuration file.

<java fork="yes" classname="proguard.ProGuard"
classpath="${proguard}">
    <arg line="-libraryjars ${midp_lib}"/>
    <arg line="-injars build/proguard/${project}-input.jar"/>
    <arg line="-outjar build/proguard/${project}-output.jar"/>
    <arg line="-keep 'public class * extends javax.microedition.midlet.MIDlet'"/>
    </java>

In PeekAndPick 2.0, ProGuard saves an impressive 20%, cutting the size of the MIDlet suite JAR size from 48,235 bytes to 38,673.

Using ProGuard with the J2ME Wireless Toolkit

The J2ME Wireless Toolkit, starting with version 1.0.4, provides a mechanism for including an obfuscator in the build cycle. Support for RetroGuard is packaged with the toolkit, but it will accept any obfuscator if you supply a little glue.

The basic technique is to write an implementation of com.sun.kvem.environment.Obfuscator that invokes the obfuscator of your choice. The Obfuscator interface declares two methods:

  • public void createScriptFile(File jadFilename, File projectDir)
    

    This method creates a script file that will be used to run the obfuscator. Standard options can be placed in the script.

  • public void run(File jarFileObfuscated, String wtkBinDir,
    		String wtkLibDir, String jarFilename,
    		String projectDir, String classPath
    		String emptyApi)
    

    This method actually runs the obfuscator, most likely using the script created by createScriptFile().

You can use the following class to integrate ProGuard into the J2ME Wireless Toolkit. Note that, like the RetroGuard integration class, it assumes that proguard.jar is present in the bin directory of the J2ME Wireless Toolkit. (You may download this file if you wish.)

import java.io.*;

import com.sun.kvem.environment.Obfuscator;

public class ProGuardWTKGlue
    implements Obfuscator {
  public void createScriptFile(
      File jadFilename, File projectDir) {
    try {
      File scriptFile = new File(projectDir, "script.pro");
      OutputStream rawOut = new FileOutputStream(scriptFile);
      PrintWriter out = new PrintWriter(rawOut);
      out.println("-keep public class * extends " + 
          "javax.microedition.midlet.MIDlet");
      out.flush();
      out.close();
    }
    catch (IOException ioe) {
      System.out.println(ioe);
    }
  }
  
  public void run(File jarFileObfuscated, String wtkBinDir,
      String wtkLibDir, String jarFilename,
      String projectDir, String classPath,
      String emptyApi) {
    // Create the command to run ProGuard.
    StringBuffer buffer = new StringBuffer();
    buffer.append("java -jar ");
      buffer.append(wtkBinDir);
      buffer.append("proguard.jar ");
    buffer.append("-libraryjars ");
      buffer.append(wtkBinDir);
      buffer.append("..");
      buffer.append(File.separatorChar);
      buffer.append("lib");
      buffer.append(File.separatorChar);
      buffer.append("midpapi.zip ");
    buffer.append("-injars ");
      buffer.append(jarFilename);
      buffer.append(' ');
    buffer.append("-outjar ");
      buffer.append(jarFileObfuscated.getPath());
      buffer.append(' ');
    buffer.append('@');
      buffer.append(projectDir);
      buffer.append(File.separatorChar);
      buffer.append("script.pro");
    String command = buffer.toString();
    System.out.println(command);
    // Run the ProGuard command.
    Runtime runtime = Runtime.getRuntime();
    try {
      Process p = runtime.exec(command);
      redirect(p.getErrorStream(), System.out);
      redirect(p.getInputStream(), System.out);
    }
    catch (IOException ioe) {
      System.out.println(ioe);
    }
    
    // Now add the resource files to the JAR.
    buffer = new StringBuffer();
    buffer.append("jar uvf ");
      buffer.append(jarFileObfuscated.getPath());
      buffer.append(" -C ");
      buffer.append(projectDir);
      buffer.append(File.separatorChar);
      buffer.append("res .");
    command = buffer.toString();
    System.out.println(command);
    // Run the jar command.
    try {
      Process p = runtime.exec(command);
      redirect(p.getInputStream(), System.out);
    }
    catch (IOException ioe) {
      System.out.println(ioe);
    }
  }
  
  private void redirect(InputStream rawIn, OutputStream rawOut)
      throws IOException {
    BufferedReader in = new BufferedReader(
        new InputStreamReader(rawIn));
    PrintWriter out = new PrintWriter(rawOut);
    
    String line;
    while ((line = in.readLine()) != null)
      out.println(line);
    in.close();
    out.flush();
  }
}

There's nothing exotic about this class. It creates the same command string you would enter manually and executes the command. Output from the obfuscator is piped into the console window. Because ProGuard does not pass resource files through to the output file, the second part of the run() method adds them by invoking jar.

To compile this class, you'll need to include wtklib/kenv.zip from the J2ME Wireless Toolkit directory in your classpath. Once you've successfully compiled ProGuardWTKGlue, you need to tell the toolkit about it by editing the ktools.properties file. On Windows, this file is located in the wtklib/Windows directory under the J2ME Wireless Toolkit directory. This file tells the toolkit both the name and the location of the obfuscator glue class. Edit the two obfuscator.runner properties as follows. Adjust the obfuscator.runner.classpath as appropriate for your system.

obfuscator.runner.class.name: ProGuardWTKGlue
obfuscator.runner.classpath: c:\\projects\\ProGuardWTKGlue

Restart the J2ME Wireless Toolkit and open a project. When you choose Project > Package > Create Obfuscated Package, the toolkit will use the class you just installed to obfuscate the package. Assuming everything is set up correctly, you should see the output of the glue class in the console window of the J2ME Wireless Toolkit, including the output of both the obfsucator and the jar command.

Summary

ProGuard is a powerful, free tool for optimizing your MIDlet's code size. Its friendly license, attractive price tag, compelling perfomance, and powerful configuration options make it an excellent addition to your MIDlet development toolbox. ProGuard can easily be integrated into a build cycle based on Ant or the J2ME Wireless Toolkit.



Back To Top