web-dev-qa-db-ja.com

引数としてClassLoaderを持つParcel.readメソッドを使用中に「BadParcelableException:ClassNotFoundException when unmarshalling <myclass>」

カスタムクラスorg.example.app.MyClass implements Parcelableが与えられた場合、List<MyClass>をパーセルに書き込みます。マーシャリングをしました

 List<MyClass> myclassList = ...
 parcel.writeList(myclassList);

クラスをアンマーシャルしようとするたびに

 List<MyClass> myclassList = new ArrayList<MyClass>();
 parcel.readList(myclassList, null);

"BadParcelableException: ClassNotFoundException when unmarshalling org.example.app.MyClass"例外があります。

ここで何が間違っていますか?クラスが見つからないのはなぜですか?

48
Flow

ClassLoaderとしてnullを指定したときに使用されるフレームワーククラスローダーで、カスタムクラス(Androidフレームワークではなく、アプリケーションによって提供されるクラス)をアンマーシャリングしないでください。引数:アプリケーションクラスローダーを使用します。

_parcel.readList(myclassList, getClass().getClassLoader());
_

Parcel.read*()メソッドにも引数としてClassLoaderがあり(たとえば Parcel.readList(List outVal, ClassLoader loader) )、Parcelからアプリケーションクラスを読み取りたい場合、getClass().getClassLoader()で取得できるアプリケーションクラスローダーを使用します。

Background:Androidには2つのクラスローダーが付属しています:システムクラスローダーは、すべてのシステムクラスをロードできますが、アプリケーションによって提供されるもの、およびシステムクラスローダーを親として設定し、すべてのクラスをロードできるアプリケーションクラスローダー。クラスローダーとしてnullを指定すると、 Parcel.readParcelableCreator()フレームワーククラスローダーを使用しますClassNotFoundException が発生します。

ヒント を提供してくれたalexanderblomに感謝します。

102
Flow

これのより正しい形式は次のようになると思います:

List<MyClass> myclassList = new ArrayList<MyClass>();
parcel.readList(myclassList, MyClass.class.getClassLoader());

ここでは、リストのジェネリック型に対してクラスローダーを明示的に使用しているためです。

18
Tanner Perrien

私は同じ問題に直面しており、parcleableの代わりにbundleを使用しました

intent.setExtrasClassLoader(getClassLoader());
/* Send optional extras */
Bundle bundle = new Bundle();
bundle.putParcelable("object", mObject);
bundle.putString("stringVal", stringVal);

intent.putExtra("bundle",bundle);

そして、私の意図のクラスでは、これを使用して値を取得しました。

Bundle oldBundle = intent.getBundleExtra("bundle");
ResultReceiver receiver = oldBundle.getParcelable("object");
String stringVal = oldBundle.getString("stringVal");
intent.setExtrasClassLoader(getClassLoader());

それが一部の人に役立つことを願っています。

4
Wasif Kirmani

カスタムビューを誤ってサブクラス化すると、このエラーが発生する可能性があります。

BottomNavigationView をサブクラス化しており、保存された状態をonSaveInstanceState()のスーパーステートに追加するとします。

Parcelableボイラープレートの誤った実装(別のクラスまたはテンプレートからコピー)は次のようになります。

static class State extends BaseSavedState {

    Bundle stateBundle;

    //incorrect as super state uses ClassLoaderCreator
    public static final Creator<State> CREATOR = new Creator<State>() {
        public State createFromParcel(Parcel in) {
            return new State(in);
        }

        public State[] newArray(int size) {
            return new State[size];
        }
    };

    State(Parcel source) {
        super(source);
        this.stateBundle = source.readBundle(getClass().getClassLoader());
    }

    State(Parcelable superState) {
        super(superState);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        super.writeToParcel(out, flags);
        out.writeBundle(stateBundle);
    }
}

BottomNavigationViewからのスーパーステートにはクラスローダーが必要なので、これは機能しません。代わりに、SavedStateからBottomNavigationViewクラスを慎重に検査し、ClassLoaderCreatorではなく正しいCreatorを使用する必要があります。

static class State extends AbsSavedState {

    Bundle stateBundle;

    public static final Creator<State> CREATOR = new ClassLoaderCreator<State>() {
        public State createFromParcel(Parcel in, ClassLoader classLoader) {
            return new State(in, classLoader);
        }

        @Override
        public State createFromParcel(Parcel source) {
            return new State(source, null);
        }

        public State[] newArray(int size) {
            return new State[size];
        }
    };

    State(Parcel source, ClassLoader classLoader) {
        super(source, classLoader);
        this.stateBundle = source.readBundle(classLoader);
    }

    State(Parcelable superState) {
        super(superState);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        super.writeToParcel(out, flags);
        out.writeBundle(stateBundle);
    }
}

Android.support.v4.view.AbsSavedStateを拡張することは、クラスローダーをスーパークラスに渡すことができるため、BaseSavedStateAndroid.view.AbsSavedStateよりも適切な選択であることに注意してください。

SavedState(Parcel source, ClassLoader classLoader) {
    super(source, classLoader); //available in Android.support.v4.view.AbsSavedState
    this.stateBundle = source.readBundle(classLoader);
}
3
David Rawson