diff options
Diffstat (limited to 'docs/html/resources/articles/backward-compatibility.jd')
-rw-r--r-- | docs/html/resources/articles/backward-compatibility.jd | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/docs/html/resources/articles/backward-compatibility.jd b/docs/html/resources/articles/backward-compatibility.jd new file mode 100644 index 0000000..9f0ddf5 --- /dev/null +++ b/docs/html/resources/articles/backward-compatibility.jd @@ -0,0 +1,238 @@ +page.title=Backward Compatibility for Applications +@jd:body + +<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>
\ No newline at end of file |