Parcelable对象存在于被加入到Parcel中的Bundle中。

11

在我的项目中,我有一个模型,它包含有关模型的基本信息。例如,假设该模型是一辆汽车。然后,有许多不同种类的汽车,它们分配了不同的数据。所有模型都必须是可捆绑的。

不同汽车之间的差异非常小,可能只有几个数据字段。因此,通过为不同的汽车创建Presenter(只是保存数据的类)来解决这个问题。然后,Presenter将知道它应该持有哪些额外的数据。因为Presenter本身不是可捆绑的,所以它将拥有一个Bundle作为其所有数据的容器,然后Car类将把它添加到可捆绑物中。我不想让Presenter成为可捆绑物。

因此,Car从Presenter获取Bundle并将其放入其可捆绑物中:

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

接下来将使用以下方式进行解包:

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

在 presenter 将一个 Parcelable 对象添加到 Bundle 中之前,一切都正常。但是添加之后,我会收到以下错误:

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

然而,这是否意味着我只能在我的bundle中拥有一种类型的可包含对象?例如,如果另一个presenter想要向bundle中添加另一个可包含对象怎么办?

有人能解释一下吗?

4个回答

20

类加载器是 Java 中最不被理解的功能之一。它们提供“命名空间”,事实上这些命名空间可以由于当前类加载器找不到类时可以检查“父”类加载器而“嵌套”。

在 Android 应用程序的情况下,有两个类加载器。其中一个知道如何从您的 APK 加载类,另一个则知道如何从 Android 框架加载类。前者将后者设置为“父”类加载器,因此通常您不会注意到。然而,由于 Bundle 是框架类,并且由框架类加载器加载,因此您需要告诉它正在使用来查找 APK 中的类的类加载器。Bundle 默认使用的类加载器是框架类加载器,它是一个比包含 APK 类的类加载器“低”的命名空间。

因此,通过告诉 bundle 使用哪个类加载器,您告诉它需要检查 APK 类加载器以查找类。它默认检查框架 APK,因为这是加载 Bundle 类的类加载器,因此是它唯一知道如何在其中查找类的“命名空间”。


18
你在评论中写道:
“然而,我的问题更多是为什么我甚至需要在这种情况下指定一个类加载器。”
Android框架工程师Dianne Hackborn在文章中写道:
“当Bundle从包裹中读取时,它只是提取数据。直到稍后你从Bundle中检索东西时才实际解包数据。”
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;

如果您没有设置ClassLoader,则在取消打包其数据时,默认情况下会使用系统ClassLoader,并且在取消打包自定义Parcelable对象数据时会出现ClassNotFoundException


我知道parcel的基础知识,并且它在我的应用程序的其他地方都能正常工作。引擎确实实现了Parcelable接口。问题是,如果我将一个Parcelable对象放入Bundle中,然后再将Bundle放入Parcel中,我就无法从Bundle(或Bundle中的任何其他字段)中解包Parcelable对象。 - Heinrisch
是的,我尝试过了,但我认为它没有起作用。然而,我的问题更多的是为什么在这种情况下我甚至需要指定一个类加载器。 - Heinrisch
1
但是你仍然没有真正回答这个问题。为什么必须指定自定义类加载器,为什么系统类加载器不能加载该类? - Bjarke Freund-Hansen

5

您写道:

这可以通过修改readBundle调用来解决:

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

但是,这是否意味着我只能在 bundle 中有一种类型的parcelables?例如,如果另一个 presenter 想要向bundle中添加另一个parcelable对象怎么办?

其实不是这样的。当您将classLoader设置为 engine.class.getClassLoader()时,您实际上提供了一个类加载器,它知道如何从您的 APK 加载所有类。因此,您可以使用相同的类加载器来取消打包应用程序中实现 Parcelable 的所有其他自定义类。

您看到的问题是,当您指定类加载器时,系统(默认)类加载器用于取消打包Bundle。系统类加载器仅知道如何加载已知于 Android 系统的类,并且不知道如何加载任何特定于应用程序的类。

只是为了澄清,在通常情况下,不存在“每个类一个类加载器”的概念,存在一个系统类加载器和一个“每个应用程序一个类加载器”。希望这回答了您的问题。


-4

这是我的实现了 Parcelable 接口的 Model:

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中从bundle读取数据:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    savedInstanceState = getIntent().getExtras();
    if (savedInstanceState != null)
    {
        fromBundleModel = savedInstanceState.getParcelable("ProgramModel");
    }
}

1
我有很多实现了Parcelable接口的模型...这对我来说没有什么新意。 - Heinrisch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接