Home | History | Annotate | Download | only in articles
      1 page.title=Backward Compatibility for Applications
      2 @jd:body
      3 
      4 
      5 <div id="qv-wrapper">
      6 <div id="qv">
      7 
      8   <h2>See also</h2>
      9   <ol>
     10     <li><a href="{@docRoot}guide/appendix/api-levels.html">Android API Levels</a></li>
     11   </ol>
     12 
     13 </div>
     14 </div>
     15 
     16 <p>A variety of Android-powered devices are now available to consumers from carriers
     17 in geographies around the world. Across those devices, a range of Android
     18 platform versions are in use, some running the latest version of the platform,
     19 others running older versions. As a developer, you need to consider the approach
     20 to backward compatibility that you will take in your application &mdash; do you
     21 want to allow your application to run on all devices, or just those running the
     22 latest software? In some cases it will be useful to employ the newer APIs on
     23 devices that support them, while continuing to support older devices. </p>
     24 
     25 <h3>Setting the minSdkVersion</h3>
     26 <p>If the use of a new API is integral to the application &mdash; perhaps you
     27 need to record video using an API introduced in Android 1.5 (API Level 3)
     28 &mdash; you should add a <a
     29 href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code>&lt;android:minSdkVersion&gt;</code></a>
     30  to the application's manifest, to ensure your app won't
     31 be installed on older devices. For example, if your application depends on an
     32 API introduced in API Level 3, you would specify "3" as the value of the minimum
     33 SDK version</a>:</p>
     34 
     35 <pre>  &lt;manifest&gt;
     36    ...
     37    &lt;uses-sdk android:minSdkVersion="3" /&gt;
     38    ...
     39   &lt;/manifest&gt;</pre>
     40 
     41 <p>However, if you want to add a useful but non-essential feature, such as
     42 popping up an on-screen keyboard even when a hardware keyboard is available, you
     43 can write your program in a way that allows it to use the newer features without
     44 failing on older devices.</p>
     45 
     46 <h3>Using reflection</h3>
     47 
     48 <p>Suppose there's a simple new call you want to use, like {@link
     49 android.os.Debug#dumpHprofData(java.lang.String)
     50 android.os.Debug.dumpHprofData(String filename)}.  The {@link android.os.Debug}
     51 class has existed since Android 1.0, but the method is new in Anroid 1.5 (API
     52 Level 3). If you try to call it directly, your app will fail to run on devices
     53 running Android 1.1 or earlier.</p>
     54 
     55 <p>The simplest way to call the method is through reflection.  This requires
     56 doing a one-time lookup and caching the result in a <code>Method</code> object. 
     57 Using the method is a matter of calling <code>Method.invoke</code> and un-boxing
     58 the result. Consider the following:</p>
     59 
     60 <pre>public class Reflect {
     61    private static Method mDebug_dumpHprofData;
     62 
     63    static {
     64        initCompatibility();
     65    };
     66 
     67    private static void initCompatibility() {
     68        try {
     69            mDebug_dumpHprofData = Debug.class.getMethod(
     70                    "dumpHprofData", new Class[] { String.class } );
     71            /* success, this is a newer device */
     72        } catch (NoSuchMethodException nsme) {
     73            /* failure, must be older device */
     74        }
     75    }
     76 
     77    private static void dumpHprofData(String fileName) throws IOException {
     78        try {
     79            mDebug_dumpHprofData.invoke(null, fileName);
     80        } catch (InvocationTargetException ite) {
     81            /* unpack original exception when possible */
     82            Throwable cause = ite.getCause();
     83            if (cause instanceof IOException) {
     84                throw (IOException) cause;
     85            } else if (cause instanceof RuntimeException) {
     86                throw (RuntimeException) cause;
     87            } else if (cause instanceof Error) {
     88                throw (Error) cause;
     89            } else {
     90                /* unexpected checked exception; wrap and re-throw */
     91                throw new RuntimeException(ite);
     92            }
     93        } catch (IllegalAccessException ie) {
     94            System.err.println("unexpected " + ie);
     95        }
     96    }
     97 
     98    public void fiddle() {
     99        if (mDebug_dumpHprofData != null) {
    100            /* feature is supported */
    101            try {
    102                dumpHprofData("/sdcard/dump.hprof");
    103            } catch (IOException ie) {
    104                System.err.println("dump failed!");
    105            }
    106        } else {
    107            /* feature not supported, do something else */
    108            System.out.println("dump not supported");
    109        }
    110    }
    111 }</pre>
    112 
    113 <p>This uses a static initializer to call <code>initCompatibility</code>,
    114 which does the method lookup. If that succeeds, it uses a private
    115 method with the same semantics as the original (arguments, return
    116 value, checked exceptions) to do the call. The return value (if it had
    117 one) and exception are unpacked and returned in a way that mimics the
    118 original. The <code>fiddle</code> method demonstrates how the
    119 application logic would choose to call the new API or do something
    120 different based on the presence of the new method.</p>
    121 
    122 <p>For each additional method you want to call, you would add an additional
    123 private <code>Method</code> field, field initializer, and call wrapper to the
    124 class.</p>
    125 
    126 <p>This approach becomes a bit more complex when the method is declared in a
    127 previously undefined class. It's also much slower to call
    128 <code>Method.invoke()</code> than it is to call the method directly. These
    129 issues can be mitigated by using a wrapper class.</p>
    130 
    131 <h3>Using a wrapper class</h3>
    132 
    133 <p>The idea is to create a class that wraps all of the new APIs exposed by a new
    134 or existing class. Each method in the wrapper class just calls through to the
    135 corresponding real method and returns the same result.</p>
    136 
    137 <p>If the target class and method exist, you get the same behavior you would get
    138 by calling the class directly, with a small amount of overhead from the
    139 additional method call. If the target class or method doesn't exist, the
    140 initialization of the wrapper class fails, and your application knows that it
    141 should avoid using the newer calls.</p>
    142 
    143 <p>Suppose this new class were added:</p><pre>public class NewClass {
    144    private static int mDiv = 1;
    145 
    146    private int mMult;
    147 
    148    public static void setGlobalDiv(int div) {
    149        mDiv = div;
    150    }
    151 
    152    public NewClass(int mult) {
    153        mMult = mult;
    154    }
    155 
    156    public int doStuff(int val) {
    157        return (val * mMult) / mDiv;
    158    }
    159 }</pre>
    160 
    161 <p>We would create a wrapper class for it:</p>
    162 
    163 <pre>class WrapNewClass {
    164    private NewClass mInstance;
    165 
    166    /* class initialization fails when this throws an exception */
    167    static {
    168        try {
    169            Class.forName("NewClass");
    170        } catch (Exception ex) {
    171            throw new RuntimeException(ex);
    172        }
    173    }
    174 
    175    /* calling here forces class initialization */
    176    public static void checkAvailable() {}
    177 
    178    public static void setGlobalDiv(int div) {
    179        NewClass.setGlobalDiv(div);
    180    }
    181 
    182    public WrapNewClass(int mult) {
    183        mInstance = new NewClass(mult);
    184    }
    185 
    186    public int doStuff(int val) {
    187        return mInstance.doStuff(val);
    188    }
    189 }</pre>
    190 
    191 <p>This has one method for each constructor and method in the original, plus a
    192 static initializer that tests for the presence of the new class. If the new
    193 class isn't available, initialization of <code>WrapNewClass</code> fails,
    194 ensuring that the wrapper class can't be used inadvertently.  The
    195 <code>checkAvailable</code> method is used as a simple way to force class
    196 initialization.  We use it like this:</p>
    197 
    198 <pre>public class MyApp {
    199    private static boolean mNewClassAvailable;
    200 
    201    /* establish whether the "new" class is available to us */
    202    static {
    203        try {
    204            WrapNewClass.checkAvailable();
    205            mNewClassAvailable = true;
    206        } catch (Throwable t) {
    207            mNewClassAvailable = false;
    208        }
    209    }
    210 
    211    public void diddle() {
    212        if (mNewClassAvailable) {
    213            WrapNewClass.setGlobalDiv(4);
    214            WrapNewClass wnc = new WrapNewClass(40);
    215            System.out.println("newer API is available - " + wnc.doStuff(10));
    216        } else {
    217            System.out.println("newer API not available");
    218        }
    219    }
    220 }</pre>
    221 
    222 <p>If the call to <code>checkAvailable</code> succeeds, we know the new class is
    223 part of the system. If it fails, we know the class isn't there, and adjust our
    224 expectations accordingly. It should be noted that the call to
    225 <code>checkAvailable</code> will fail before it even starts if the bytecode
    226 verifier decides that it doesn't want to accept a class that has references to a
    227 nonexistent class. The way this code is structured, the end result is the same
    228 whether the exception comes from the verifier or from the call to
    229 <code>Class.forName</code>.</p>
    230 
    231 <p>When wrapping an existing class that now has new methods, you only need to
    232 put the new methods in the wrapper class. Invoke the old methods directly. The
    233 static initializer in <code>WrapNewClass</code> would be augmented to do a
    234 one-time check with reflection.</p>
    235 
    236 <h3>Testing is key</h3>
    237 
    238 <p>You must test your application on every version of the Android framework that
    239 is expected to support it. By definition, the behavior of your application will
    240 be different on each. Remember the mantra: if you haven't tried it, it doesn't
    241 work.</p>
    242 
    243 <p>You can test for backward compatibility by running your application in an
    244 emulator that uses an older version of the platform. The Android SDK allows you
    245 to do this easily by creating "Android Virtual Devices" with different API
    246 levels. Once you create the AVDs, you can test your application with old and new
    247 versions of the system, perhaps running them side-by-side to see the
    248 differences. More information about emulator AVDs can be found <a
    249 href="{@docRoot}guide/developing/tools/avd.html">in the AVD documentation</a> and
    250 from <code>emulator -help-virtual-device</code>.</p>