3

さまざまなApiレベルをサポートするために、ここで説明する手法を使用しています:http://android-developers.blogspot.com/2010/07/how-to-have-your-担甲-and-eat-it-too。 html

記事の例は次のとおりです。

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;
}

このアプローチは、「ClassLoaderの怠惰を利用します」。新しいAPIレベルのデバイス(この例ではFroyo)の場合、新しいバージョンのAPIにアクセスするFroyoクラスを使用できます。古いデバイスの場合、古いAPIのみを使用するクラスを受け取ります。

これは完全に機能します。

ただし、FroyoDetectorに新しいAPIレベルにのみ存在するインターフェイスを実装させる場合、newInstance()が呼び出されると、そのメソッド内のコードを実行する前であっても、FroyoDetectorが実装して配置するインターフェイスクラスを読み込もうとします。 FroyoDetectorクラスをロードできなかったことを示すエラーがログに記録されます。

だから私の質問は、なぜこれが起こるのですか?この手法では、新しいクラスが初めて直接参照されるまでロードされないという印象を受けました。detector = new FroyoDetector(context);ただし、インターフェイスを追加すると、回線を呼び出さなくてもロードしようとしているようです。

問題を再現するためのコードを次に示します。

これは、最小8のSDK16を対象とするアプリにあります。2.3デバイスでこれを実行すると、問題が再現されます。

これが3つのクラスです:

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は、3.1以降でのみ使用できます。

今実行する場合:Object obj = VersionedLoader.newInstance();

このエラーはログに表示されます。

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;

クラッシュすることはなく、実際には正しく機能します。

4

1 に答える 1

4

はい、問題を再現できます。ちょっと驚くべきことですが、ご存知のように、クラッシュしないという事実は、これが、アプリに害を及ぼすはずの何よりも、おそらくLogCatでDalvikが少しおしゃべりすぎるケースであることを意味します。

回避策の1つは、インターフェイスを内部クラスに移動することです。NewVersionLoaderあなたの例では、を実装する代わりにAnimatorListener、の内部クラスは以下NewVersionLoaderを実装します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) {}

    }
}

確かに、これは、の使用目的によっては理想的ではない場合がありますVersionedLoader。ただし、それVersionedLoader自体は実装されていないためAnimationListener、のユーザーはメソッドVersionedLoaderを呼び出さないAnimationListenerため、ロジックが実際のクラスではなく内部クラスにあるという事実は、大きな問題にはなりません。

于 2012-10-28T18:42:42.473 に答える