【问题标题】:Parcelable inside bundle which is added to ParcelParcelable 内的捆绑包被添加到 Parcel
【发布时间】:2012-11-05 11:21:16
【问题描述】:

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

不同车型之间的差异很小,可能只是几个数据字段。因此,这是通过为不同的汽车创建演示者(只是一个保存数据的类)来解决的。然后演示者将知道它应该保存哪些额外数据。因为演示者本身是不可打包的,所以它的所有数据都会有一个 Bundle,然后 Car 类将添加到 Parcelable 中。我不想让演示者变成 Parcelables。

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

但是,这是否意味着我的包裹中只能包含一种类型的包裹?例如,如果另一个演示者想要将另一个可打包对象添加到捆绑包中怎么办?

有人能解释一下吗?

【问题讨论】:

    标签: android parcelable


    【解决方案1】:

    ClassLoader 是 Java 中最不为人知的特性之一。它们提供“命名空间”,实际上这些命名空间可以“嵌套”,因为如果当前的 ClassLoader 没有找到某个类,它可以检查“Parent”ClassLoader。

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

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

    【讨论】:

      【解决方案2】:

      你在评论中写道:

      但是,我的问题是更多地说明为什么在这种情况下我什至必须指定一个类加载器

      Dianne Hackborn,Android 框架工程师,writes

      当包从包裹中读取时,它只是提取数据。直到稍后您从包中检索内容时,它才真正从该数据中解包。

      Bundle source code中,我们看到setClassLoader()方法设置了字段mClassLoader

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

      如果我们不使用setClassLoader()或构造函数Bundle(ClassLoader loader)mClassLoader字段将被设置为默认的ClassLoader

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

      然后使用mClassLoaderunparcel() 方法中解组打包的数据:

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

      Net,如果你不设置ClassLoader,它会在解组它的打包数据时默认为系统ClassLoader,当解组我们自定义的Parcelable对象数据时,我们会得到一个ClassNotFoundException

      【讨论】:

      • 我了解包裹的基础知识,并且它在我的应用程序的其他任何地方都可以使用。引擎确实实现了 Parcelable。问题是,如果我将包裹放入一个包裹中,然后将包裹放入包裹中,我将无法从包裹(或包裹中的任何其他字段)中解压包裹。
      • 是的,我已经尝试过了,但我认为它不起作用。但是,我的问题是更多地说明为什么在这种情况下我什至必须指定一个类加载器。
      • 但你仍然没有真正回答这个问题。为什么一定要指定自定义的classloader,为什么系统classloader加载不了类?
      【解决方案3】:

      你写道:

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

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

      但是,这是否意味着我只能拥有一种 我的包裹中的包裹?例如,如果另一个演示者 想要将另一个可打包对象添加到捆绑包中?

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

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

      澄清一下,(通常)没有“每个类的类加载器”,有一个系统类加载器,然后有一个“每个应用程序的类加载器”。我希望这能回答你的问题。

      【讨论】:

        【解决方案4】:

        这是我实现 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 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");
            }
        }
        

        【讨论】:

        • 我有很多实现 parcelable 的模型...这给了我什么新东西。
        猜你喜欢
        • 2020-05-02
        • 1970-01-01
        • 1970-01-01
        • 2012-03-12
        • 1970-01-01
        • 2012-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多