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 — 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 — perhaps you 27 need to record video using an API introduced in Android 1.5 (API Level 3) 28 — you should add a <a 29 href="{@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 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> <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 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>