可添加到Parcel的可分配内部包

Hei*_*sch 8 android parcelable

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

不同车型之间的差异非常小,可能只是几个数据领域.所以这可以通过为不同的汽车创建演示者(只是一个保存数据的类)来解决.然后,演示者将知道它应该保留哪些额​​外数据.因为演示者本身不是可分区的,所以它的所有数据都有一个Bundle,然后Car类将添加到parcelable.我不想让主持人成为可能的人.

因此,Car从演示者处获取Bundle并将其放入其包裹中:

  public void writeToParcel(Parcel parcel, int flags) {
    parcel.writeBundle(getPresenter().getBundle());
  } 
Run Code Online (Sandbox Code Playgroud)

然后它将解压缩:

  public Car(Parcel parcel) {    
    getPresenter().setBundle(parcel.readBundle());
  }
Run Code Online (Sandbox Code Playgroud)

这可以正常工作,直到演示者将可分配的对象添加到包中.然后我收到这个错误:

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
Run Code Online (Sandbox Code Playgroud)

所以它以某种方式无法从Bundle中读取任何内容.

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

  public Car(Parcel parcel) {    
    getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader()));
  }
Run Code Online (Sandbox Code Playgroud)

但是,这不是说我的捆绑中只能有一种类型的parcelables吗?例如,如果另一位演示者想要将另一个可分配的对象添加到该包中,该怎么办?

谁能对此有所了解?

Gun*_*son 15

你在评论中写道:

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

Android框架工程师Dianne Hackborn 写道:

当捆绑包从包裹中读取时,它只是提取数据.直到稍后从捆绑中检索内容时,它实际上不会从该数据中取消.

Bundle源代码中,我们看到该setClassLoader()方法设置了字段mClassLoader:

public void setClassLoader(ClassLoader loader) {
     mClassLoader = loader;
 }
Run Code Online (Sandbox Code Playgroud)

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

public Bundle(int capacity) {
     //...
     mClassLoader = getClass().getClassLoader();
 }
Run Code Online (Sandbox Code Playgroud)

mClassLoader然后用于解组方法中的parceled数据unparcel():

 mParcelledData.readMapInternal(mMap, N, mClassLoader);
 mParcelledData.recycle();
 mParcelledData = null;
Run Code Online (Sandbox Code Playgroud)

Net,如果你没有设置ClassLoader它,那么ClassLoader当解组它的parceled数据时它将默认为系统,并且ClassNotFoundException当解组我们的自定义Parcelable对象数据时我们会得到一个,


Nic*_*mer 14

ClassLoaders是Java中最不了解的特性之一.它们提供"命名空间",实际上这些命名空间可以"嵌套",因为如果当前的ClassLoader找不到类,它可以检查"Parent"ClassLoader.

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

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


Dav*_*ser 5

你写了:

这可以通过将readBundle调用修改为:

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

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

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

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

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