在下一个活动中检索时,置于Intent extra中的LinkedList会重新转换为ArrayList

时间:2012-09-06 13:10:57

标签: android android-intent

我正在观察w.r.t传递可序列化数据作为意图额外的行为是非常奇怪的,我只是想澄清是否有一些我不会错过的东西。

所以我试图做的是在ActivtyA中我将LinkedList个实例放入我为开始下一个活动而创建的intent - ActivityB。< / p>

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

onCreate的{​​{1}}中,我尝试按以下方式检索ActivityB附加内容 -

LinkedList

在运行此操作时,我在LinkedList<Item> items = (LinkedList<Item>) getIntent() .getSerializableExtra(AppConstants.KEY_ITEMS); 的上面一行中反复获得ClassCastException。基本上,例外说我收到ActivityB。一旦我将上面的代码更改为接收ArrayList,一切都运行正常。

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

感谢。

3 个答案:

答案 0 :(得分:49)

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

首先是一些背景信息:

Intent中的

附加内容基本上是Android Bundle,基本上是HashMap个键/值对。所以当你做类似的事情时

intent.putExtra(AppConstants.KEY_ITEMS, items);

Android会为附加内容创建新的Bundle,并向Bundle添加一个地图条目,其中密钥为AppConstants.KEY_ITEMS,值为(其中是你的LinkedList对象。)

这一切都很好,如果你在代码执行后查看extras包,你会发现它包含LinkedList。现在来了有趣的部分...

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

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

反序列化Bundle时,即:执行此操作时:

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

它会为ArrayList类型的Bundle中的所有对象生成List

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

就这样你知道:我实际上写了一个小测试程序来验证这种行为,我查看了Parcel.writeValue(Object v)的源代码,这是Bundle转换时调用的方法将地图转换为字节流。

重要说明:由于List是一个接口,这意味着您在List中添加Bundle的任何类都将以ArrayList形式出现{1}}。 同样有趣的是,Map也在“已知对象类型”列表中,这意味着无论您将哪个Map对象放入Bundle(例如{{1} }},TreeMap或任何实现SortedMap接口的类),您将始终获得Map

答案 1 :(得分:1)

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

任何List对象作为ArrayList出现的问题并不可怕,因为您可以随时执行类似

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

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

问题在Map时更糟糕,因为您可能尝试序列化LinkedHashMap并且现在已经丢失了元素排序。

幸运的是,这是一种(相对)无痛的方法:定义自己的可序列化包装类。您可以针对特定类型执行此操作或一般性地执行此操作:

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

答案 2 :(得分:0)

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

props