BadParcelableException: ClassNotFoundException при демаршалинге ‹myclass› при использовании метода Parcel.read, который имеет ClassLoader в качестве аргумента

Учитывая настраиваемый класс org.example.app.MyClass implements Parcelable, я хочу записать List<MyClass> в Parcel. Я провел сортировку с

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

всякий раз, когда я пытаюсь разобрать класс с помощью

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

есть "BadParcelableException: ClassNotFoundException when unmarshalling org.example.app.MyClass" исключение.

Что здесь не так? Почему класс не найден?


person Flow    schedule 08.08.2013    source источник
comment
Я получил эту ошибку в другом контексте - при вызове bundle.keySet() для пакета, содержащего Parcelable. При запуске отдельного тестового класса для рассматриваемого кода он прошел, но запуск всего набора тестов привел к BadParcelableException. Исправление было делать bundle.setClassloader(MyClass.class.getClassLoader()) перед bundle.keySet().   -  person Stan Kurdziel    schedule 23.05.2015


Ответы (4)


Не демаршалируйте настраиваемый класс (т.е. тот, который предоставляется вашим приложением, а не платформой Android) с помощью загрузчика классов инфраструктуры, который используется, когда вы указываете null в качестве аргумента ClassLoader. Используйте загрузчик классов приложения:

parcel.readList(myclassList, getClass().getClassLoader());

Всякий раз, когда метод Parcel.read*() также имеет ClassLoader в качестве аргумента (например, _ 4_) и вы хотите прочитать класс приложения из Parcel, используйте загрузчик классов приложения, который можно получить с помощью getClass().getClassLoader().

Предпосылки: Android поставляется с двумя загрузчиками классов: загрузчиком системных классов, который может загружать все системные классы, кроме тех, которые предоставлены вашим приложением; и загрузчик классов приложения, который установил загрузчик системных классов в качестве своего родителя и, следовательно, может загружать все классы. Если вы укажете null в качестве загрузчика классов, _ 7_ будет использовать загрузчик классов фреймворка, вызывая ClassNotFoundException.

Спасибо alexanderblom за подсказку, которая привела меня на правильный путь.

person Flow    schedule 08.08.2013
comment
По какой причине это не происходит в 100% случаев? Я получаю это на некоторых пользовательских устройствах, но не могу воспроизвести на своей стороне. - person Arka Prava Basu; 05.11.2020

Я считаю, что более правильной формой этого было бы:

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

Потому что здесь вы явно используете загрузчик классов для универсального типа List.

person Tanner Perrien    schedule 29.01.2014
comment
Это в основном то же самое, что и мой ответ. Нет разницы между MyClass.class и getClass(), оба возвращают один и тот же объект класса, поскольку getClass() вызывается в MyClass. Кроме того, вы можете использовать здесь любой класс, если это настраиваемый класс, а не тот, который предоставляется фреймворком. - person Flow; 29.01.2014
comment
Я не сказал, что вы ошибались, просто эта форма может быть более подходящей. Также учтите, что вы делаете дополнительный вызов метода getClass(), который не нужен при статической ссылке на класс. - person Tanner Perrien; 24.11.2014
comment
В моем случае это не работает. Использование classLoader, как уже упоминалось. - person SimpleCoder; 26.01.2019

Я столкнулся с той же проблемой, и вместо 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());

Надеюсь, это будет полезно для некоторых.

person Wasif Kirmani    schedule 12.09.2015

Вы можете получить эту ошибку, если неправильно подклассифицируете пользовательский вид.

Предположим, вы создаете подкласс 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 может быть лучшим выбором, чем BaseSavedState или android.view.AbsSavedState, поскольку оно позволит вам передать загрузчик класса суперклассу:

SavedState(Parcel source, ClassLoader classLoader) {
    super(source, classLoader); //available in android.support.v4.view.AbsSavedState
    this.stateBundle = source.readBundle(classLoader);
}
person David Rawson    schedule 23.01.2018
comment
Привет, Дэвид Роусон, большое спасибо за ваш ответ. Это как раз мой случай с расширенным BottomNavigationView. - person temnoi; 07.07.2018