250 lines
9.3 KiB
Plaintext
250 lines
9.3 KiB
Plaintext
page.title=Backward Compatibility for Applications
|
|
@jd:body
|
|
|
|
|
|
<div id="qv-wrapper">
|
|
<div id="qv">
|
|
|
|
<h2>See also</h2>
|
|
<ol>
|
|
<li><a href="{@docRoot}guide/appendix/api-levels.html">Android API Levels</a></li>
|
|
</ol>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<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 — 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 — perhaps you
|
|
need to record video using an API introduced in Android 1.5 (API Level 3)
|
|
— you should add a <a
|
|
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><code><android:minSdkVersion></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> <manifest>
|
|
...
|
|
<uses-sdk android:minSdkVersion="3" />
|
|
...
|
|
</manifest></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> |