Skip to content

Android application backwards compatibility

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

{ 10 } Comments

  1. Daniel | 2010-06-06 at 18:01 | Permalink

    Thank you! Had been trying different solutions for over an hour when I found this.

  2. Alex | 2010-06-23 at 10:15 | Permalink

    Very nice trick. The advantage of using java reflection is that one can then make an ant-build target which compiles against the old SDK-version (the version listed in minSdk), and if it compiles against the minSDK version, well then at least we probably havn’t accidentily started using new non-minSDK methods since our last release.

  3. Artem | 2010-06-23 at 14:27 | Permalink

    This is exactly workaround that I googled for!

  4. Guy Siverson | 2010-08-03 at 14:47 | Permalink

    “I am an Android application developer. ”

    I just had too. LOL – After all, you started this great post with that request.

    The truth is I’m a mere user of what other people develop. From the user side of the coin I don’t like it when developers don’t make items backward compatible. What reasons are there for not making applications backward compatible?

    I’m admittedly on the other side of the coin on this one, but certainly curious none the less.

  5. Pedro | 2010-09-22 at 19:47 | Permalink

    “Simply referencing the class name somewhere in the code does not count as class use, and does not produce a class verification.”

    Unfortunately, this doesn’t appear to be the case with 1.6 (see error below). I have a program that conditionally uses TrafficStats, which is only available on 2.2. I test BUILD.VERSION.SDK before referencing TrafficStats, but this causes the program to crash running on 1.6 when the class that contains the conditional reference to TrafficStats is loaded. Strangely, when running on 2.1, it behaves as expected.

    E/dalvikvm( 1980): Could not find class ‘android.net.TrafficStats’, referenced f
    rom method com.iPass.MobileConnect.Wlan.WiFiManager.logTrafficStats
    W/dalvikvm( 1980): VFY: unable to resolve new-instance 45 (Landroid/net/TrafficS
    tats;) in Lcom/iPass/MobileConnect/Wlan/WiFiManager;
    W/dalvikvm( 1980): VFY: rejecting opcode 0×22 at 0x000a
    W/dalvikvm( 1980): VFY: rejected Lcom/iPass/MobileConnect/Wlan/WiFiManager;.log
    TrafficStats ()V
    W/dalvikvm( 1980): Verifier rejected class Lcom/iPass/MobileConnect/Wlan/WiFiMan
    ager;

  6. Mitja | 2010-12-07 at 09:01 | Permalink

    Thank you for the article. Was very helpful! Now my app works also on 1.6 :-)

  7. Nona Mills | 2010-12-22 at 17:20 | Permalink

    “Simply referencing the class name somewhere in the code does not count as class use, and does not produce a class verification.” Unfortunately, this doesn’t appear to be the case with 1.6 (see error below). I have a program that conditionally uses TrafficStats, which is only available on 2.2. I test BUILD.VERSION.SDK before referencing TrafficStats, but this causes the program to crash running on 1.6 when the class that contains the conditional reference to TrafficStats is loaded. Strangely, when running on 2.1, it behaves as expected. E/dalvikvm( 1980): Could not find class ‘android.net.TrafficStats’, referenced f rom method com.iPass.MobileConnect.Wlan.WiFiManager.logTrafficStats W/dalvikvm( 1980): VFY: unable to resolve new-instance 45 (Landroid/net/TrafficS tats;) in Lcom/iPass/MobileConnect/Wlan/WiFiManager; W/dalvikvm( 1980): VFY: rejecting opcode 0×22 at 0x000a W/dalvikvm( 1980): VFY: rejected Lcom/iPass/MobileConnect/Wlan/WiFiManager;.log TrafficStats ()V W/dalvikvm( 1980): Verifier rejected class Lcom/iPass/MobileConnect/Wlan/WiFiMan ager;

  8. Edwin Evans | 2011-01-03 at 00:28 | Permalink

    Thanks!! Just what I needed, now I’ll release w/multi-touch for Bubbles Deluxe :)

  9. Martin | 2011-05-06 at 10:01 | Permalink

    Thanks!! Very useful, you just saved me from stripping out loads of functionality from an app I am working on that has to support all the way back to 1.1, works nicely :)

  10. Ondrej Cermak | 2011-05-11 at 19:16 | Permalink

    Thanks for that wrap class trick. I was stuck with reflection which is too slow for what I need to do…

Post a Comment

Your email is never published nor shared. Required fields are marked *