Parcelable внутри пакета, который добавляется к Parcel

В моем проекте у меня есть модель, которая содержит основную информацию о модели. Например, скажем, что модель является автомобилем. Кроме того, есть много разных разновидностей автомобилей, и им присвоены разные данные. Все модели должны быть разделены.

Различий между разными автомобилями очень мало, это может быть всего несколько полей данных. Так что это решается созданием презентаторов (просто класс, который содержит данные) для разных автомобилей. Затем ведущий будет знать, какие дополнительные данные он должен хранить. Поскольку сам презентатор не подлежит разделению, он будет иметь Bundle для всех своих данных, которые затем класс Car добавит к разделяемому. Я не хочу превращать докладчиков в посылок.

Итак, Автомобиль берет у ведущего Bundle и кладет его в свою посылку:

  public void writeToParcel(Parcel parcel, int flags) {
    parcel.writeBundle(getPresenter().getBundle());
  } 

Затем он распаковывает его с помощью:

  public Car(Parcel parcel) {    
    getPresenter().setBundle(parcel.readBundle());
  }

Это работает нормально до тех пор, пока ведущий не добавит в пакет объект, который можно разделить. Затем я получаю эту ошибку:

11-16 15:06:37.255: E/AndroidRuntime(15193): FATAL EXCEPTION: main
11-16 15:06:37.255: E/AndroidRuntime(15193): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example/com.example.activity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.model.engine
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2185)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2210)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.access$600(ActivityThread.java:142)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1208)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Handler.dispatchMessage(Handler.java:99)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Looper.loop(Looper.java:137)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.main(ActivityThread.java:4931)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at java.lang.reflect.Method.invokeNative(Native Method)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at java.lang.reflect.Method.invoke(Method.java:511)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:558)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at dalvik.system.NativeStart.main(Native Method)
11-16 15:06:37.255: E/AndroidRuntime(15193): Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.model.engine
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readParcelable(Parcel.java:2077)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readValue(Parcel.java:1965)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readMapInternal(Parcel.java:2226)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Bundle.unparcel(Bundle.java:223)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Bundle.getString(Bundle.java:1055)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.example.cars.CarPresenter.getExtraString(CarPresenter.java:34)
11-16 15:06:37.255: E/AndroidRuntime(15193):         ... 11 more

Таким образом, он почему-то не может ничего прочитать из Bundle.

Это можно решить, изменив вызов readBundle на:

  public Car(Parcel parcel) {    
    getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader()));
  }

Однако не означает ли это, что в моей связке может быть только один тип посылок? Например, что, если другой докладчик захочет добавить в пакет еще один предмет, который можно разделить?

Может ли кто-нибудь пролить свет на это?


person Heinrisch    schedule 16.11.2012    source источник


Ответы (4)


Вы написали в комментарии:

Тем не менее, мой вопрос был больше о том, почему мне вообще нужно указывать загрузчик классов в этом случае

Дайанн Хакборн, разработчик платформы Android, пишет следующее:

когда пакет читает из посылки, он просто извлекает данные. На самом деле он не распаковывается из этих данных до тех пор, пока вы не извлечете материал из пакета.

В исходном коде Bundle мы видим, что setClassLoader()метод устанавливает поле mClassLoader:

public void setClassLoader(ClassLoader loader) {
     mClassLoader = loader;
 }

Если мы не используем setClassLoader() или конструктор Bundle(ClassLoader loader), для поля mClassLoader будет установлено значение по умолчанию ClassLoader:

public Bundle(int capacity) {
     //...
     mClassLoader = getClass().getClassLoader();
 }

Затем mClassLoader используется для демаршалирования разделенных данных в методе unparcel():

 mParcelledData.readMapInternal(mMap, N, mClassLoader);
 mParcelledData.recycle();
 mParcelledData = null;

Net, если вы не установите ClassLoader, по умолчанию будет использоваться системный ClassLoader при разупорядочении его разделенных данных, и мы получим ClassNotFoundException при разупорядочении наших пользовательских данных объекта Parcelable,

person onosendai    schedule 23.11.2012
comment
Я знаю основы работы с посылками, и он работает везде в моем приложении. Движок реализует Parcelable. Проблема в том, что если я кладу посылку в пачку, а потом пачку в посылку, я не могу распаковать посылку из пачки (или любого другого поля в пачке). - person Heinrisch; 23.11.2012
comment
Да, я пробовал это, и я думаю, что это не сработало. Тем не менее, мой вопрос был больше о том, почему мне вообще нужно указывать загрузчик классов в этом случае. - person Heinrisch; 23.11.2012
comment
Но вы так и не ответили на вопрос. Почему вам нужно указать собственный загрузчик классов, почему системный загрузчик классов не может загрузить класс? - person Bjarke Freund-Hansen; 30.10.2013

ClassLoaders — одна из наименее понятных функций Java. Они предоставляют «пространства имен», и на самом деле эти пространства имен могут «вкладываться» в силу того факта, что, если класс не найден текущим загрузчиком классов, он может проверить «родительский» загрузчик классов.

В случае приложения для Android есть два ClassLoaders. Есть один, который знает, как загружать классы из вашего APK, и другой, который знает, как загружать классы из платформы Android. В первом случае второй установлен как загрузчик класса «Родитель», поэтому в целом вы этого не замечаете. Однако, поскольку Bundle является классом фреймворка и загружается загрузчиком классов фреймворка, вам нужно сообщить ему о том, что ClassLoader используется для поиска классов в вашем APK. Загрузчик классов, который Bundle использует по умолчанию, — это фреймворк ClassLoader, который является «нижним» пространством имен, чем пространство имен с вашими классами APK.

Таким образом, сообщая пакету, какой ClassLoader использовать, вы сообщаете ему, что ему нужно проверить APK ClassLoader на наличие классов. По умолчанию он проверяет фреймворк APK, так как это ClassLoader, который загрузил класс Bundle и, таким образом, является единственным «пространством имен», в котором он знает, как находить классы.

person Nick Palmer    schedule 27.11.2012

Вы написали:

Это можно решить, изменив вызов readBundle на:

public Car(Parcel parcel) { getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader())); }

Однако не означает ли это, что в моей связке может быть только один тип посылок? Например, что, если другой докладчик захочет добавить в пакет еще один предмет, который можно разделить?

Вообще-то, нет. Когда вы устанавливаете для classLoader значение engine.class.getClassLoader(), вы фактически предоставляете загрузчик классов, который знает, как загружать все классы из вашего APK. Таким образом, вы можете использовать тот же загрузчик классов для распараллеливания всех других пользовательских классов в вашем приложении, которые реализуют Parcelable.

Проблема, с которой вы столкнулись, заключается в том, что когда вы не указываете загрузчик классов, для распаковки файла Bundle используется системный загрузчик классов (по умолчанию). Загрузчик системных классов знает, как загружать только те классы, которые известны системе Android, и не знает, как загружать какие-либо классы, специфичные для приложения.

Просто чтобы уточнить, (в общем) нет «загрузчика классов для каждого класса», есть загрузчик системных классов, а затем есть «загрузчик классов для каждого приложения». Надеюсь, это ответит на ваш вопрос.

person David Wasser    schedule 23.11.2012

Это моя модель, которая реализует Parcelable:

public class ProgramModel implements Parcelable
{
    private int id = -1;
    private String title = "";
    private String time = "";
    private String day = "";
    private String organization = "";
    private String participants = "";
    private String description = "";
    private String descriptionFull = "";
    private String location = "";
    private String locationCaption = "";
    private int locationCode = 0;
    private String locationMap = "";
    private String imageUrl = "";
    private String color = "";
    private int colorId = -1;
    private int columnId = -1;
    private String startTime = "";
    private String endTime = "";
    private Date convertedTimeStart = null;
    private Date convertedTimeEnd = null;
    private String theme = "";
    private ArrayList<TagModel> tags = new ArrayList<TagModel>();
    private ArrayList<Integer> tagsId = new ArrayList<Integer>();

    public ProgramModel()
    {
    }

    private ProgramModel(Parcel in)
    {
        id = in.readInt();
        title = in.readString();
        time = in.readString();
        day = in.readString();
        organization = in.readString();
        participants = in.readString();
        description = in.readString();
        descriptionFull = in.readString();
        location = in.readString();
        locationCaption = in.readString();
        locationCode = in.readInt();
        locationMap = in.readString();
        imageUrl = in.readString();
        color = in.readString();
        colorId = in.readInt();
        columnId = in.readInt();
        startTime = in.readString();
        endTime = in.readString();
        convertedTimeStart = new Date(in.readLong());
        convertedTimeEnd = new Date(in.readLong());
        theme = in.readString();
        in.readTypedList(tags, TagModel.CREATOR);
        in.readList(tagsId, Integer.class.getClassLoader());
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    // Other getters and setters

    @Override
    public int describeContents()
    {
        return 0;
    }

    // this is used to regenerate your object
    public static final Parcelable.Creator<ProgramModel> CREATOR = new Parcelable.Creator<ProgramModel>()
    {
        public ProgramModel createFromParcel(Parcel in)
        {
            return new ProgramModel(in);
        }

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

    @Override
    public void writeToParcel(Parcel out, int flags)
    {
        out.writeInt(id);
        out.writeString(title);
        out.writeString(time);
        out.writeString(day);
        out.writeString(organization);
        out.writeString(participants);
        out.writeString(description);
        out.writeString(descriptionFull);
        out.writeString(location);
        out.writeString(locationCaption);
        out.writeInt(locationCode);
        out.writeString(locationMap);
        out.writeString(imageUrl);
        out.writeString(color);
        out.writeInt(colorId);
        out.writeInt(columnId);
        out.writeString(startTime);
        out.writeString(endTime);
        out.writeLong(convertedTimeStart.getTime());
        out.writeLong(convertedTimeEnd.getTime());
        out.writeString(theme);
        out.writeTypedList(tags);
        out.writeList(tagsId);
    }
}

Запишите данные в намерение в Activity:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("ProgramModel", programDetailsModel);
startActivity(intent);

Чтение данных из пакета в Activity:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    savedInstanceState = getIntent().getExtras();
    if (savedInstanceState != null)
    {
        fromBundleModel = savedInstanceState.getParcelable("ProgramModel");
    }
}
person Artyom Kiriliyk    schedule 22.11.2012
comment
У меня есть много моделей, которые реализуются по частям... это не дает мне ничего нового. - person Heinrisch; 23.11.2012