• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &mdash; 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 &mdash; perhaps you
27need to record video using an API introduced in Android 1.5 (API Level 3)
28&mdash; you should add a <a
29href="{@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
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>  &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
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>