Try PandaPow for Android
By date:

RSS Feed

Skip this one

Backwards compatibility

2010-05-08 13:51:54

Ok, so finally time to take the bull by the horns (is that even an expression?). As I want my lovely apps full of all the latest stuff, but at the same time I don't want to deny all poor bastards with older android versions the pleasure of using said apps. Solution: eh, right. That's what I need to find out.

In fact the reason I decided to do this now is that I ran across a bug that is unique to 2.1. So I need to do one thing on 2.1 and another on all other platforms. To do this, I would check: Build.VERSION.SDK_INT which would have the value 7 on android 2.1. But this constant wasn't introduced until release 1.6. There is a similar constant in earlier versions, but since it is deprecated (and a String) I'd prefer to use newer one.

Method 1: Reflection

The "official" documentation on the how to ensure backwards compatibility says that the simplest way is by using reflection. But the example they use is really quite big and complex. In my case, the absolute minimal way of solving the problem using reflection would be the following piece of code:

int my_SDK_INT;
try {
    // works for level 4 and up
    Field SDK_INT_field = Build.VERSION.class.getField("SDK_INT");
    my_SDK_INT = (Integer) SDK_INT_field.get(null);
} catch (NoSuchFieldException e) {
    // Must be level 3 (since my app doesn't support lower levels)
    my_SDK_INT=3;
}

That is, instead of a direct reference to Build.VERSION.SDK_INT, which would cause a dreaded VerifyError exception, I use getField to check if it exists. This returns a Field object, or throws an exception if the field wasn't found. In my case, an exception would mean that SDK_INT is 3 since my Manifest contains:

<uses-sdk android:minSdkVersion="3"/>

the app won't install on prior versions. On level 4 and up, once we have our Field object, we can retrieve the value using the get) method. Since the field is static, we pass a null pointer as argument to this method. Had it been an object field, we'd have to pass in the object in question.

The same technique can be used for methods that may be non existing. E.g. the following would handle the case where a static method "someMethod" may not exist:

try {
    Method m=SomeClass.getMethod("someMethod");
    m.invoke(obj, args);
} catch (NoSuchMethodException e) {
    // Some default action
}

So that is reflection, the quick and dirty way. Needless to say, it makes sense to put all the code using reflection into a separate file and class just to make it tidy.

Method 2: Wrapper class

If we're dealing with classes or packages that may be non-existant, or if we want to avoid the overhead introduced by 'invoke' and 'get' of reflection, we can turn to the second method (which actually is a bit simpler in my opinion): using wrapper classes.

The idea is to place all references to methods, fields, and classes that may be non-existing, in separate wrapper classes. The upside to this is that in the wrapper class, we can write nice and efficient code without having to check anything. The downside is that loading the class may throw an exception. The class should implement some 'isAvailable' method that the user can call to know if it may be used. Example:

class WrapperUser {
    boolean mWrapperOK=false;
    static {
        try {
            WrapperClass.isAvailable()
            mWrapperOK=True;
        } catch (Throwable e) {
        }
    }
    ...
}

class WrapperClass {
    public void isAvailable() throws Exception {
        if (Reflect.SDK_INT<3)
          throw new Exception();
    }
}

The documentation suggests to use Class.forName() ) in the isAvailable-method. But this doesn't seem to work reliably when I've tried it.

Instead I prefer to use my_SDK_INT, that I wrapped so nicely above, in order to decide if a wrapper class is available or not. Obviously, this has its drawbacks, but at least it is reliable.

Caveats

Note that the wrapper class should not implement an interface which may not exist. Doing so seems to cause a dreaded VerifyError, even before the class is loaded, hence it is impossible to catch for a mere mortal application. The workaround is to introduce additional classes accessed only by the wrapper class, which in turn can implement the interfaces. I suspect that there are other similar issues with extending a potentially missing class.

Page 1