Reputation: 21600
In order to support different Api Levels, I am using the technique described here: http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html
Here is the example from the article:
public static VersionedGestureDetector newInstance(Context context,
OnGestureListener listener) {
final int sdkVersion = Integer.parseInt(Build.VERSION.SDK);
VersionedGestureDetector detector = null;
if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
detector = new CupcakeDetector();
} else if (sdkVersion < Build.VERSION_CODES.FROYO) {
detector = new EclairDetector();
} else {
detector = new FroyoDetector(context);
}
detector.mListener = listener;
return detector;
}
This approach "takes advantage of the laziness of ClassLoaders." For devices with the newer API level (in the example's case, Froyo), it can use the Froyo class which accesses APIs in the newer version. For older devices, they receive a class that only uses the older APIs.
This works perfectly.
However, if you make FroyoDetector implement an interface, that only exists in a newer api level, when newInstance() is called, even before it runs any of the code within that method, it tries to load the interface class that FroyoDetector implements and puts an error into the logs saying that the FroyoDetector class could not be loaded.
So my question is, why does this happen? I was under the impression that with this technique that the newer class wouldn't be loaded until it was directly referenced for the first time. However if you add an interface to it, it seems to try to load it even without calling the detector = new FroyoDetector(context);
line.
Here is some code to reproduce the issue:
This is in an app targeting sdk 16 with a min of 8. Running this on a 2.3 device reproduces the issue.
Here are three classes:
public class VersionedLoader {
public static VersionedLoader newInstance() {
if (Build.VERSION.SDK_INT < 12) {
return new OldVersionLoader();
} else {
return new NewVersionLoader();
}
}
}
-
public class OldVersionLoader extends VersionedLoader {
}
-
@TargetApi(11)
public class NewVersionLoader extends VersionedLoader implements AnimatorListener {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
}
AnimatorListener is only available from 3.1 onwards.
Now if you run: Object obj = VersionedLoader.newInstance();
This error will appear in the logs:
10-27 13:51:14.437: I/dalvikvm(7673): Failed resolving Lyour/package/name/NewVersionLoader; interface 7 'Landroid/animation/Animator$AnimatorListener;'
10-27 13:51:14.437: W/dalvikvm(7673): Link of class 'Lyour/package/name/NewVersionLoader;' failed
10-27 13:51:14.445: E/dalvikvm(7673): Could not find class 'your.package.name.NewVersionLoader', referenced from method your.package.name.VersionedLoader.newInstance
10-27 13:51:14.445: W/dalvikvm(7673): VFY: unable to resolve new-instance 1327 (Lyour/package/name/NewVersionLoader;) in Lyour/package/name/VersionedLoader;
10-27 13:51:14.445: D/dalvikvm(7673): VFY: replacing opcode 0x22 at 0x000c
10-27 13:51:14.445: D/dalvikvm(7673): VFY: dead code 0x000e-0011 in Lyour/package/name/VersionedLoader;.newInstance ()Lyour/package/name/VersionedLoader;
It won't crash, and it will actually go on to work correctly.
Upvotes: 3
Views: 706
Reputation: 1006614
Yes, I can reproduce the problem. Kinda surprising, though, as you note, the fact that it does not crash means that this is more a case of Dalvik being perhaps a bit too chatty in LogCat than anything that should cause harm to an app.
One workaround is to move the interface to an inner class. In your example, instead of NewVersionLoader
implementing AnimatorListener
, an inner class in NewVersionLoader
would implement AnimationListener
:
@TargetApi(11)
public class NewVersionLoader extends VersionedLoader {
private class Foo implements AnimatorListener {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
}
}
Admittedly, this may not be ideal depending on your intended use of VersionedLoader
. However, since VersionedLoader
itself does not implement AnimationListener
, users of VersionedLoader
will not be calling AnimationListener
methods, so the fact that your logic is on an inner class rather than the actual class should not be a huge issue AFAIK.
Upvotes: 4