1page.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 17in geographies around the world. Across those devices, a range of Android 18platform versions are in use, some running the latest version of the platform, 19others running older versions. As a developer, you need to consider the approach 20to backward compatibility that you will take in your application — do you 21want to allow your application to run on all devices, or just those running the 22latest software? In some cases it will be useful to employ the newer APIs on 23devices 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 — perhaps you 27need to record video using an API introduced in Android 1.5 (API Level 3) 28— you should add a <a 29href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code><android:minSdkVersion></code></a> 30 to the application's manifest, to ensure your app won't 31be installed on older devices. For example, if your application depends on an 32API introduced in API Level 3, you would specify "3" as the value of the minimum 33SDK version</a>:</p> 34 35<pre> <manifest> 36 ... 37 <uses-sdk android:minSdkVersion="3" /> 38 ... 39 </manifest></pre> 40 41<p>However, if you want to add a useful but non-essential feature, such as 42popping up an on-screen keyboard even when a hardware keyboard is available, you 43can write your program in a way that allows it to use the newer features without 44failing 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 49android.os.Debug#dumpHprofData(java.lang.String) 50android.os.Debug.dumpHprofData(String filename)}. The {@link android.os.Debug} 51class has existed since Android 1.0, but the method is new in Anroid 1.5 (API 52Level 3). If you try to call it directly, your app will fail to run on devices 53running Android 1.1 or earlier.</p> 54 55<p>The simplest way to call the method is through reflection. This requires 56doing a one-time lookup and caching the result in a <code>Method</code> object. 57Using the method is a matter of calling <code>Method.invoke</code> and un-boxing 58the 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>, 114which does the method lookup. If that succeeds, it uses a private 115method with the same semantics as the original (arguments, return 116value, checked exceptions) to do the call. The return value (if it had 117one) and exception are unpacked and returned in a way that mimics the 118original. The <code>fiddle</code> method demonstrates how the 119application logic would choose to call the new API or do something 120different 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 123private <code>Method</code> field, field initializer, and call wrapper to the 124class.</p> 125 126<p>This approach becomes a bit more complex when the method is declared in a 127previously undefined class. It's also much slower to call 128<code>Method.invoke()</code> than it is to call the method directly. These 129issues 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 134or existing class. Each method in the wrapper class just calls through to the 135corresponding 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 138by calling the class directly, with a small amount of overhead from the 139additional method call. If the target class or method doesn't exist, the 140initialization of the wrapper class fails, and your application knows that it 141should 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 192static initializer that tests for the presence of the new class. If the new 193class isn't available, initialization of <code>WrapNewClass</code> fails, 194ensuring that the wrapper class can't be used inadvertently. The 195<code>checkAvailable</code> method is used as a simple way to force class 196initialization. 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 223part of the system. If it fails, we know the class isn't there, and adjust our 224expectations accordingly. It should be noted that the call to 225<code>checkAvailable</code> will fail before it even starts if the bytecode 226verifier decides that it doesn't want to accept a class that has references to a 227nonexistent class. The way this code is structured, the end result is the same 228whether 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 232put the new methods in the wrapper class. Invoke the old methods directly. The 233static initializer in <code>WrapNewClass</code> would be augmented to do a 234one-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 239is expected to support it. By definition, the behavior of your application will 240be different on each. Remember the mantra: if you haven't tried it, it doesn't 241work.</p> 242 243<p>You can test for backward compatibility by running your application in an 244emulator that uses an older version of the platform. The Android SDK allows you 245to do this easily by creating "Android Virtual Devices" with different API 246levels. Once you create the AVDs, you can test your application with old and new 247versions of the system, perhaps running them side-by-side to see the 248differences. More information about emulator AVDs can be found <a 249href="{@docRoot}guide/developing/tools/avd.html">in the AVD documentation</a> and 250from <code>emulator -help-virtual-device</code>.</p>