3

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.

cottonBallPaws
  • 21,220
  • 37
  • 123
  • 171
  • "This works as perfectly" -- not on Android 1.x. With respect to your interface problem, what version of Android are you trying to run this code on? – CommonsWare Oct 27 '12 at 20:30
  • (The code above is not the code I'm running, just example of the method). I am seeing this problem on a 2.3 device. The class implements android.animation.Animator.AnimatorListener which is not available until 3.2. I haven't tried other versions beyond 2.3 yet – cottonBallPaws Oct 27 '12 at 20:35
  • Very strange. If nobody comes up with a solution or explanation, I'll run some experiments of my own. That will be delayed a bit due to some travel (and perhaps a wee bit of a hurricane), but I'll try to get to it before too terribly long. If you have a sample project that can demonstrate the phenomenon, post the source somewhere and I'll take a look at it. – CommonsWare Oct 27 '12 at 20:38
  • I'll put together some example code. – cottonBallPaws Oct 27 '12 at 20:42
  • I added some code above and verified that it too reproduces the issue. Since it doesn't actually crash and still works correctly, this is more of a curiosity then a deep concern. Good luck with the storm! – cottonBallPaws Oct 27 '12 at 21:00

1 Answers1

4

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.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491