Overcoming Android's Problems with JDK 7, Part 1
Google doesn't include JDK 7 in Android's system requirements, but you can still use this JDK to develop Android apps. Before doing so, you need to be aware of three problems that are bound to plague you during development. This article begins a two-part series that introduces you to these problems and shows you how to overcome them. Part 1 investigates problems related to JAR library creation and release mode APK signing.
Creating JAR Libraries
You've created a JAR library whose source code doesn't reference Android-specific APIs, nor does it reference standard Java APIs newer than Java 5, except for the few Java 6 enhancements to Android's version of the java.io.File class (and maybe other Java 6 enhancements that are present in Android). Furthermore, the source code doesn't reference any language features introduced after Java 5.
You decide to incorporate this library into the app that you're building by copying its JAR file into your project's libs directory (which is located underneath the project home directory). You compile the source code and let Android merge the library into the resulting app, and note a successful build. You then install the APK and attempt to run the app, and are greeted with a message that the app has stopped.
To demonstrate this problem, create a UseUtils project. Execute the following command (spread across two lines for readability) to create UseUtils:
android create project -t 2 -p C:\prj\dev\UseUtils -a UseUtils -k ca.tutortutor.useutils
This command creates a UseUtils project directory whose src subdirectory contains a ca subdirectory, which contains a tutortutor subdirectory, which contains a useutils subdirectory. Furthermore, useutils contains a skeletal UseUtils.java source file. Replace this file's contents with Listing 1.
Listing 1Toasting startup by presenting a randomly generated integer.
package ca.tutortutor.useutils; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; import ca.tutortutor.utils.Utils; public class UseUtils extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Toast.makeText(this, "Random integer [0, 255]: "+Utils.rnd(256), Toast.LENGTH_LONG).show(); } }
Listing 1 declares a UseUtils activity class that imports a Utils class from package ca.tutortutor.utils. UseUtils calls this other class's rnd(int) class method to return a randomly generated integer ranging from 0 through 255. This integer is included in a message that's presented to the user via a toast (a message that's displayed for a short time).
Listing 2 presents the contents of a Utils.java source file that declares the Utils class.
Listing 2Utils consists of simple utility methods.
package ca.tutortutor.utils; public class Utils { public static int rnd(int limit) { // Return random integer between 0 and limit (exclusive). return (int) (Math.random()*limit); } }
Listing 2's Utils class is a placeholder for various simple utility methods, such as the int rnd(int limit) method that returns a randomly generated integer between 0 (inclusive) and limit (exclusive). Presumably, other utility classes offering more complex utilities could be added to the ca.tutortutor.utils package in the future.
Complete the following steps to create a utils.jar file:
- Create a ca\tutortutor\utils directory hierarchy under the current directory.
- Copy a Utils.java source file containing Listing 2 into utils.
- From the current directory, execute javac ca/tutortutor/utils/Utils.java to compile this file.
- From the current directory, execute jar cf utils.jar ca/tutortutor/utils/*.class to create utils.jar.
Copy utils.jar to the project's libs directory. Then, with UseUtils as the current directory, execute ant debug to build the APK.
Switch to the bin subdirectory, and you should observe a UseUtils-debug.apk file among other files. Assuming that an emulated device for Android 4.1 is running, execute the following command to install this APK file (which is really a ZIP file with an .apk file extension) onto this emulated device:
adb install UseUtils-debug.apk
Switch to the device's app launcher screen and locate the UseUtils icon. Click this icon and you should observe Figure 1's error message.
Figure 1 Android has forced UseUtils to close.
Why has Android closed the app? The following excerpt from the output generated while running Ant's build.xml file hints at the answer:
[dx] trouble processing: [dx] bad class file magic (cafebabe) or version (0033.0000) [dx] ...while parsing ca/tutortutor/utils/Utils.class [dx] ...while processing ca/tutortutor/utils/Utils.class [dx] 1 warning
Android's dx tool, which converts Java classfiles to their Android equivalent, silently excludes classes that target Java 7 (their classfiles have a major version number of 51 and a minor version number of 0) from the generated classes.dex file. This omission results in a thrown error, which can be viewed by executing adb logcat, whose output appears in Figure 2.
Figure 2 The close is caused by a thrown error.
The Dalvik virtual machine equivalent of Utils.class was not stored in classes.dex, and an attempt to reference this class (at startup) has resulted in a thrown instance of the Dalvik equivalent of the java.lang.NoClassDefFoundError class. You can further prove this cause to yourself by executing the following command from within the bin directory:
dexdump classes.dex
The classes.dex file contains Android's Dalvik equivalent of the project's compiled Java classfiles. The dexdump tool processes this file and generates a disassembly. Search this disassembly and you'll not find a reference to the Utils class, because this JDK 7-created class is not present.
To fix this problem, include the -source and -target options when compiling the source code via javac, as follows:
javac -source 1.5 -target 1.5 ca/tutortutor/utils/Utils.java javac -source 1.6 -target 1.6 ca/tutortutor/utils/Utils.java
Execute either of these commands, and a Java 5- or Java 6-compatible classfile is generated. Furthermore, the dx tool will not exclude the Utils class from classes.dex. For proof, rebuild the project, reinstall UseUtils-debug.apk, and run the app. You should see a screen similar to that shown in Figure 3, with the toast near the bottom.
Figure 3 The Hello World, UseUtils message results from the default res\layout\main.xml file.