| page.title=Backward Compatibility for Applications |
| @jd:body |
| |
| |
| <div id="qv-wrapper"> |
| <div id="qv"> |
| |
| <h2>See also</h2> |
| <ol> |
| <li><a href="{@docRoot}guide/appendix/api-levels.html">Android API Levels</a></li> |
| </ol> |
| |
| </div> |
| </div> |
| |
| <p>A variety of Android-powered devices are now available to consumers from carriers |
| in geographies around the world. Across those devices, a range of Android |
| platform versions are in use, some running the latest version of the platform, |
| others running older versions. As a developer, you need to consider the approach |
| to backward compatibility that you will take in your application — do you |
| want to allow your application to run on all devices, or just those running the |
| latest software? In some cases it will be useful to employ the newer APIs on |
| devices that support them, while continuing to support older devices. </p> |
| |
| <h3>Setting the minSdkVersion</h3> |
| <p>If the use of a new API is integral to the application — perhaps you |
| need to record video using an API introduced in Android 1.5 (API Level 3) |
| — you should add a <a |
| href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code><android:minSdkVersion></code></a> |
| to the application's manifest, to ensure your app won't |
| be installed on older devices. For example, if your application depends on an |
| API introduced in API Level 3, you would specify "3" as the value of the minimum |
| SDK version</a>:</p> |
| |
| <pre> <manifest> |
| ... |
| <uses-sdk android:minSdkVersion="3" /> |
| ... |
| </manifest></pre> |
| |
| <p>However, if you want to add a useful but non-essential feature, such as |
| popping up an on-screen keyboard even when a hardware keyboard is available, you |
| can write your program in a way that allows it to use the newer features without |
| failing on older devices.</p> |
| |
| <h3>Using reflection</h3> |
| |
| <p>Suppose there's a simple new call you want to use, like {@link |
| android.os.Debug#dumpHprofData(java.lang.String) |
| android.os.Debug.dumpHprofData(String filename)}. The {@link android.os.Debug} |
| class has existed since Android 1.0, but the method is new in Anroid 1.5 (API |
| Level 3). If you try to call it directly, your app will fail to run on devices |
| running Android 1.1 or earlier.</p> |
| |
| <p>The simplest way to call the method is through reflection. This requires |
| doing a one-time lookup and caching the result in a <code>Method</code> object. |
| Using the method is a matter of calling <code>Method.invoke</code> and un-boxing |
| the result. Consider the following:</p> |
| |
| <pre>public class Reflect { |
| private static Method mDebug_dumpHprofData; |
| |
| static { |
| initCompatibility(); |
| }; |
| |
| private static void initCompatibility() { |
| try { |
| mDebug_dumpHprofData = Debug.class.getMethod( |
| "dumpHprofData", new Class[] { String.class } ); |
| /* success, this is a newer device */ |
| } catch (NoSuchMethodException nsme) { |
| /* failure, must be older device */ |
| } |
| } |
| |
| private static void dumpHprofData(String fileName) throws IOException { |
| try { |
| mDebug_dumpHprofData.invoke(null, fileName); |
| } catch (InvocationTargetException ite) { |
| /* unpack original exception when possible */ |
| Throwable cause = ite.getCause(); |
| if (cause instanceof IOException) { |
| throw (IOException) cause; |
| } else if (cause instanceof RuntimeException) { |
| throw (RuntimeException) cause; |
| } else if (cause instanceof Error) { |
| throw (Error) cause; |
| } else { |
| /* unexpected checked exception; wrap and re-throw */ |
| throw new RuntimeException(ite); |
| } |
| } catch (IllegalAccessException ie) { |
| System.err.println("unexpected " + ie); |
| } |
| } |
| |
| public void fiddle() { |
| if (mDebug_dumpHprofData != null) { |
| /* feature is supported */ |
| try { |
| dumpHprofData("/sdcard/dump.hprof"); |
| } catch (IOException ie) { |
| System.err.println("dump failed!"); |
| } |
| } else { |
| /* feature not supported, do something else */ |
| System.out.println("dump not supported"); |
| } |
| } |
| }</pre> |
| |
| <p>This uses a static initializer to call <code>initCompatibility</code>, |
| which does the method lookup. If that succeeds, it uses a private |
| method with the same semantics as the original (arguments, return |
| value, checked exceptions) to do the call. The return value (if it had |
| one) and exception are unpacked and returned in a way that mimics the |
| original. The <code>fiddle</code> method demonstrates how the |
| application logic would choose to call the new API or do something |
| different based on the presence of the new method.</p> |
| |
| <p>For each additional method you want to call, you would add an additional |
| private <code>Method</code> field, field initializer, and call wrapper to the |
| class.</p> |
| |
| <p>This approach becomes a bit more complex when the method is declared in a |
| previously undefined class. It's also much slower to call |
| <code>Method.invoke()</code> than it is to call the method directly. These |
| issues can be mitigated by using a wrapper class.</p> |
| |
| <h3>Using a wrapper class</h3> |
| |
| <p>The idea is to create a class that wraps all of the new APIs exposed by a new |
| or existing class. Each method in the wrapper class just calls through to the |
| corresponding real method and returns the same result.</p> |
| |
| <p>If the target class and method exist, you get the same behavior you would get |
| by calling the class directly, with a small amount of overhead from the |
| additional method call. If the target class or method doesn't exist, the |
| initialization of the wrapper class fails, and your application knows that it |
| should avoid using the newer calls.</p> |
| |
| <p>Suppose this new class were added:</p><pre>public class NewClass { |
| private static int mDiv = 1; |
| |
| private int mMult; |
| |
| public static void setGlobalDiv(int div) { |
| mDiv = div; |
| } |
| |
| public NewClass(int mult) { |
| mMult = mult; |
| } |
| |
| public int doStuff(int val) { |
| return (val * mMult) / mDiv; |
| } |
| }</pre> |
| |
| <p>We would create a wrapper class for it:</p> |
| |
| <pre>class WrapNewClass { |
| private NewClass mInstance; |
| |
| /* class initialization fails when this throws an exception */ |
| static { |
| try { |
| Class.forName("NewClass"); |
| } catch (Exception ex) { |
| throw new RuntimeException(ex); |
| } |
| } |
| |
| /* calling here forces class initialization */ |
| public static void checkAvailable() {} |
| |
| public static void setGlobalDiv(int div) { |
| NewClass.setGlobalDiv(div); |
| } |
| |
| public WrapNewClass(int mult) { |
| mInstance = new NewClass(mult); |
| } |
| |
| public int doStuff(int val) { |
| return mInstance.doStuff(val); |
| } |
| }</pre> |
| |
| <p>This has one method for each constructor and method in the original, plus a |
| static initializer that tests for the presence of the new class. If the new |
| class isn't available, initialization of <code>WrapNewClass</code> fails, |
| ensuring that the wrapper class can't be used inadvertently. The |
| <code>checkAvailable</code> method is used as a simple way to force class |
| initialization. We use it like this:</p> |
| |
| <pre>public class MyApp { |
| private static boolean mNewClassAvailable; |
| |
| /* establish whether the "new" class is available to us */ |
| static { |
| try { |
| WrapNewClass.checkAvailable(); |
| mNewClassAvailable = true; |
| } catch (Throwable t) { |
| mNewClassAvailable = false; |
| } |
| } |
| |
| public void diddle() { |
| if (mNewClassAvailable) { |
| WrapNewClass.setGlobalDiv(4); |
| WrapNewClass wnc = new WrapNewClass(40); |
| System.out.println("newer API is available - " + wnc.doStuff(10)); |
| } else { |
| System.out.println("newer API not available"); |
| } |
| } |
| }</pre> |
| |
| <p>If the call to <code>checkAvailable</code> succeeds, we know the new class is |
| part of the system. If it fails, we know the class isn't there, and adjust our |
| expectations accordingly. It should be noted that the call to |
| <code>checkAvailable</code> will fail before it even starts if the bytecode |
| verifier decides that it doesn't want to accept a class that has references to a |
| nonexistent class. The way this code is structured, the end result is the same |
| whether the exception comes from the verifier or from the call to |
| <code>Class.forName</code>.</p> |
| |
| <p>When wrapping an existing class that now has new methods, you only need to |
| put the new methods in the wrapper class. Invoke the old methods directly. The |
| static initializer in <code>WrapNewClass</code> would be augmented to do a |
| one-time check with reflection.</p> |
| |
| <h3>Testing is key</h3> |
| |
| <p>You must test your application on every version of the Android framework that |
| is expected to support it. By definition, the behavior of your application will |
| be different on each. Remember the mantra: if you haven't tried it, it doesn't |
| work.</p> |
| |
| <p>You can test for backward compatibility by running your application in an |
| emulator that uses an older version of the platform. The Android SDK allows you |
| to do this easily by creating "Android Virtual Devices" with different API |
| levels. Once you create the AVDs, you can test your application with old and new |
| versions of the system, perhaps running them side-by-side to see the |
| differences. More information about emulator AVDs can be found <a |
| href="{@docRoot}guide/developing/tools/avd.html">in the AVD documentation</a> and |
| from <code>emulator -help-virtual-device</code>.</p> |