Say you are an Android application developer. You have a last-generation Nexus or Droid phone, and you use for development a recent Android SDK version 2.1. Yet more than a half of your potential users have older phones with older versions of Android: 1.5 (Cupcake) or 1.6 (Donut).
You can simply ignore the not-up-to-date user base, and write your application exclusively for Android 2.0 (Eclair) or more. For this you set minSdkVersion=5 in your AndroidManifest, and you’re done. This is the simplest solution from the developer point of view, but also the worst solution from the user point of view (since half the users don’t have access to the application at all).
Or you can think about making your application backwards compatible, so that it can still be used on Cupcake and Donut. How to do this?
First, you need to look carefully at the API version for the SDK classes and methods that you use. This is indicated in the Android SDK documentation, on the right. For example looking at MotionEvent.getPointerCount() you read Since API level 5, which means that this method is available starting with Eclair — it is not available in Cupcake or Donut, although the class MotionEvent itself is available.
If you mark your application minSdkVersion=3 (Cupcake) but still use the method MotionEvent.getPointerCount(), the application will crash at runtime with a Dalvik VerificationError exception, because the method is not found. Note that you’ll only see this error at runtime and not earlier — not at compile time, and not at Market upload time. So you really have to test your application on Cupcake and Donut in order to catch these crashes. If you don’t test, users with Cupcake will download and try out the application, which will crash on them, and they’ll give you 1-star rating in exchange. Sometimes a helpful user may even drop you an email informing that your app is having a FC (forced close) on G1 with Android 1.5.
Let’s say your application wants to use multiple-touch where available, otherwise the application is still usable with single touch. For multi-touch you use, among others, the method MotionEvent.getPointerCount(). Multiple finger support was introduced in 2.0, Eclair.
You test on Cupcake and you realize that MotionEvent.getPointerCount() does not exist in Cupcake, although MotionEvent with most of the other methods is available. What to do to take advantage of getPointerCount() and multi-touch on Eclair, while not crashing on Cupcake? There is a trick related to how Dalvik loads the Java classes.
Dalvik uses a delayed class loading. The class is only verified when it is used for the first time in the application, i.e. when one of its methods or members is called/accessed. Simply referencing the class name somewhere in the code does not count as class use, and does not produce a class verification.
The second element that we use is android.os.Build.VERSION.SDK_INT. This is an integer giving the OS version installed on the phone (the same as minSdkVersion, 3 for Cupcake, 4 for Donut, 5 Eclair). We use this information to know which of the recent APIs are available and to adapt the code behavior on different OS versions depending on the available APIs.
And now you hit the API joke. By testing on Cupcake, you realize that the app crashes when accessing Build.VERSION.SDK_INT! You double-check the documentation, and indeed SDK_INT is not available on Cupcake, it was introduced in Donut. In other words, the mechanism for version-checking itself is not available on older versions, nice eh?
The workaround is to use Build.VERSION.SDK, which is a String containing the same integer as SDK_INT (e.g. “3″ on Cupcake), and this is available on Cupcake. You wonder why some developer thought that using a String for storing an integer is a good API choice… an API mistake, in other words.
Anyway, let’s go back to the code.
class WrapNew {
static int getPointerCount(MotionEvent event) {
return event.getPointerCount();
}
}
You create a class that wraps the calls to the new Eclair API. Instead of calling the MotionEvent.getPointerCount() directly, you call through the wrapper class. You only call the new method after you verified that the Build.VERSION.SDK is hight enough that the new API exists.
public boolean onTouchEvent(MotionEvent event) {
boolean hasMultiTouch = Integer.parseInt(Build.VERSION.SDK) >= 5;
int nPointer = hasMultiTouch ? WrapNew.getPointerCount(event) : 1;
// ...
}
The Dalvik trick is: as long as we don’t call WrapNew.getPointerCount(), the WrapNew class is not loaded by Dalvik and does not generate a VerificationError by referencing the non-existing API MotionEvent.getPointerCount().
You test again the application on Cupcake, and it does not crash anymore with VerificationError! It also supports multi-touch on Eclair, and the solution is pretty clean and simple code.
One more note: if you use Proguard to optimize your Android application, be sure to mark the wrapper class as non-inlinable, because otherwise Proguard would optimize it out of existence, denying it’s purpose.
-keep ,allowobfuscation class package.WrapNew { *; }
You can see all this in practice, real-life working code, in the Arity Calculator application, which is Open Source so you can freely explore the code. Arity Calculator is available on Market on Android phones, for Cupcake, Donut, Eclair and on.
And here is the missing table from the Android documentation:
| Codename | Version | API level |
|---|---|---|
| Cupcake | 1.5 | 3 |
| Donut | 1.6 | 4 |
| Eclair | 2.0 | 5 |
| Eclair update | 2.0.1 | 6 |
| Eclair MR1 | 2.1 | 7 |
