当将LinkedList放入Intent extra中并在下一个活动中检索时,它会被重新转换为ArrayList。

28

我发现在将可序列化的数据作为intent额外信息传递时存在一种奇怪的行为,我想澄清是否有些东西我没有注意到。

所以我尝试的事情是,在ActivtyA中,我将一个LinkedList实例放入了我创建用于启动下一个活动-ActivityBintent中。

LinkedList<Item> items = (some operation);
Intent intent = new Intent(this, ActivityB.class);
intent.putExtra(AppConstants.KEY_ITEMS, items);
在ActivityB的onCreate方法中,我尝试按如下方式检索LinkedList类型的Extra:
LinkedList<Item> items = (LinkedList<Item>) getIntent()
                             .getSerializableExtra(AppConstants.KEY_ITEMS);

运行时,我一直在ActivityB上收到一个ClassCastException异常,就在上面的那一行。基本上,异常提示我正在接收一个ArrayList。一旦我将上面的代码更改为接收ArrayList,一切都正常了。

现在我无法从现有文档中找出在Android上传递可序列化列表实现是否是预期行为。或者,我的做法存在根本性错误。

谢谢。


但是为什么对于 LinkedList 会出现这种行为,而如果我在 intent 上添加了一个 ArrayList 实例作为额外数据,一切都会很好。并且,我不需要使用 Parcelable 吗? - anirvan
+1 非常有趣的问题。我花了一些时间思考,被它迷住了,于是自己去研究了一下。现在你和我都更聪明了(请看我的答案)。 - David Wasser
@anirvan,你不能像你所做的那样传递一个 ArrayList<CustomObject>。只有原始类型的 ArrayList 是允许的,即 StringInteger - Lalit Poptani
@LalitPoptani 你错了。只要 CustomObject 实现了 Serializable 接口,这样做就没有问题。而且它可以正常工作。 - anirvan
如果你只是想使用Serializable,那么你可以创建一个带有LinkedList的getter和setter的类,并实现Serializable接口并传递它。 - Lalit Poptani
显示剩余2条评论
3个回答

53
我可以告诉你为什么会发生这种情况,但你可能不会喜欢它 ;-)
首先一些背景信息:
在一个Intent中的 "Extras" 基本上是一个Android Bundle, 它基本上是一个键值对的HashMap。所以当你执行类似以下的操作时:
intent.putExtra(AppConstants.KEY_ITEMS, items);

Android为extras创建一个新的Bundle,并向Bundle中添加一个映射条目,其中键是AppConstants.KEY_ITEMS,值是items(即您的LinkedList对象)。

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

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

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

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

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

它为List类型的所有对象在Bundle中产生一个ArrayList

Android没有真正可以更改此行为的任何方法。至少现在你知道它为什么这样做。

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

重要提示:由于List是一个接口,这意味着您放入Bundle中实现List的任何类都将出现为ArrayList。 有趣的是,Map也在“已知对象类型”列表中,这意味着无论您把什么样的Map对象放入Bundle中(例如TreeMapSortedMap或实现Map接口的任何类),您总是会得到一个HashMap


3
哎呀哎呀 - 首先我想说,是的,我真的不喜欢这个。毕竟,“已知对象类型”的整个概念听起来好像是某个人在构建逻辑的部分时想要走捷径。而且,如果是这种情况,他们应该确实注意防止所有“不太知名”的对象类型实现Serializable,或者在意图“extras”中根本不支持Serializable。无论如何,我对你的发现感激不尽,我们因此变得更聪明了。 - anirvan
3
非常感谢您的回答,节省了我数小时的调试时间。 - Andree
2
谢谢David,这个救了我...我遇到了与savedInstanceState相同的问题,它被传递到我的片段 - 我无法弄清楚为什么Android坚持给我一个ArrayList... - Patrick

5

@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;
    }
}

然后,您可以使用此方法隐藏Android类型检查中的ListMap或其他数据类型:

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

以及之后:

items = ((Wrapper<LinkedList<String>>) intent.getSerializableExtra(KEY)).get();

你可能意思是 public class Wrapper <T extends Serializable> implements Serializable - Sergio Serra
@SergioSerra - 确实。感谢你的指出。 - Ted Hopp

0
如果您正在使用IcePick库并遇到此问题,您可以使用Ted Hoop的技术和自定义捆绑程序来避免在代码中处理包装实例。
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

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