26

我观察到的将可序列化数据作为额外意图传递的行为非常奇怪,我只是想澄清是否有一些我没有错过的东西。

所以我想做的事情是在我为开始下一个活动创建的实例中ActivtyA放入一个LinkedList实例- .intentActivityB

LinkedList<Item> items = (some operation);
Intent intent = new Intent(this, ActivityB.class);
intent.putExtra(AppConstants.KEY_ITEMS, items);

onCreateof 中ActivityB,我尝试LinkedList按如下方式检索额外内容 -

LinkedList<Item> items = (LinkedList<Item>) getIntent()
                             .getSerializableExtra(AppConstants.KEY_ITEMS);

在运行此程序时,我在上面的行中反复输入了一个ClassCastExceptionin ActivityB。基本上,异常说我收到了一个ArrayList. 一旦我将上面的代码更改为接收一个ArrayList,一切正常。

现在我不能只从现有文档中弄清楚这是否是 Android 在传递可序列化列表实现时的预期行为。或者,也许,我正在做的事情根本上是错误的。

谢谢。

4

3 回答 3

51

我可以告诉你为什么会这样,但你不会喜欢它;-)

首先是一些背景信息:

an中的ExtrasIntent基本上是一个 Android Bundle,它基本上是一个HashMap键/值对。所以当你做类似的事情时

intent.putExtra(AppConstants.KEY_ITEMS, items);

AndroidBundle为 extras 创建了一个新条目,并将一个映射条目添加到Bundle键所在的位置AppConstants.KEY_ITEMS和值是项目的位置(这是您的 LinkedList 对象)。

这一切都很好,如果您在代码执行后查看附加包,您会发现它包含一个LinkedList. 现在是有趣的部分......

当您startActivity()使用包含 extras 的 Intent 调用时,Android 需要将 extras 从键/值对映射转换为字节流。基本上它需要序列化 ​​Bundle。它需要这样做,因为它可能会在另一个进程中启动活动,并且为了做到这一点,它需要序列化/反序列化 Bundle 中的对象,以便它可以在新进程中重新创建它们。它也需要这样做,因为 Android 将 Intent 的内容保存在一些系统表中,以便以后需要时可以重新生成 Intent。

为了将其序列Bundle化为字节流,它会遍历包中的映射并获取每个键/值对。然后它获取每个“值”(它是某种对象)并尝试确定它是哪种对象,以便它可以以最有效的方式序列化它。为此,它会根据已知对象类型列表检查对象类型。“已知对象类型”列表包含诸如Integer, Long, String,之类的内容MapBundle不幸的是还有List. 因此,如果对象是 a List(其中有许多不同的种类,包括LinkedList),它会将其序列化并将其标记为 type 的对象List

Bundle反序列化时,即:当你这样做时:

LinkedList<Item> items = (LinkedList<Item>)
        getIntent().getSerializableExtra(AppConstants.KEY_ITEMS);

它为类型ArrayList中的所有对象生成一个。BundleList

实际上,您无法做任何事情来改变 Android 的这种行为。至少现在你知道它为什么这样做了。

只是为了让您知道:我实际上编写了一个小型测试程序来验证这种行为,并且我查看了源代码,Parcel.writeValue(Object v)该方法是在Bundle将映射转换为字节流时调用的方法。

重要说明:由于List是接口,这意味着任何实现List您放入 a 的类Bundle都将以ArrayList. 有趣的Map是,它也在“已知对象类型”列表中,这意味着无论Map您放入什么类型的对象Bundle(例如TreeMap,SortedMap或任何实现该Map接口的类),您总是会得到HashMap一个它。

于 2012-09-06T17:35:58.667 回答
5

@David Wasser的答案在诊断问题方面是正确的。这篇文章是为了分享我是如何处理它的。

任何List对象作为一个出来的问题ArrayList并不可怕,因为你总是可以做类似的事情

LinkedList<String> items = new LinkedList<>(
    (List<String>) intent.getSerializableExtra(KEY));

这会将反序列化列表的所有元素添加到新的LinkedList.

当涉及到 时,问题要严重得多Map,因为您可能已经尝试序列化 aLinkedHashMap并且现在已经丢失了元素排序。

幸运的是,有一种(相对)轻松的方法来解决这个问题:定义您自己的可序列化包装类。您可以针对特定类型执行此操作,也可以通用执行此操作:

public class Wrapper <T extends Serializable> implements Serializable {
    private T wrapped;

    public Wrapper(T wrapped) {
        this.wrapped = wrapped;
    }

    public T get() {
        return wrapped;
    }
}

然后,您可以使用它来隐藏您的ListMap或其他数据类型以防止 Android 的类型检查:

intent.putExtra(KEY, new Wrapper<>(items));

然后:

items = ((Wrapper<LinkedList<String>>) intent.getSerializableExtra(KEY)).get();
于 2017-12-17T20:14:42.470 回答
0

如果您正在使用 IcePick 库并且遇到此问题,您可以使用 Ted Hoop 的技术和自定义捆绑器,以避免在代码中处理 Wrapper 实例。

public class LinkedHashmapBundler implements Bundler<LinkedHashMap> {

 @Override
 public void put(String s, LinkedHashMap val, Bundle bundle) {
    bundle.putSerializable(s, new Wrapper<>(val));
 }

 @SuppressWarnings("unchecked")
 @Override
 public LinkedHashMap get(String s, Bundle bundle) {
   return ((Wrapper<LinkedHashMap>) bundle.getSerializable(s)).get();
 }
} 

// Use it like this
@State(LinkedHashmapBundler.class) LinkedHasMap map
于 2018-06-15T15:26:56.987 回答