summaryrefslogtreecommitdiffstats
path: root/docs/html/resources/articles/backward-compatibility.jd
diff options
context:
space:
mode:
Diffstat (limited to 'docs/html/resources/articles/backward-compatibility.jd')
-rw-r--r--docs/html/resources/articles/backward-compatibility.jd238
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 &mdash; 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 &mdash; perhaps you
+need to record video using an API introduced in Android 1.5 (API Level 3)
+&mdash; you should add a <a
+href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code>&lt;android:minSdkVersion&gt;</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> &lt;manifest&gt;
+ ...
+ &lt;uses-sdk android:minSdkVersion="3" /&gt;
+ ...
+ &lt;/manifest&gt;</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